diff --git a/.github/actions/try-runtime/action.yml b/.github/actions/try-runtime/action.yml new file mode 100644 index 0000000000..df51d53d81 --- /dev/null +++ b/.github/actions/try-runtime/action.yml @@ -0,0 +1,99 @@ +name: "Subtensor Try Runtime" +description: "Check Subtensor runtime migrations using try-runtime-cli" +author: "OpenTensor Foundation" +branding: + icon: "shield" + color: "green" + +inputs: + runtime-package: + description: "The runtime package name" + required: true + node-uri: + description: "URI of a node to scrape state" + required: true + checks: + description: "Types of checks to run." + default: "all" + blocktime: + description: "Block time in milliseconds to use for try-runtime checks." + default: "12000" + extra-args: + description: "Extra args to pass to the on-runtime-upgrade subcommand." + default: "" + cache-on-failure: + description: "Whether to save the Rust cache even if the job fails" + default: "false" + +runs: + using: "composite" + steps: + - name: Download try-runtime-cli v0.10.1 + run: | + TRY_RUNTIME_BIN="$RUNNER_TEMP/try-runtime" + curl --fail --silent --show-error --location \ + "https://github.com/paritytech/try-runtime-cli/releases/download/v0.10.1/try-runtime-x86_64-unknown-linux-musl" \ + --output "$TRY_RUNTIME_BIN" + chmod +x "$TRY_RUNTIME_BIN" + echo "TRY_RUNTIME_BIN=$TRY_RUNTIME_BIN" >> "$GITHUB_ENV" + shell: bash + + - name: Install Protoc + uses: arduino/setup-protoc@v1.3.0 + with: + version: "3.6.1" + repo-token: ${{ github.token }} + + - name: Add wasm32-unknown-unknown target + run: rustup target add wasm32-unknown-unknown + shell: bash + + - name: Add rust-src component + run: rustup component add rust-src + shell: bash + + - name: Fetch cache + uses: Swatinem/rust-cache@v2.7.0 + with: + shared-key: try-runtime + cache-on-failure: ${{ inputs.cache-on-failure }} + + - name: Build ${{ inputs.runtime-package }} + run: cargo build --profile production -p "$RUNTIME_PACKAGE" --features try-runtime -q --locked + shell: bash + env: + RUNTIME_PACKAGE: ${{ inputs.runtime-package }} + + - name: Check migrations + run: | + RUNTIME_BLOB_NAME="${RUNTIME_PACKAGE//-/_}.compact.compressed.wasm" + RUNTIME_BLOB_PATH="./target/production/wbuild/$RUNTIME_PACKAGE/$RUNTIME_BLOB_NAME" + export RUST_LOG=remote-ext=debug,runtime=debug + + command=( + "$TRY_RUNTIME_BIN" + --runtime "$RUNTIME_BLOB_PATH" + on-runtime-upgrade + "--checks=$CHECKS" + --blocktime + "$BLOCKTIME" + ) + + if [[ -n "$EXTRA_ARGS" ]]; then + read -r -a extra_args <<< "$EXTRA_ARGS" + command+=("${extra_args[@]}") + fi + + command+=(live --uri "$NODE_URI") + + echo "Running command:" + printf '%q ' "${command[@]}" + printf '\n' + "${command[@]}" + shell: bash + env: + RUNTIME_PACKAGE: ${{ inputs.runtime-package }} + NODE_URI: ${{ inputs.node-uri }} + CHECKS: ${{ inputs.checks }} + BLOCKTIME: ${{ inputs.blocktime }} + EXTRA_ARGS: ${{ inputs.extra-args }} diff --git a/.github/ai-review/README.md b/.github/ai-review/README.md index 07d997eca2..e2e00edbee 100644 --- a/.github/ai-review/README.md +++ b/.github/ai-review/README.md @@ -29,7 +29,7 @@ workflow will automatically use its token instead. ### Setup -1. Create a GitHub App under the `opentensor` org: +1. Create a GitHub App under the `RaoFoundation` org: - Settings → Developer settings → GitHub Apps → New GitHub App. - Webhook: not needed; disable. - Repository permissions: @@ -39,7 +39,7 @@ workflow will automatically use its token instead. - **Metadata**: Read - User permissions: none. - "Where can this GitHub App be installed?": Only on this account. -2. Install the App on the `opentensor/subtensor` repo (only). +2. Install the App on the `RaoFoundation/subtensor` repo (only). 3. From the App settings page, generate a private key (`.pem` file). 4. In repo Settings → Secrets and variables → Actions: - Variables tab: add `AI_REVIEW_APP_ID` = the App's numeric ID. @@ -98,7 +98,7 @@ the PR number. `workflow_dispatch` runs in base context with secrets available, performs the real review, and the required checks turn green. ```bash -gh workflow run ai-review.yml --repo opentensor/subtensor -f pr_number= +gh workflow run ai-review.yml --repo RaoFoundation/subtensor -f pr_number= ``` ## Required-checks setup @@ -114,7 +114,7 @@ After the first successful run, add these to branch protection on `devnet-ready` Manual trigger: ```bash -gh workflow run ai-review-index-gittensor.yml --repo opentensor/subtensor +gh workflow run ai-review-index-gittensor.yml --repo RaoFoundation/subtensor ``` Daily cron is already configured (06:17 UTC). The indexer opens a PR with any diff --git a/.github/ai-review/auditor.md b/.github/ai-review/auditor.md index 3db5af1393..78970031bb 100644 --- a/.github/ai-review/auditor.md +++ b/.github/ai-review/auditor.md @@ -70,7 +70,7 @@ Look up the PR author's gittensor association: 1. Read `.github/ai-review/known-gittensor-accounts.json` (auto-maintained from on-chain bounty data). 2. Read `.github/ai-review/gittensor-accounts.txt` (nucleus-curated supplement). -3. If neither matches, apply the heuristic: ≥70% of the author's recent merged PRs are to gittensor-whitelisted repos (subtensor / opentensor / latent-to / etc.) AND average PR size is small. If so, classify as `LIKELY`. +3. If neither matches, apply the heuristic: ≥70% of the author's recent merged PRs are to gittensor-whitelisted repos (subtensor / RaoFoundation / opentensor / latent-to / etc.) AND average PR size is small. If so, classify as `LIKELY`. Tier the author: - **KNOWN** (on-chain or curated): high confidence gittensor miner. diff --git a/.github/ai-review/common.md b/.github/ai-review/common.md index 50471c7f0c..8ef0bff5d9 100644 --- a/.github/ai-review/common.md +++ b/.github/ai-review/common.md @@ -1,6 +1,6 @@ # Subtensor AI Review — Shared Context -You are reviewing a pull request to **opentensor/subtensor**, the Substrate-based runtime for the Bittensor blockchain (~$4B market cap). Lives and livelihoods depend on the security and correctness of this code. Be thorough, precise, and uncompromising on safety. +You are reviewing a pull request to **RaoFoundation/subtensor**, the Substrate-based runtime for the Bittensor blockchain (~$4B market cap). Lives and livelihoods depend on the security and correctness of this code. Be thorough, precise, and uncompromising on safety. ## Repository topology diff --git a/.github/ai-review/post_review.py b/.github/ai-review/post_review.py index beb0c362cc..50853ee54d 100755 --- a/.github/ai-review/post_review.py +++ b/.github/ai-review/post_review.py @@ -25,7 +25,7 @@ Usage: GH_TOKEN=... python3 post_review.py \ - --persona skeptic --pr 2668 --repo opentensor/subtensor \ + --persona skeptic --pr 2668 --repo RaoFoundation/subtensor \ --commit-sha --input-file skeptic-output.json """ diff --git a/.github/ai-review/prefetch.sh b/.github/ai-review/prefetch.sh index 3a19a3a87b..6d54ca4290 100755 --- a/.github/ai-review/prefetch.sh +++ b/.github/ai-review/prefetch.sh @@ -7,7 +7,7 @@ set -euo pipefail : "${PR_NUMBER:?PR_NUMBER required}" -: "${REPO:?REPO required (e.g. opentensor/subtensor)}" +: "${REPO:?REPO required (e.g. RaoFoundation/subtensor)}" : "${GH_TOKEN:?GH_TOKEN required (used here only — NOT passed to Codex)}" OUTPUT_DIR="${OUTPUT_DIR:-/tmp/ai-review-context}" diff --git a/.github/ai-review/skeptic.md b/.github/ai-review/skeptic.md index 2198b85585..2600b9232f 100644 --- a/.github/ai-review/skeptic.md +++ b/.github/ai-review/skeptic.md @@ -58,7 +58,7 @@ diff from a HIGH-risk contributor tips toward `[VULNERABLE]`. **Account-age + contribution-graph tiers** (apply before reading the diff): - **VERY HIGH scrutiny**: account < 30 days old, OR < 10 lifetime contributions, OR < 3 public repos. Treat any non-trivial change as suspicious until proven otherwise. A `[SAFE]` verdict here requires the diff to be small, mechanical, and obviously correct. -- **HIGH scrutiny**: account < 90 days old, OR < 50 lifetime contributions, OR no contribution history outside of subtensor / opentensor. +- **HIGH scrutiny**: account < 90 days old, OR < 50 lifetime contributions, OR no contribution history outside of subtensor / RaoFoundation / opentensor (the org's former name). - **MEDIUM scrutiny**: account 90 days – 1 year old with modest contribution history, OR established account whose contribution pattern recently pivoted heavily toward subtensor / gittensor-whitelisted repos. - **BASELINE scrutiny**: account > 1 year old with substantive non-subtensor history, OR known nucleus member. @@ -73,7 +73,7 @@ diff from a HIGH-risk contributor tips toward `[VULNERABLE]`. **Patterns that lower risk**: - Established contributor with a long history of substantive merged PRs to this repo. -- "Nucleus" team member: `gh api repos/opentensor/subtensor/collaborators/$AUTHOR/permission` — `admin` or `write` permission. +- "Nucleus" team member: `gh api repos/RaoFoundation/subtensor/collaborators/$AUTHOR/permission` — `admin` or `write` permission. - Substantive contribution history to unrelated reputable open-source projects. ## Step 2 — Diff analysis diff --git a/.github/workflows/ai-review.yml b/.github/workflows/ai-review.yml index 2c68114327..7ed7e7fb1e 100644 --- a/.github/workflows/ai-review.yml +++ b/.github/workflows/ai-review.yml @@ -252,7 +252,7 @@ jobs: output-schema-file: /tmp/ai-review-trusted/codex-output-schema.json prompt: | You are running as the **Skeptic** persona reviewing PR #${{ needs.decide.outputs.pr_number }} - in the opentensor/subtensor repository. + in the RaoFoundation/subtensor repository. Branch: `${{ needs.decide.outputs.head_ref }}` -> `${{ needs.decide.outputs.base_ref }}` Author: `${{ needs.decide.outputs.author }}` @@ -469,7 +469,7 @@ jobs: output-schema-file: /tmp/ai-review-trusted/codex-output-schema.json prompt: | You are running as the **Auditor** persona reviewing PR #${{ needs.decide.outputs.pr_number }} - in the opentensor/subtensor repository. + in the RaoFoundation/subtensor repository. Branch: `${{ needs.decide.outputs.head_ref }}` -> `${{ needs.decide.outputs.base_ref }}` Author: `${{ needs.decide.outputs.author }}` diff --git a/.github/workflows/check-bittensor-e2e-tests.yml b/.github/workflows/check-bittensor-e2e-tests.yml index dea4c4ea9f..8988eacebf 100644 --- a/.github/workflows/check-bittensor-e2e-tests.yml +++ b/.github/workflows/check-bittensor-e2e-tests.yml @@ -23,6 +23,10 @@ on: env: CARGO_TERM_COLOR: always VERBOSE: ${{ github.event.inputs.verbose }} + # Name the locally built localnet image under the RaoFoundation namespace + # (lowercase — Docker requires it). The bittensor/btcli e2e harnesses read + # LOCALNET_IMAGE_NAME from the environment, overriding their built-in default. + LOCALNET_IMAGE_NAME: ghcr.io/raofoundation/subtensor-localnet:devnet-ready jobs: check-label: @@ -75,7 +79,7 @@ jobs: - name: Research preparation working-directory: ${{ github.workspace }} - run: git clone https://github.com/opentensor/btcli.git + run: git clone https://github.com/RaoFoundation/btcli.git - name: Checkout working-directory: ${{ github.workspace }}/btcli @@ -135,7 +139,7 @@ jobs: - name: Research preparation working-directory: ${{ github.workspace }} - run: git clone https://github.com/opentensor/bittensor.git + run: git clone https://github.com/RaoFoundation/bittensor.git - name: Checkout working-directory: ${{ github.workspace }}/bittensor @@ -340,7 +344,7 @@ jobs: - name: Clone Bittensor CLI repo working-directory: ${{ github.workspace }} - run: git clone https://github.com/opentensor/btcli.git + run: git clone https://github.com/RaoFoundation/btcli.git - name: Setup Bittensor-cli from cloned repo working-directory: ${{ github.workspace }}/btcli @@ -361,7 +365,7 @@ jobs: run: docker load -i subtensor-localnet.tar - name: Retag Docker Image - run: docker tag localnet ghcr.io/opentensor/subtensor-localnet:devnet-ready + run: docker tag localnet "$LOCALNET_IMAGE_NAME" - name: Run with retry working-directory: ${{ github.workspace }}/btcli @@ -421,7 +425,7 @@ jobs: - name: Clone Bittensor SDK repo working-directory: ${{ github.workspace }} - run: git clone https://github.com/opentensor/bittensor.git + run: git clone https://github.com/RaoFoundation/bittensor.git - name: Setup Bittensor SDK from cloned repo working-directory: ${{ github.workspace }}/bittensor @@ -442,7 +446,7 @@ jobs: run: docker load -i subtensor-localnet.tar - name: Retag Docker Image - run: docker tag localnet ghcr.io/opentensor/subtensor-localnet:devnet-ready + run: docker tag localnet "$LOCALNET_IMAGE_NAME" - name: Run with retry working-directory: ${{ github.workspace }}/bittensor diff --git a/.github/workflows/check-node-compat.yml b/.github/workflows/check-node-compat.yml index 30495b45f2..08694dc343 100644 --- a/.github/workflows/check-node-compat.yml +++ b/.github/workflows/check-node-compat.yml @@ -22,8 +22,10 @@ jobs: strategy: matrix: version: - - { name: old, ref: devnet-ready } - - { name: new, ref: ${{ github.head_ref }} } + - name: old + ref: devnet-ready + - name: new + ref: ${{ github.head_ref }} steps: - name: Install dependencies diff --git a/.github/workflows/docker-localnet.yml b/.github/workflows/docker-localnet.yml index 1d5d246d05..dfeebc413e 100644 --- a/.github/workflows/docker-localnet.yml +++ b/.github/workflows/docker-localnet.yml @@ -200,6 +200,10 @@ jobs: username: ${{ github.actor }} password: ${{ secrets.GITHUB_TOKEN }} + - name: Determine image name + # Docker requires lowercase image names; github.repository is RaoFoundation/subtensor + run: echo "image=ghcr.io/${GITHUB_REPOSITORY,,}-localnet" >> $GITHUB_ENV + - name: Build and push Docker image uses: docker/build-push-action@v6 with: @@ -211,5 +215,5 @@ jobs: push: true platforms: linux/amd64,linux/arm64 tags: | - ghcr.io/${{ github.repository }}-localnet:${{ needs.setup.outputs.tag }} - ${{ needs.setup.outputs.latest_tag == 'true' && format('ghcr.io/{0}-localnet:latest', github.repository) || '' }} + ${{ env.image }}:${{ needs.setup.outputs.tag }} + ${{ needs.setup.outputs.latest_tag == 'true' && format('{0}:latest', env.image) || '' }} diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml index 9f2de4ec88..33947fcf2b 100644 --- a/.github/workflows/docker.yml +++ b/.github/workflows/docker.yml @@ -45,6 +45,10 @@ jobs: echo "latest_tag=false" >> $GITHUB_ENV fi + - name: Determine image name + # Docker requires lowercase image names; github.repository is RaoFoundation/subtensor + run: echo "image=ghcr.io/${GITHUB_REPOSITORY,,}" >> $GITHUB_ENV + - name: Checkout code uses: actions/checkout@v4 with: @@ -70,5 +74,5 @@ jobs: push: true platforms: linux/amd64,linux/arm64 tags: | - ghcr.io/${{ github.repository }}:${{ env.tag }} - ${{ env.latest_tag == 'true' && format('ghcr.io/{0}:latest', github.repository) || '' }} + ${{ env.image }}:${{ env.tag }} + ${{ env.latest_tag == 'true' && format('{0}:latest', env.image) || '' }} diff --git a/.github/workflows/label-triggers.yml b/.github/workflows/label-triggers.yml index 51147e2565..76c36750b1 100644 --- a/.github/workflows/label-triggers.yml +++ b/.github/workflows/label-triggers.yml @@ -24,5 +24,5 @@ jobs: issue_number: context.issue.number, owner: context.repo.owner, repo: context.repo.repo, - body: '@opentensor/cerebrum / @opentensor/gyrus / @opentensor/cortex breaking change detected! Please prepare accordingly!' + body: '@RaoFoundation/arbos breaking change detected! Please prepare accordingly!' }) diff --git a/.github/workflows/try-runtime.yml b/.github/workflows/try-runtime.yml index 2cecce2a58..9aa732e3b9 100644 --- a/.github/workflows/try-runtime.yml +++ b/.github/workflows/try-runtime.yml @@ -29,17 +29,13 @@ jobs: with: toolchain: stable - - name: Utilize Shared Rust Cache - uses: Swatinem/rust-cache@v2 - with: - key: "try-runtime" - - name: Run Try Runtime Checks - uses: "paritytech/try-runtime-gha@v0.1.0" + uses: ./.github/actions/try-runtime with: runtime-package: "node-subtensor-runtime" node-uri: "wss://dev.chain.opentensor.ai:443" checks: "all" + blocktime: "12000" extra-args: "--disable-spec-version-check --no-weight-warnings" check-testnet: @@ -60,17 +56,13 @@ jobs: with: toolchain: stable - - name: Utilize Shared Rust Cache - uses: Swatinem/rust-cache@v2 - with: - key: "try-runtime" - - name: Run Try Runtime Checks - uses: "paritytech/try-runtime-gha@v0.1.0" + uses: ./.github/actions/try-runtime with: runtime-package: "node-subtensor-runtime" node-uri: "wss://archive.dev.opentensor.ai:8443" checks: "all" + blocktime: "12000" extra-args: "--disable-spec-version-check --no-weight-warnings" check-finney: @@ -91,16 +83,12 @@ jobs: with: toolchain: stable - - name: Utilize Shared Rust Cache - uses: Swatinem/rust-cache@v2 - with: - key: try-runtime - cache-on-failure: true - - name: Run Try Runtime Checks - uses: "paritytech/try-runtime-gha@v0.1.0" + uses: ./.github/actions/try-runtime with: runtime-package: "node-subtensor-runtime" node-uri: "wss://archive.dev.opentensor.ai:443" checks: "all" + blocktime: "12000" extra-args: "--disable-spec-version-check --no-weight-warnings" + cache-on-failure: "true" diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 8ac806367f..b3396e7c31 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -3,7 +3,7 @@ ## Lifecycle of a Pull Request 1. Individuals wishing to contribute to subtensor should develop their change/feature/fix in a - [Pull Request](https://github.com/opentensor/subtensor/compare) (PR) targeting the `devnet-ready` + [Pull Request](https://github.com/RaoFoundation/subtensor/compare) (PR) targeting the `devnet-ready` branch of the subtensor GitHub repository. It is recommended to start your pull request as a draft initially until you are ready to have other developers actively look at it. Any changes to pallet/runtime code should be accompanied by integration and/or unit tests fully diff --git a/Cargo.lock b/Cargo.lock index b49277401c..2742764a10 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4513,7 +4513,7 @@ dependencies = [ [[package]] name = "fc-api" version = "1.0.0-dev" -source = "git+https://github.com/opentensor/frontier?rev=d553a11551333c2457d9e64d6d912e2210a4ca26#d553a11551333c2457d9e64d6d912e2210a4ca26" +source = "git+https://github.com/RaoFoundation/frontier?rev=d553a11551333c2457d9e64d6d912e2210a4ca26#d553a11551333c2457d9e64d6d912e2210a4ca26" dependencies = [ "async-trait", "fp-storage", @@ -4525,7 +4525,7 @@ dependencies = [ [[package]] name = "fc-aura" version = "1.0.0-dev" -source = "git+https://github.com/opentensor/frontier?rev=d553a11551333c2457d9e64d6d912e2210a4ca26#d553a11551333c2457d9e64d6d912e2210a4ca26" +source = "git+https://github.com/RaoFoundation/frontier?rev=d553a11551333c2457d9e64d6d912e2210a4ca26#d553a11551333c2457d9e64d6d912e2210a4ca26" dependencies = [ "fc-rpc", "fp-storage", @@ -4541,7 +4541,7 @@ dependencies = [ [[package]] name = "fc-babe" version = "1.0.0-dev" -source = "git+https://github.com/opentensor/frontier?rev=d553a11551333c2457d9e64d6d912e2210a4ca26#d553a11551333c2457d9e64d6d912e2210a4ca26" +source = "git+https://github.com/RaoFoundation/frontier?rev=d553a11551333c2457d9e64d6d912e2210a4ca26#d553a11551333c2457d9e64d6d912e2210a4ca26" dependencies = [ "fc-rpc", "sc-client-api", @@ -4557,7 +4557,7 @@ dependencies = [ [[package]] name = "fc-consensus" version = "2.0.0-dev" -source = "git+https://github.com/opentensor/frontier?rev=d553a11551333c2457d9e64d6d912e2210a4ca26#d553a11551333c2457d9e64d6d912e2210a4ca26" +source = "git+https://github.com/RaoFoundation/frontier?rev=d553a11551333c2457d9e64d6d912e2210a4ca26#d553a11551333c2457d9e64d6d912e2210a4ca26" dependencies = [ "async-trait", "fp-consensus", @@ -4573,7 +4573,7 @@ dependencies = [ [[package]] name = "fc-db" version = "2.0.0-dev" -source = "git+https://github.com/opentensor/frontier?rev=d553a11551333c2457d9e64d6d912e2210a4ca26#d553a11551333c2457d9e64d6d912e2210a4ca26" +source = "git+https://github.com/RaoFoundation/frontier?rev=d553a11551333c2457d9e64d6d912e2210a4ca26#d553a11551333c2457d9e64d6d912e2210a4ca26" dependencies = [ "async-trait", "ethereum", @@ -4603,7 +4603,7 @@ dependencies = [ [[package]] name = "fc-mapping-sync" version = "2.0.0-dev" -source = "git+https://github.com/opentensor/frontier?rev=d553a11551333c2457d9e64d6d912e2210a4ca26#d553a11551333c2457d9e64d6d912e2210a4ca26" +source = "git+https://github.com/RaoFoundation/frontier?rev=d553a11551333c2457d9e64d6d912e2210a4ca26#d553a11551333c2457d9e64d6d912e2210a4ca26" dependencies = [ "fc-db", "fc-storage", @@ -4626,7 +4626,7 @@ dependencies = [ [[package]] name = "fc-rpc" version = "2.0.0-dev" -source = "git+https://github.com/opentensor/frontier?rev=d553a11551333c2457d9e64d6d912e2210a4ca26#d553a11551333c2457d9e64d6d912e2210a4ca26" +source = "git+https://github.com/RaoFoundation/frontier?rev=d553a11551333c2457d9e64d6d912e2210a4ca26#d553a11551333c2457d9e64d6d912e2210a4ca26" dependencies = [ "ethereum", "ethereum-types", @@ -4677,7 +4677,7 @@ dependencies = [ [[package]] name = "fc-rpc-core" version = "1.1.0-dev" -source = "git+https://github.com/opentensor/frontier?rev=d553a11551333c2457d9e64d6d912e2210a4ca26#d553a11551333c2457d9e64d6d912e2210a4ca26" +source = "git+https://github.com/RaoFoundation/frontier?rev=d553a11551333c2457d9e64d6d912e2210a4ca26#d553a11551333c2457d9e64d6d912e2210a4ca26" dependencies = [ "ethereum", "ethereum-types", @@ -4692,7 +4692,7 @@ dependencies = [ [[package]] name = "fc-storage" version = "1.0.0-dev" -source = "git+https://github.com/opentensor/frontier?rev=d553a11551333c2457d9e64d6d912e2210a4ca26#d553a11551333c2457d9e64d6d912e2210a4ca26" +source = "git+https://github.com/RaoFoundation/frontier?rev=d553a11551333c2457d9e64d6d912e2210a4ca26#d553a11551333c2457d9e64d6d912e2210a4ca26" dependencies = [ "ethereum", "ethereum-types", @@ -4884,7 +4884,7 @@ dependencies = [ [[package]] name = "fp-account" version = "1.0.0-dev" -source = "git+https://github.com/opentensor/frontier?rev=d553a11551333c2457d9e64d6d912e2210a4ca26#d553a11551333c2457d9e64d6d912e2210a4ca26" +source = "git+https://github.com/RaoFoundation/frontier?rev=d553a11551333c2457d9e64d6d912e2210a4ca26#d553a11551333c2457d9e64d6d912e2210a4ca26" dependencies = [ "hex", "impl-serde", @@ -4902,7 +4902,7 @@ dependencies = [ [[package]] name = "fp-consensus" version = "2.0.0-dev" -source = "git+https://github.com/opentensor/frontier?rev=d553a11551333c2457d9e64d6d912e2210a4ca26#d553a11551333c2457d9e64d6d912e2210a4ca26" +source = "git+https://github.com/RaoFoundation/frontier?rev=d553a11551333c2457d9e64d6d912e2210a4ca26#d553a11551333c2457d9e64d6d912e2210a4ca26" dependencies = [ "ethereum", "parity-scale-codec", @@ -4913,7 +4913,7 @@ dependencies = [ [[package]] name = "fp-ethereum" version = "1.0.0-dev" -source = "git+https://github.com/opentensor/frontier?rev=d553a11551333c2457d9e64d6d912e2210a4ca26#d553a11551333c2457d9e64d6d912e2210a4ca26" +source = "git+https://github.com/RaoFoundation/frontier?rev=d553a11551333c2457d9e64d6d912e2210a4ca26#d553a11551333c2457d9e64d6d912e2210a4ca26" dependencies = [ "ethereum", "ethereum-types", @@ -4925,7 +4925,7 @@ dependencies = [ [[package]] name = "fp-evm" version = "3.0.0-dev" -source = "git+https://github.com/opentensor/frontier?rev=d553a11551333c2457d9e64d6d912e2210a4ca26#d553a11551333c2457d9e64d6d912e2210a4ca26" +source = "git+https://github.com/RaoFoundation/frontier?rev=d553a11551333c2457d9e64d6d912e2210a4ca26#d553a11551333c2457d9e64d6d912e2210a4ca26" dependencies = [ "environmental", "evm", @@ -4941,7 +4941,7 @@ dependencies = [ [[package]] name = "fp-rpc" version = "3.0.0-dev" -source = "git+https://github.com/opentensor/frontier?rev=d553a11551333c2457d9e64d6d912e2210a4ca26#d553a11551333c2457d9e64d6d912e2210a4ca26" +source = "git+https://github.com/RaoFoundation/frontier?rev=d553a11551333c2457d9e64d6d912e2210a4ca26#d553a11551333c2457d9e64d6d912e2210a4ca26" dependencies = [ "ethereum", "ethereum-types", @@ -4957,7 +4957,7 @@ dependencies = [ [[package]] name = "fp-self-contained" version = "1.0.0-dev" -source = "git+https://github.com/opentensor/frontier?rev=d553a11551333c2457d9e64d6d912e2210a4ca26#d553a11551333c2457d9e64d6d912e2210a4ca26" +source = "git+https://github.com/RaoFoundation/frontier?rev=d553a11551333c2457d9e64d6d912e2210a4ca26#d553a11551333c2457d9e64d6d912e2210a4ca26" dependencies = [ "frame-support", "parity-scale-codec", @@ -4969,7 +4969,7 @@ dependencies = [ [[package]] name = "fp-storage" version = "2.0.0" -source = "git+https://github.com/opentensor/frontier?rev=d553a11551333c2457d9e64d6d912e2210a4ca26#d553a11551333c2457d9e64d6d912e2210a4ca26" +source = "git+https://github.com/RaoFoundation/frontier?rev=d553a11551333c2457d9e64d6d912e2210a4ca26#d553a11551333c2457d9e64d6d912e2210a4ca26" dependencies = [ "parity-scale-codec", "serde", @@ -9279,7 +9279,7 @@ dependencies = [ [[package]] name = "pallet-base-fee" version = "1.0.0" -source = "git+https://github.com/opentensor/frontier?rev=d553a11551333c2457d9e64d6d912e2210a4ca26#d553a11551333c2457d9e64d6d912e2210a4ca26" +source = "git+https://github.com/RaoFoundation/frontier?rev=d553a11551333c2457d9e64d6d912e2210a4ca26#d553a11551333c2457d9e64d6d912e2210a4ca26" dependencies = [ "fp-evm", "frame-support", @@ -9857,7 +9857,7 @@ dependencies = [ [[package]] name = "pallet-ethereum" version = "4.0.0-dev" -source = "git+https://github.com/opentensor/frontier?rev=d553a11551333c2457d9e64d6d912e2210a4ca26#d553a11551333c2457d9e64d6d912e2210a4ca26" +source = "git+https://github.com/RaoFoundation/frontier?rev=d553a11551333c2457d9e64d6d912e2210a4ca26#d553a11551333c2457d9e64d6d912e2210a4ca26" dependencies = [ "ethereum", "ethereum-types", @@ -9880,7 +9880,7 @@ dependencies = [ [[package]] name = "pallet-evm" version = "6.0.0-dev" -source = "git+https://github.com/opentensor/frontier?rev=d553a11551333c2457d9e64d6d912e2210a4ca26#d553a11551333c2457d9e64d6d912e2210a4ca26" +source = "git+https://github.com/RaoFoundation/frontier?rev=d553a11551333c2457d9e64d6d912e2210a4ca26#d553a11551333c2457d9e64d6d912e2210a4ca26" dependencies = [ "cumulus-primitives-storage-weight-reclaim", "environmental", @@ -9905,7 +9905,7 @@ dependencies = [ [[package]] name = "pallet-evm-chain-id" version = "1.0.0-dev" -source = "git+https://github.com/opentensor/frontier?rev=d553a11551333c2457d9e64d6d912e2210a4ca26#d553a11551333c2457d9e64d6d912e2210a4ca26" +source = "git+https://github.com/RaoFoundation/frontier?rev=d553a11551333c2457d9e64d6d912e2210a4ca26#d553a11551333c2457d9e64d6d912e2210a4ca26" dependencies = [ "frame-support", "frame-system", @@ -9916,7 +9916,7 @@ dependencies = [ [[package]] name = "pallet-evm-precompile-bn128" version = "2.0.0-dev" -source = "git+https://github.com/opentensor/frontier?rev=d553a11551333c2457d9e64d6d912e2210a4ca26#d553a11551333c2457d9e64d6d912e2210a4ca26" +source = "git+https://github.com/RaoFoundation/frontier?rev=d553a11551333c2457d9e64d6d912e2210a4ca26#d553a11551333c2457d9e64d6d912e2210a4ca26" dependencies = [ "fp-evm", "sp-core", @@ -9926,7 +9926,7 @@ dependencies = [ [[package]] name = "pallet-evm-precompile-dispatch" version = "2.0.0-dev" -source = "git+https://github.com/opentensor/frontier?rev=d553a11551333c2457d9e64d6d912e2210a4ca26#d553a11551333c2457d9e64d6d912e2210a4ca26" +source = "git+https://github.com/RaoFoundation/frontier?rev=d553a11551333c2457d9e64d6d912e2210a4ca26#d553a11551333c2457d9e64d6d912e2210a4ca26" dependencies = [ "fp-evm", "frame-support", @@ -9938,7 +9938,7 @@ dependencies = [ [[package]] name = "pallet-evm-precompile-modexp" version = "2.0.0-dev" -source = "git+https://github.com/opentensor/frontier?rev=d553a11551333c2457d9e64d6d912e2210a4ca26#d553a11551333c2457d9e64d6d912e2210a4ca26" +source = "git+https://github.com/RaoFoundation/frontier?rev=d553a11551333c2457d9e64d6d912e2210a4ca26#d553a11551333c2457d9e64d6d912e2210a4ca26" dependencies = [ "fp-evm", "num", @@ -9947,7 +9947,7 @@ dependencies = [ [[package]] name = "pallet-evm-precompile-sha3fips" version = "2.0.0-dev" -source = "git+https://github.com/opentensor/frontier?rev=d553a11551333c2457d9e64d6d912e2210a4ca26#d553a11551333c2457d9e64d6d912e2210a4ca26" +source = "git+https://github.com/RaoFoundation/frontier?rev=d553a11551333c2457d9e64d6d912e2210a4ca26#d553a11551333c2457d9e64d6d912e2210a4ca26" dependencies = [ "fp-evm", "tiny-keccak", @@ -9956,7 +9956,7 @@ dependencies = [ [[package]] name = "pallet-evm-precompile-simple" version = "2.0.0-dev" -source = "git+https://github.com/opentensor/frontier?rev=d553a11551333c2457d9e64d6d912e2210a4ca26#d553a11551333c2457d9e64d6d912e2210a4ca26" +source = "git+https://github.com/RaoFoundation/frontier?rev=d553a11551333c2457d9e64d6d912e2210a4ca26#d553a11551333c2457d9e64d6d912e2210a4ca26" dependencies = [ "fp-evm", "ripemd", @@ -10024,7 +10024,7 @@ dependencies = [ [[package]] name = "pallet-hotfix-sufficients" version = "1.0.0" -source = "git+https://github.com/opentensor/frontier?rev=d553a11551333c2457d9e64d6d912e2210a4ca26#d553a11551333c2457d9e64d6d912e2210a4ca26" +source = "git+https://github.com/RaoFoundation/frontier?rev=d553a11551333c2457d9e64d6d912e2210a4ca26#d553a11551333c2457d9e64d6d912e2210a4ca26" dependencies = [ "frame-benchmarking", "frame-support", @@ -13303,7 +13303,7 @@ dependencies = [ [[package]] name = "precompile-utils" version = "0.1.0" -source = "git+https://github.com/opentensor/frontier?rev=d553a11551333c2457d9e64d6d912e2210a4ca26#d553a11551333c2457d9e64d6d912e2210a4ca26" +source = "git+https://github.com/RaoFoundation/frontier?rev=d553a11551333c2457d9e64d6d912e2210a4ca26#d553a11551333c2457d9e64d6d912e2210a4ca26" dependencies = [ "derive_more 1.0.0", "environmental", @@ -13332,7 +13332,7 @@ dependencies = [ [[package]] name = "precompile-utils-macro" version = "0.1.0" -source = "git+https://github.com/opentensor/frontier?rev=d553a11551333c2457d9e64d6d912e2210a4ca26#d553a11551333c2457d9e64d6d912e2210a4ca26" +source = "git+https://github.com/RaoFoundation/frontier?rev=d553a11551333c2457d9e64d6d912e2210a4ca26#d553a11551333c2457d9e64d6d912e2210a4ca26" dependencies = [ "case", "num_enum", @@ -18538,6 +18538,7 @@ dependencies = [ "approx", "environmental", "frame-support", + "impl-trait-for-tuples", "num-traits", "parity-scale-codec", "polkadot-runtime-common", @@ -18545,6 +18546,7 @@ dependencies = [ "serde", "sp-arithmetic", "sp-core", + "sp-io", "sp-rpc", "sp-runtime", "substrate-fixed", @@ -19772,7 +19774,7 @@ checksum = "6a02e4885ed3bc0f2de90ea6dd45ebcbb66dacffe03547fadbb0eeae2770887d" [[package]] name = "w3f-bls" version = "0.1.3" -source = "git+https://github.com/opentensor/bls?branch=fix-no-std#4ac443d11a6c9fdebe329d113702ad7387ba1688" +source = "git+https://github.com/RaoFoundation/bls?branch=fix-no-std#4ac443d11a6c9fdebe329d113702ad7387ba1688" dependencies = [ "ark-bls12-377", "ark-bls12-381 0.4.0", diff --git a/Cargo.toml b/Cargo.toml index 6f160c29e4..8b8eb786ce 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -7,7 +7,7 @@ homepage = "https://substrate.io/" edition = "2024" license = "Unlicense" publish = false -repository = "https://github.com/opentensor/subtensor" +repository = "https://github.com/RaoFoundation/subtensor" [build-dependencies] subtensor-linting = { path = "support/linting", version = "0.1.0" } @@ -250,41 +250,41 @@ runtime-common = { package = "polkadot-runtime-common", git = "https://github.co # Frontier # current frontier branch is polkadot-stable2506-2-otf-get-call-indices-metadata -fp-evm = { git = "https://github.com/opentensor/frontier", rev = "d553a11551333c2457d9e64d6d912e2210a4ca26", default-features = false } -fp-rpc = { git = "https://github.com/opentensor/frontier", rev = "d553a11551333c2457d9e64d6d912e2210a4ca26", default-features = false } -fp-self-contained = { git = "https://github.com/opentensor/frontier", rev = "d553a11551333c2457d9e64d6d912e2210a4ca26", default-features = false } -fp-account = { git = "https://github.com/opentensor/frontier", rev = "d553a11551333c2457d9e64d6d912e2210a4ca26", default-features = false } -fc-storage = { git = "https://github.com/opentensor/frontier", rev = "d553a11551333c2457d9e64d6d912e2210a4ca26", default-features = false } -fc-db = { git = "https://github.com/opentensor/frontier", rev = "d553a11551333c2457d9e64d6d912e2210a4ca26", default-features = false } -fc-consensus = { git = "https://github.com/opentensor/frontier", rev = "d553a11551333c2457d9e64d6d912e2210a4ca26", default-features = false } -fp-consensus = { git = "https://github.com/opentensor/frontier", rev = "d553a11551333c2457d9e64d6d912e2210a4ca26", default-features = false } -fp-dynamic-fee = { git = "https://github.com/opentensor/frontier", rev = "d553a11551333c2457d9e64d6d912e2210a4ca26", default-features = false } -fc-api = { git = "https://github.com/opentensor/frontier", rev = "d553a11551333c2457d9e64d6d912e2210a4ca26", default-features = false } -fc-rpc = { git = "https://github.com/opentensor/frontier", rev = "d553a11551333c2457d9e64d6d912e2210a4ca26", default-features = false } -fc-rpc-core = { git = "https://github.com/opentensor/frontier", rev = "d553a11551333c2457d9e64d6d912e2210a4ca26", default-features = false } -fc-aura = { git = "https://github.com/opentensor/frontier", rev = "d553a11551333c2457d9e64d6d912e2210a4ca26", default-features = false } -fc-babe = { git = "https://github.com/opentensor/frontier", rev = "d553a11551333c2457d9e64d6d912e2210a4ca26", default-features = false } -fc-mapping-sync = { git = "https://github.com/opentensor/frontier", rev = "d553a11551333c2457d9e64d6d912e2210a4ca26", default-features = false } -precompile-utils = { git = "https://github.com/opentensor/frontier", rev = "d553a11551333c2457d9e64d6d912e2210a4ca26", default-features = false } +fp-evm = { git = "https://github.com/RaoFoundation/frontier", rev = "d553a11551333c2457d9e64d6d912e2210a4ca26", default-features = false } +fp-rpc = { git = "https://github.com/RaoFoundation/frontier", rev = "d553a11551333c2457d9e64d6d912e2210a4ca26", default-features = false } +fp-self-contained = { git = "https://github.com/RaoFoundation/frontier", rev = "d553a11551333c2457d9e64d6d912e2210a4ca26", default-features = false } +fp-account = { git = "https://github.com/RaoFoundation/frontier", rev = "d553a11551333c2457d9e64d6d912e2210a4ca26", default-features = false } +fc-storage = { git = "https://github.com/RaoFoundation/frontier", rev = "d553a11551333c2457d9e64d6d912e2210a4ca26", default-features = false } +fc-db = { git = "https://github.com/RaoFoundation/frontier", rev = "d553a11551333c2457d9e64d6d912e2210a4ca26", default-features = false } +fc-consensus = { git = "https://github.com/RaoFoundation/frontier", rev = "d553a11551333c2457d9e64d6d912e2210a4ca26", default-features = false } +fp-consensus = { git = "https://github.com/RaoFoundation/frontier", rev = "d553a11551333c2457d9e64d6d912e2210a4ca26", default-features = false } +fp-dynamic-fee = { git = "https://github.com/RaoFoundation/frontier", rev = "d553a11551333c2457d9e64d6d912e2210a4ca26", default-features = false } +fc-api = { git = "https://github.com/RaoFoundation/frontier", rev = "d553a11551333c2457d9e64d6d912e2210a4ca26", default-features = false } +fc-rpc = { git = "https://github.com/RaoFoundation/frontier", rev = "d553a11551333c2457d9e64d6d912e2210a4ca26", default-features = false } +fc-rpc-core = { git = "https://github.com/RaoFoundation/frontier", rev = "d553a11551333c2457d9e64d6d912e2210a4ca26", default-features = false } +fc-aura = { git = "https://github.com/RaoFoundation/frontier", rev = "d553a11551333c2457d9e64d6d912e2210a4ca26", default-features = false } +fc-babe = { git = "https://github.com/RaoFoundation/frontier", rev = "d553a11551333c2457d9e64d6d912e2210a4ca26", default-features = false } +fc-mapping-sync = { git = "https://github.com/RaoFoundation/frontier", rev = "d553a11551333c2457d9e64d6d912e2210a4ca26", default-features = false } +precompile-utils = { git = "https://github.com/RaoFoundation/frontier", rev = "d553a11551333c2457d9e64d6d912e2210a4ca26", default-features = false } # Frontier FRAME -pallet-base-fee = { git = "https://github.com/opentensor/frontier", rev = "d553a11551333c2457d9e64d6d912e2210a4ca26", default-features = false } -pallet-dynamic-fee = { git = "https://github.com/opentensor/frontier", rev = "d553a11551333c2457d9e64d6d912e2210a4ca26", default-features = false } -pallet-ethereum = { git = "https://github.com/opentensor/frontier", rev = "d553a11551333c2457d9e64d6d912e2210a4ca26", default-features = false } -pallet-evm = { git = "https://github.com/opentensor/frontier", rev = "d553a11551333c2457d9e64d6d912e2210a4ca26", default-features = false } -pallet-evm-precompile-dispatch = { git = "https://github.com/opentensor/frontier", rev = "d553a11551333c2457d9e64d6d912e2210a4ca26", default-features = false } -pallet-evm-chain-id = { git = "https://github.com/opentensor/frontier", rev = "d553a11551333c2457d9e64d6d912e2210a4ca26", default-features = false } -pallet-evm-precompile-modexp = { git = "https://github.com/opentensor/frontier", rev = "d553a11551333c2457d9e64d6d912e2210a4ca26", default-features = false } -pallet-evm-precompile-sha3fips = { git = "https://github.com/opentensor/frontier", rev = "d553a11551333c2457d9e64d6d912e2210a4ca26", default-features = false } -pallet-evm-precompile-simple = { git = "https://github.com/opentensor/frontier", rev = "d553a11551333c2457d9e64d6d912e2210a4ca26", default-features = false } -pallet-evm-precompile-bn128 = { git = "https://github.com/opentensor/frontier", rev = "d553a11551333c2457d9e64d6d912e2210a4ca26", default-features = false } -pallet-hotfix-sufficients = { git = "https://github.com/opentensor/frontier", rev = "d553a11551333c2457d9e64d6d912e2210a4ca26", default-features = false } +pallet-base-fee = { git = "https://github.com/RaoFoundation/frontier", rev = "d553a11551333c2457d9e64d6d912e2210a4ca26", default-features = false } +pallet-dynamic-fee = { git = "https://github.com/RaoFoundation/frontier", rev = "d553a11551333c2457d9e64d6d912e2210a4ca26", default-features = false } +pallet-ethereum = { git = "https://github.com/RaoFoundation/frontier", rev = "d553a11551333c2457d9e64d6d912e2210a4ca26", default-features = false } +pallet-evm = { git = "https://github.com/RaoFoundation/frontier", rev = "d553a11551333c2457d9e64d6d912e2210a4ca26", default-features = false } +pallet-evm-precompile-dispatch = { git = "https://github.com/RaoFoundation/frontier", rev = "d553a11551333c2457d9e64d6d912e2210a4ca26", default-features = false } +pallet-evm-chain-id = { git = "https://github.com/RaoFoundation/frontier", rev = "d553a11551333c2457d9e64d6d912e2210a4ca26", default-features = false } +pallet-evm-precompile-modexp = { git = "https://github.com/RaoFoundation/frontier", rev = "d553a11551333c2457d9e64d6d912e2210a4ca26", default-features = false } +pallet-evm-precompile-sha3fips = { git = "https://github.com/RaoFoundation/frontier", rev = "d553a11551333c2457d9e64d6d912e2210a4ca26", default-features = false } +pallet-evm-precompile-simple = { git = "https://github.com/RaoFoundation/frontier", rev = "d553a11551333c2457d9e64d6d912e2210a4ca26", default-features = false } +pallet-evm-precompile-bn128 = { git = "https://github.com/RaoFoundation/frontier", rev = "d553a11551333c2457d9e64d6d912e2210a4ca26", default-features = false } +pallet-hotfix-sufficients = { git = "https://github.com/RaoFoundation/frontier", rev = "d553a11551333c2457d9e64d6d912e2210a4ca26", default-features = false } #DRAND pallet-drand = { path = "pallets/drand", default-features = false } sp-crypto-ec-utils = { git = "https://github.com/opentensor/polkadot-sdk.git", rev = "7cc54bf2d50ae3921d718736dfeb0de9468539c7", default-features = false } sp-keystore = { git = "https://github.com/opentensor/polkadot-sdk.git", rev = "7cc54bf2d50ae3921d718736dfeb0de9468539c7", default-features = false } -w3f-bls = { git = "https://github.com/opentensor/bls", branch = "fix-no-std", default-features = false } +w3f-bls = { git = "https://github.com/RaoFoundation/bls", branch = "fix-no-std", default-features = false } ark-crypto-primitives = { version = "0.4.0", default-features = false } ark-scale = { version = "0.0.11", default-features = false } ark-bls12-381 = { version = "0.4.0", default-features = false } @@ -320,6 +320,6 @@ default = [] pow-faucet = [] [patch.crates-io] -w3f-bls = { git = "https://github.com/opentensor/bls", branch = "fix-no-std" } +w3f-bls = { git = "https://github.com/RaoFoundation/bls", branch = "fix-no-std" } zstd-sys = { git = "https://github.com/gztensor/zstd-sys" } zstd-safe = { git = "https://github.com/gztensor/zstd-safe", rev = "42cc34ef6abe5d35d982f6afefb5d7e4e69f5f18" } diff --git a/Dockerfile b/Dockerfile index efa124db33..69ec89a3d2 100644 --- a/Dockerfile +++ b/Dockerfile @@ -12,7 +12,7 @@ FROM ${BASE_IMAGE} AS base_builder LABEL ai.opentensor.image.authors="operations@opentensor.ai" \ ai.opentensor.image.vendor="Opentensor Foundation" \ - ai.opentensor.image.title="opentensor/subtensor" \ + ai.opentensor.image.title="raofoundation/subtensor" \ ai.opentensor.image.description="Opentensor Subtensor Blockchain" \ ai.opentensor.image.documentation="https://docs.bittensor.com" @@ -48,7 +48,7 @@ FROM ${BASE_IMAGE} AS subtensor # ---- security hardening: create least-privilege user ---- RUN addgroup --system --gid 10001 subtensor && \ adduser --system --uid 10001 --gid 10001 --home /home/subtensor --disabled-password subtensor - + # Install gosu for privilege dropping RUN apt-get update && apt-get install -y gosu && \ rm -rf /var/lib/apt/lists/* @@ -71,7 +71,7 @@ RUN chmod +x /entrypoint.sh EXPOSE 30333 9933 9944 -# Run entrypoint as root to handle permissions, then drop to subtensor user +# Run entrypoint as root to handle permissions, then drop to subtensor user # in the script USER root ENTRYPOINT ["/entrypoint.sh"] diff --git a/Dockerfile-localnet b/Dockerfile-localnet index b4be1f9291..f0eae8488d 100644 --- a/Dockerfile-localnet +++ b/Dockerfile-localnet @@ -9,7 +9,7 @@ ARG DEBIAN_FRONTEND=noninteractive LABEL ai.opentensor.image.authors="operations@opentensor.ai" \ ai.opentensor.image.vendor="Opentensor Foundation" \ - ai.opentensor.image.title="opentensor/subtensor-localnet" \ + ai.opentensor.image.title="raofoundation/subtensor-localnet" \ ai.opentensor.image.description="Opentensor Subtensor Blockchain" \ ai.opentensor.image.documentation="https://docs.bittensor.com" @@ -75,5 +75,5 @@ EXPOSE 30334 30335 9944 9945 ENTRYPOINT ["/scripts/localnet.sh"] # Fast blocks defaults to True, you can disable it by passing False to the docker command, e.g.: -# docker run ghcr.io/opentensor/subtensor-localnet False +# docker run ghcr.io/raofoundation/subtensor-localnet False CMD ["True"] diff --git a/README.md b/README.md index b3e902c0bf..b45a673397 100644 --- a/README.md +++ b/README.md @@ -9,7 +9,7 @@ ``` # **Subtensor** -[![CodeQL](https://github.com/opentensor/subtensor/actions/workflows/github-code-scanning/codeql/badge.svg)](https://github.com/opentensor/subtensor/actions) +[![CodeQL](https://github.com/RaoFoundation/subtensor/actions/workflows/github-code-scanning/codeql/badge.svg)](https://github.com/RaoFoundation/subtensor/actions) [![Discord Chat](https://img.shields.io/discord/308323056592486420.svg)](https://discord.gg/bittensor) [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT) diff --git a/chain-extensions/Cargo.toml b/chain-extensions/Cargo.toml index 74209cc106..f9f5737657 100644 --- a/chain-extensions/Cargo.toml +++ b/chain-extensions/Cargo.toml @@ -5,7 +5,7 @@ edition.workspace = true authors = ['Francisco Silva '] homepage = "https://taostats.io/" publish = false -repository = "https://github.com/opentensor/subtensor/" +repository = "https://github.com/RaoFoundation/subtensor/" [package.metadata.docs.rs] targets = ["x86_64-unknown-linux-gnu"] diff --git a/chain-extensions/src/mock.rs b/chain-extensions/src/mock.rs index db25a9df68..93eeaf684c 100644 --- a/chain-extensions/src/mock.rs +++ b/chain-extensions/src/mock.rs @@ -476,7 +476,12 @@ impl PrivilegeCmp for OriginPrivilegeCmp { pub struct CommitmentsI; impl CommitmentsInterface for CommitmentsI { - fn purge_netuid(_netuid: NetUid) {} + fn purge_netuid( + _netuid: NetUid, + _weight_meter: &mut frame_support::weights::WeightMeter, + ) -> bool { + true + } } parameter_types! { diff --git a/chain-extensions/src/tests.rs b/chain-extensions/src/tests.rs index 16619389bf..3136cdc812 100644 --- a/chain-extensions/src/tests.rs +++ b/chain-extensions/src/tests.rs @@ -1297,15 +1297,26 @@ fn add_stake_recycle_rollback_on_recycle_failure() { let netuid = mock::add_dynamic_network(&owner_hotkey, &owner_coldkey); - // Set up very low reserves so recycle will fail with InsufficientLiquidity + mock::register_ok_neuron(netuid, hotkey, coldkey, 0); + pallet_subtensor::Pallet::::insert_lock_state( + &coldkey, + netuid, + &hotkey, + pallet_subtensor::staking::lock::LockState { + locked_mass: AlphaBalance::from(u64::MAX / 4), + conviction: U64F64::saturating_from_num(0), + last_update: pallet_subtensor::Pallet::::get_current_block_as_u64(), + }, + ); + + // Leave enough input-side liquidity for add_stake to pass the 1000x swap input cap. + // The lock above makes the recycle leg fail, exercising atomic rollback. mock::setup_reserves( netuid, - TaoBalance::from(1_000_u64), + TaoBalance::from(tao_amount_raw / 1000 + 1), AlphaBalance::from(1_000_u64), ); - mock::register_ok_neuron(netuid, hotkey, coldkey, 0); - add_balance_to_coldkey_account( &coldkey, TaoBalance::from(tao_amount_raw.saturating_add(1_000_000_000)), @@ -1368,15 +1379,26 @@ fn add_stake_burn_rollback_on_burn_failure() { let netuid = mock::add_dynamic_network(&owner_hotkey, &owner_coldkey); - // Set up very low reserves so burn will fail with InsufficientLiquidity + mock::register_ok_neuron(netuid, hotkey, coldkey, 0); + pallet_subtensor::Pallet::::insert_lock_state( + &coldkey, + netuid, + &hotkey, + pallet_subtensor::staking::lock::LockState { + locked_mass: AlphaBalance::from(u64::MAX / 4), + conviction: U64F64::saturating_from_num(0), + last_update: pallet_subtensor::Pallet::::get_current_block_as_u64(), + }, + ); + + // Leave enough input-side liquidity for add_stake to pass the 1000x swap input cap. + // The lock above makes the burn leg fail, exercising atomic rollback. mock::setup_reserves( netuid, - TaoBalance::from(1_000_u64), + TaoBalance::from(tao_amount_raw / 1000 + 1), AlphaBalance::from(1_000_u64), ); - mock::register_ok_neuron(netuid, hotkey, coldkey, 0); - add_balance_to_coldkey_account( &coldkey, TaoBalance::from(tao_amount_raw.saturating_add(1_000_000_000)), @@ -1705,6 +1727,11 @@ fn get_subnet_registration_state_detects_reused_netuid_generation() { assert_ok!(pallet_subtensor::Pallet::::do_dissolve_network( netuid )); + + pallet_subtensor::Pallet::::remove_data_for_dissolved_networks( + Weight::from_parts(u64::MAX, u64::MAX), + ); + let reused_netuid = mock::add_dynamic_network(&second_hotkey, &second_coldkey); assert_eq!(reused_netuid, netuid); diff --git a/common/Cargo.toml b/common/Cargo.toml index 9fa9bd1856..7767e950f8 100644 --- a/common/Cargo.toml +++ b/common/Cargo.toml @@ -2,10 +2,10 @@ name = "subtensor-runtime-common" version = "0.1.0" edition.workspace = true -authors = ["Opentensor Foundation "] +authors = ["Opentensor Foundation "] homepage = "https://opentensor.ai/" publish = false -repository = "https://github.com/opentensor/subtensor/" +repository = "https://github.com/RaoFoundation/subtensor/" [package.metadata.docs.rs] targets = ["x86_64-unknown-linux-gnu"] @@ -19,11 +19,13 @@ scale-info.workspace = true serde.workspace = true sp-arithmetic.workspace = true sp-core.workspace = true +sp-io.workspace = true sp-runtime.workspace = true sp-rpc = { workspace = true, optional = true } substrate-fixed.workspace = true subtensor-macros.workspace = true runtime-common.workspace = true +impl-trait-for-tuples.workspace = true approx = { workspace = true, optional = true } [lints] @@ -47,6 +49,7 @@ std = [ "serde/std", "sp-arithmetic/std", "sp-core/std", + "sp-io/std", "sp-runtime/std", "sp-rpc", "substrate-fixed/std", diff --git a/common/src/lib.rs b/common/src/lib.rs index ad29f123b2..06dd03a9fd 100644 --- a/common/src/lib.rs +++ b/common/src/lib.rs @@ -12,14 +12,20 @@ use sp_runtime::{ MultiSignature, Vec, traits::{IdentifyAccount, Verify}, }; + +pub use sp_io::MultiRemovalResults; use subtensor_macros::freeze_struct; pub use currency::*; pub use evm_context::*; +pub use proxy::*; pub use transaction_error::*; +use frame_support::weights::WeightMeter; + mod currency; mod evm_context; +mod proxy; mod transaction_error; /// Balance of an account. @@ -132,181 +138,6 @@ impl TypeInfo for NetUid { } } -#[derive( - Copy, - Clone, - Eq, - PartialEq, - Ord, - PartialOrd, - Encode, - Decode, - DecodeWithMemTracking, - Debug, - MaxEncodedLen, - TypeInfo, -)] -pub enum ProxyType { - Any, - Owner, // Subnet owner Calls - NonCritical, - NonTransfer, - Senate, - NonFungible, // Nothing involving moving TAO - Triumvirate, - Governance, // Both above governance - Staking, - Registration, - Transfer, - SmallTransfer, - RootWeights, // deprecated - ChildKeys, - SudoUncheckedSetCode, - SwapHotkey, - SubnetLeaseBeneficiary, // Used to operate the leased subnet - RootClaim, -} - -impl TryFrom for ProxyType { - type Error = (); - - fn try_from(value: u8) -> Result { - match value { - 0 => Ok(Self::Any), - 1 => Ok(Self::Owner), - 2 => Ok(Self::NonCritical), - 3 => Ok(Self::NonTransfer), - 4 => Ok(Self::Senate), - 5 => Ok(Self::NonFungible), - 6 => Ok(Self::Triumvirate), - 7 => Ok(Self::Governance), - 8 => Ok(Self::Staking), - 9 => Ok(Self::Registration), - 10 => Ok(Self::Transfer), - 11 => Ok(Self::SmallTransfer), - 12 => Ok(Self::RootWeights), - 13 => Ok(Self::ChildKeys), - 14 => Ok(Self::SudoUncheckedSetCode), - 15 => Ok(Self::SwapHotkey), - 16 => Ok(Self::SubnetLeaseBeneficiary), - 17 => Ok(Self::RootClaim), - _ => Err(()), - } - } -} - -impl From for u8 { - fn from(proxy_type: ProxyType) -> Self { - match proxy_type { - ProxyType::Any => 0, - ProxyType::Owner => 1, - ProxyType::NonCritical => 2, - ProxyType::NonTransfer => 3, - ProxyType::Senate => 4, - ProxyType::NonFungible => 5, - ProxyType::Triumvirate => 6, - ProxyType::Governance => 7, - ProxyType::Staking => 8, - ProxyType::Registration => 9, - ProxyType::Transfer => 10, - ProxyType::SmallTransfer => 11, - ProxyType::RootWeights => 12, - ProxyType::ChildKeys => 13, - ProxyType::SudoUncheckedSetCode => 14, - ProxyType::SwapHotkey => 15, - ProxyType::SubnetLeaseBeneficiary => 16, - ProxyType::RootClaim => 17, - } - } -} - -impl ProxyType { - pub fn is_deprecated(&self) -> bool { - matches!( - self, - Self::Triumvirate | Self::Senate | Self::Governance | Self::RootWeights - ) - } -} - -impl Default for ProxyType { - // allow all Calls; required to be most permissive - fn default() -> Self { - Self::Any - } -} - -/// Conditions that must be met beyond matching the call itself. -#[derive(Clone, PartialEq, Eq, Encode, Decode, Debug, TypeInfo)] -pub enum CallCondition { - /// A numeric parameter must be less than this limit - ParamLessThan { param_name: Vec, limit: u128 }, - /// The nested call inside must match this pallet/call - NestedCallMustBe { - pallet_name: Vec, - call_name: Vec, - }, -} - -/// Describes which call(s) a proxy filter rule applies to. -/// -/// When `call_name` and `call_index` are `None`, the rule applies to ALL calls in the pallet. -/// When they are `Some`, the rule applies to that specific call only. -#[freeze_struct("57f984617f6084cc")] -#[derive(Clone, PartialEq, Eq, Encode, Decode, Debug, TypeInfo)] -pub struct CallInfo { - /// Pallet name (always present) - pub pallet_name: Vec, - /// Pallet index in runtime (always present) - pub pallet_index: u8, - /// Call name within pallet. None means ALL calls in this pallet. - pub call_name: Option>, - /// Call index within pallet. None means ALL calls in this pallet. - pub call_index: Option, - /// Additional condition that must be met (value limits, nested call requirements) - pub condition: Option, -} - -/// Describes how a ProxyType filters incoming calls. -#[derive(Clone, PartialEq, Eq, Encode, Decode, Debug, TypeInfo)] -pub enum FilterMode { - /// All calls are permitted regardless of the calls list (e.g. ProxyType::Any) - AllowAll, - /// No calls are permitted (e.g. deprecated proxy types) - DenyAll, - /// Only calls listed in the `calls` field are permitted - Allow, - /// All calls are permitted EXCEPT those listed in the `calls` field - Deny, -} - -/// Complete filter description for a ProxyType, returned by the Runtime API. -/// -/// Interpretation: -/// - `filter_mode: AllowAll` — everything permitted, `calls` is empty -/// - `filter_mode: DenyAll` — nothing permitted, `calls` is empty -/// - `filter_mode: Allow` — only `calls` are permitted (minus `exceptions`) -/// - `filter_mode: Deny` — everything EXCEPT `calls` is permitted -/// - `call_name: None` in a CallInfo — rule applies to ALL calls in the pallet -#[freeze_struct("4453d44869f8a188")] -#[derive(Clone, PartialEq, Eq, Encode, Decode, Debug, TypeInfo)] -pub struct ProxyFilterInfo { - pub proxy_type: u8, - pub name: Vec, - pub deprecated: bool, - pub filter_mode: FilterMode, - pub calls: Vec, - pub exceptions: Vec, -} - -#[freeze_struct("b0cce66ed9b2451b")] -#[derive(Clone, PartialEq, Eq, Encode, Decode, Debug, TypeInfo)] -pub struct ProxyTypeInfo { - pub name: Vec, - pub index: u8, - pub deprecated: bool, -} - pub trait SubnetInfo { fn exists(netuid: NetUid) -> bool; fn mechanism(netuid: NetUid) -> u16; @@ -523,12 +354,133 @@ impl TypeInfo for NetUidStorageIndex { } } +/// Clears as many entries as the weight budget allows via `clear_prefix`, charging +/// `per_item` for each removed entry. Returns `true` once the prefix is fully cleared. +pub fn clear_prefix_with_meter( + meter: &mut WeightMeter, + per_item: Weight, + clear_prefix: impl FnOnce(u32) -> MultiRemovalResults, +) -> bool { + let Some(limit) = meter.remaining().checked_div_per_component(&per_item) else { + return false; + }; + // Saturate: a budget allowing more than u32::MAX removals is capped, not rejected. + let limit = u32::try_from(limit).unwrap_or(u32::MAX); + + if limit == 0 { + return false; + } + + let result = clear_prefix(limit); + meter.consume(per_item.saturating_mul(result.unique.max(result.loops).into())); + + result.maybe_cursor.is_none() +} + #[cfg(test)] mod tests { use super::*; + use frame_support::{ + Blake2_128Concat, storage::types::StorageDoubleMap, traits::StorageInstance, + weights::WeightMeter, + }; + const REF_TIME_WEIGHT: u64 = 100; + const PROOF_SIZE_WEIGHT: u64 = 100; + + struct ClearPrefixTestStorage; + + impl StorageInstance for ClearPrefixTestStorage { + fn pallet_prefix() -> &'static str { + "CommonTests" + } + + const STORAGE_PREFIX: &'static str = "ClearPrefixTestStorage"; + } + + type ClearPrefixTestMap = + StorageDoubleMap; #[test] fn netuid_has_u16_bin_repr() { assert_eq!(NetUid(5).encode(), 5u16.encode()); } + + #[test] + fn test_clear_prefix_with_meter_respects_budget() { + let netuid = NetUid::from(42); + let entry_weight = Weight::from_parts(REF_TIME_WEIGHT, PROOF_SIZE_WEIGHT); + let mut ext = sp_io::TestExternalities::default(); + + ext.execute_with(|| { + for key in 0..3 { + ClearPrefixTestMap::insert(netuid, key, key as u32); + } + }); + + let _ = ext.commit_all(); + + ext.execute_with(|| { + assert_eq!(ClearPrefixTestMap::iter_prefix(netuid).count(), 3); + + // Budget for exactly one entry: one entry is removed, not done yet. + let mut weight_meter = WeightMeter::with_limit(entry_weight); + assert!(!clear_prefix_with_meter( + &mut weight_meter, + entry_weight, + |limit| ClearPrefixTestMap::clear_prefix(netuid, limit, None), + )); + + assert_eq!(ClearPrefixTestMap::iter_prefix(netuid).count(), 2); + assert_eq!(weight_meter.consumed(), entry_weight); + }); + } + + #[test] + fn test_clear_prefix_with_meter_zero_budget_is_noop() { + let netuid = NetUid::from(43); + let entry_weight = Weight::from_parts(REF_TIME_WEIGHT, PROOF_SIZE_WEIGHT); + let mut ext = sp_io::TestExternalities::default(); + + ext.execute_with(|| { + ClearPrefixTestMap::insert(netuid, 0, 0u32); + + let mut weight_meter = WeightMeter::with_limit(Weight::zero()); + assert!(!clear_prefix_with_meter( + &mut weight_meter, + entry_weight, + |limit| ClearPrefixTestMap::clear_prefix(netuid, limit, None), + )); + + assert_eq!(ClearPrefixTestMap::iter_prefix(netuid).count(), 1); + assert!(weight_meter.consumed().is_zero()); + }); + } + + #[test] + fn test_clear_prefix_with_meter_completes_with_enough_budget() { + let netuid = NetUid::from(44); + let entry_weight = Weight::from_parts(REF_TIME_WEIGHT, PROOF_SIZE_WEIGHT); + let mut ext = sp_io::TestExternalities::default(); + + ext.execute_with(|| { + for key in 0..3 { + ClearPrefixTestMap::insert(netuid, key, key as u32); + } + }); + + let _ = ext.commit_all(); + + ext.execute_with(|| { + // Budget for more entries than exist: everything is cleared in one call. + let mut weight_meter = WeightMeter::with_limit(entry_weight.saturating_mul(10)); + assert!(clear_prefix_with_meter( + &mut weight_meter, + entry_weight, + |limit| ClearPrefixTestMap::clear_prefix(netuid, limit, None), + )); + + assert_eq!(ClearPrefixTestMap::iter_prefix(netuid).count(), 0); + assert_eq!(weight_meter.consumed(), entry_weight.saturating_mul(3)); + }); + } } diff --git a/common/src/proxy.rs b/common/src/proxy.rs new file mode 100644 index 0000000000..f40b3f2076 --- /dev/null +++ b/common/src/proxy.rs @@ -0,0 +1,219 @@ +use codec::{Decode, DecodeWithMemTracking, Encode, MaxEncodedLen}; +use frame_support::traits::{Contains, GetCallIndex, GetCallName, PalletInfoAccess}; +use scale_info::TypeInfo; +use sp_runtime::Vec; +use subtensor_macros::freeze_struct; + +/// Shared proxy filter model exposed by the runtime API. +/// +/// Runtime filtering remains the source of truth. This metadata is the client-facing +/// allowlist view of the same rules. +/// Stable proxy type identifiers used on-chain and by RPC clients. +#[derive( + Copy, + Clone, + Eq, + PartialEq, + Ord, + PartialOrd, + Encode, + Decode, + DecodeWithMemTracking, + Debug, + MaxEncodedLen, + TypeInfo, +)] +pub enum ProxyType { + Any, + Owner, + NonCritical, + NonTransfer, + Senate, + NonFungible, // Nothing involving moving TAO + Triumvirate, + Governance, + Staking, + Registration, + Transfer, + SmallTransfer, + RootWeights, // Deprecated + ChildKeys, + SudoUncheckedSetCode, + SwapHotkey, + SubnetLeaseBeneficiary, + RootClaim, +} + +impl TryFrom for ProxyType { + type Error = (); + + fn try_from(value: u8) -> Result { + match value { + 0 => Ok(Self::Any), + 1 => Ok(Self::Owner), + 2 => Ok(Self::NonCritical), + 3 => Ok(Self::NonTransfer), + 4 => Ok(Self::Senate), + 5 => Ok(Self::NonFungible), + 6 => Ok(Self::Triumvirate), + 7 => Ok(Self::Governance), + 8 => Ok(Self::Staking), + 9 => Ok(Self::Registration), + 10 => Ok(Self::Transfer), + 11 => Ok(Self::SmallTransfer), + 12 => Ok(Self::RootWeights), + 13 => Ok(Self::ChildKeys), + 14 => Ok(Self::SudoUncheckedSetCode), + 15 => Ok(Self::SwapHotkey), + 16 => Ok(Self::SubnetLeaseBeneficiary), + 17 => Ok(Self::RootClaim), + _ => Err(()), + } + } +} + +impl From for u8 { + fn from(proxy_type: ProxyType) -> Self { + match proxy_type { + ProxyType::Any => 0, + ProxyType::Owner => 1, + ProxyType::NonCritical => 2, + ProxyType::NonTransfer => 3, + ProxyType::Senate => 4, + ProxyType::NonFungible => 5, + ProxyType::Triumvirate => 6, + ProxyType::Governance => 7, + ProxyType::Staking => 8, + ProxyType::Registration => 9, + ProxyType::Transfer => 10, + ProxyType::SmallTransfer => 11, + ProxyType::RootWeights => 12, + ProxyType::ChildKeys => 13, + ProxyType::SudoUncheckedSetCode => 14, + ProxyType::SwapHotkey => 15, + ProxyType::SubnetLeaseBeneficiary => 16, + ProxyType::RootClaim => 17, + } + } +} + +impl ProxyType { + pub fn is_deprecated(&self) -> bool { + matches!( + self, + Self::Triumvirate | Self::Senate | Self::Governance | Self::RootWeights + ) + } +} + +impl Default for ProxyType { + fn default() -> Self { + Self::Any + } +} + +/// Extra constraint attached to an allowed call. +#[derive(Clone, PartialEq, Eq, Encode, Decode, Debug, TypeInfo)] +pub enum CallConstraint { + /// The named numeric parameter must be lower than `limit`. + ParamLessThan { param_name: Vec, limit: u128 }, + /// The named boxed call parameter must target this pallet/call pair. + NestedCallMustBe { + param_name: Vec, + pallet_name: Vec, + call_name: Vec, + }, +} + +/// Runtime call identity exposed in proxy filter metadata. +#[freeze_struct("85f86877d3d9b870")] +#[derive(Clone, PartialEq, Eq, Encode, Decode, Debug, TypeInfo)] +pub struct CallInfo { + /// Runtime pallet name. + pub pallet_name: Vec, + /// Runtime pallet index. + pub pallet_index: u8, + /// Pallet call name. + pub call_name: Vec, + /// Pallet call index. + pub call_index: u8, + /// Optional value or nested-call constraint. + pub constraint: Option, +} + +pub fn call_info_by_name( + name: &str, +) -> CallInfo { + let names = C::get_call_names(); + let indices = C::get_call_indices(); + let pos = names + .iter() + .position(|n| *n == name) + .unwrap_or_else(|| panic!("Call '{}' not found in pallet '{}'", name, P::name())); + + CallInfo { + pallet_name: P::name().as_bytes().to_vec(), + pallet_index: P::index() as u8, + call_name: name.as_bytes().to_vec(), + call_index: indices + .get(pos) + .copied() + .unwrap_or_else(|| panic!("Call '{}' index out of bounds in '{}'", name, P::name())), + constraint: None, + } +} + +/// Metadata view for a call filter group. +/// +/// Implementations should be generated from the same rules as the executable +/// filter so clients and runtime behavior cannot drift. +pub trait CallFilterMetadata { + fn call_infos() -> Vec; +} + +/// A reusable filter group: executable predicate plus metadata for clients. +pub trait CallFilterGroup: Contains + CallFilterMetadata {} + +impl CallFilterGroup for T where T: Contains + CallFilterMetadata {} + +impl CallFilterMetadata for () { + fn call_infos() -> Vec { + Vec::new() + } +} + +#[impl_trait_for_tuples::impl_for_tuples(1, 32)] +impl CallFilterMetadata for Tuple { + fn call_infos() -> Vec { + let mut infos = Vec::new(); + for_tuples!( #( infos.extend(Tuple::call_infos()); )* ); + infos + } +} + +/// Public metadata model for a proxy filter. +#[derive(Clone, PartialEq, Eq, Encode, Decode, Debug, TypeInfo)] +pub enum FilterMode { + /// All runtime calls are allowed. + AllowAll, + /// Only listed calls are allowed. An empty list means deny all. + Allow(Vec), +} + +/// Runtime API response for one proxy type. +#[freeze_struct("288413f4da5ab4ee")] +#[derive(Clone, PartialEq, Eq, Encode, Decode, Debug, TypeInfo)] +pub struct ProxyFilterInfo { + pub proxy_type: u8, + pub name: Vec, + pub deprecated: bool, + pub filter_mode: FilterMode, +} + +#[freeze_struct("b0cce66ed9b2451b")] +#[derive(Clone, PartialEq, Eq, Encode, Decode, Debug, TypeInfo)] +pub struct ProxyTypeInfo { + pub name: Vec, + pub index: u8, + pub deprecated: bool, +} diff --git a/docker-compose.localnet.yml b/docker-compose.localnet.yml index f9c1f4efd9..bbfab1f4aa 100644 --- a/docker-compose.localnet.yml +++ b/docker-compose.localnet.yml @@ -4,7 +4,7 @@ volumes: services: common: &common - image: ghcr.io/opentensor/subtensor:latest-local + image: ghcr.io/raofoundation/subtensor:latest-local cpu_count: 4 mem_limit: 40000000000 memswap_limit: 80000000000 diff --git a/docker-compose.yml b/docker-compose.yml index cb3f626ec1..bf005e054a 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -8,7 +8,7 @@ volumes: services: common: &common - image: ghcr.io/opentensor/subtensor:latest + image: ghcr.io/raofoundation/subtensor:latest ## Uncomment to build from scratch: # build: # context: . diff --git a/docs/consensus.md b/docs/consensus.md index 6678b4f52f..bb411d65d6 100644 --- a/docs/consensus.md +++ b/docs/consensus.md @@ -306,7 +306,7 @@ ssh -L 8888:localhost:8888 root@ -p -i ~/.s 3. **Clone the Subtensor repository and checkout the relevant branch:** ```bash - git clone https://github.com/opentensor/subtensor.git + git clone https://github.com/RaoFoundation/subtensor.git cd subtensor git checkout main diff --git a/eco-tests/Cargo.toml b/eco-tests/Cargo.toml index 673b7d2f29..e6c884c9ac 100644 --- a/eco-tests/Cargo.toml +++ b/eco-tests/Cargo.toml @@ -58,4 +58,4 @@ rand = { version = "0.10.0", default-features = false, features = ["std", "threa hex-literal = "0.4.1" [patch.crates-io] -w3f-bls = { git = "https://github.com/opentensor/bls", branch = "fix-no-std" } +w3f-bls = { git = "https://github.com/RaoFoundation/bls", branch = "fix-no-std" } diff --git a/eco-tests/src/mock.rs b/eco-tests/src/mock.rs index 5ce8bc9b6b..d1afc76c55 100644 --- a/eco-tests/src/mock.rs +++ b/eco-tests/src/mock.rs @@ -369,7 +369,12 @@ impl PrivilegeCmp for OriginPrivilegeCmp { pub struct CommitmentsI; impl CommitmentsInterface for CommitmentsI { - fn purge_netuid(_netuid: NetUid) {} + fn purge_netuid( + _netuid: NetUid, + _weight_meter: &mut frame_support::weights::WeightMeter, + ) -> bool { + true + } } parameter_types! { diff --git a/node/Cargo.toml b/node/Cargo.toml index d067eb19c8..142dc29b64 100644 --- a/node/Cargo.toml +++ b/node/Cargo.toml @@ -7,7 +7,7 @@ homepage = "https://substrate.io/" edition.workspace = true license = "Unlicense" publish = false -repository = "https://github.com/opentensor/subtensor" +repository = "https://github.com/RaoFoundation/subtensor" build = "build.rs" [lints] diff --git a/node/src/chain_spec/finney.rs b/node/src/chain_spec/finney.rs index 4b47c29473..4455466d26 100644 --- a/node/src/chain_spec/finney.rs +++ b/node/src/chain_spec/finney.rs @@ -185,7 +185,7 @@ pub fn finney_mainnet_config() -> Result { .build(); // Load and set the code substitute to avoid archive node sync panic - // See + // See // // Need to do it in this hacky way because the ChainSpec builder doesn't support setting it let code_substitute_2585476_hex = include_bytes!("code_substitute_2585476.txt"); diff --git a/node/src/consensus/hybrid_import_queue.rs b/node/src/consensus/hybrid_import_queue.rs index 342d67dbc1..1edb46b974 100644 --- a/node/src/consensus/hybrid_import_queue.rs +++ b/node/src/consensus/hybrid_import_queue.rs @@ -116,7 +116,7 @@ impl BlockImport for HybridBlockImport { ) -> Result { // The Babe and Aura `BlockImport` implementations both defer to the inner // client's `check_block` implementation defined here: - // https://github.com/opentensor/polkadot-sdk/blob/d13f915d8a1f55af53fd51fdb4544c47badddc7e/substrate/client/service/src/client/client.rs#L1748. + // https://github.com/RaoFoundation/polkadot-sdk/blob/d13f915d8a1f55af53fd51fdb4544c47badddc7e/substrate/client/service/src/client/client.rs#L1748. self.client.check_block(block).await.map_err(Into::into) } diff --git a/pallets/admin-utils/Cargo.toml b/pallets/admin-utils/Cargo.toml index ae374b4938..50091a316c 100644 --- a/pallets/admin-utils/Cargo.toml +++ b/pallets/admin-utils/Cargo.toml @@ -7,7 +7,7 @@ homepage = "https://bittensor.com" edition.workspace = true license = "Unlicense" publish = false -repository = "https://github.com/opentensor/subtensor" +repository = "https://github.com/RaoFoundation/subtensor" [lints] workspace = true diff --git a/pallets/admin-utils/src/lib.rs b/pallets/admin-utils/src/lib.rs index 0e63a1f05a..23df61e05c 100644 --- a/pallets/admin-utils/src/lib.rs +++ b/pallets/admin-utils/src/lib.rs @@ -1532,28 +1532,17 @@ pub mod pallet { Ok(()) } - /// Change the SubnetOwnerHotkey for a given subnet. - /// - /// # Arguments - /// * `origin` - The origin of the call, which must be the subnet owner. - /// * `netuid` - The unique identifier for the subnet. - /// * `hotkey` - The new hotkey for the subnet owner. - /// - /// # Errors - /// * `BadOrigin` - If the caller is not the subnet owner or root account. - /// - /// # Weight - /// Weight is handled by the `#[pallet::weight]` attribute. - #[pallet::call_index(64)] - #[pallet::weight(Weight::from_parts(3_918_000, 0) // TODO: add benchmarks - .saturating_add(T::DbWeight::get().writes(1_u64)))] - pub fn sudo_set_subnet_owner_hotkey( - origin: OriginFor, - netuid: NetUid, - hotkey: ::AccountId, - ) -> DispatchResult { - pallet_subtensor::Pallet::::do_set_sn_owner_hotkey(origin, netuid, &hotkey) - } + // Deprecated for sudo_set_sn_owner_hotkey + // #[pallet::call_index(64)] + // #[pallet::weight(Weight::from_parts(3_918_000, 0) // TODO: add benchmarks + // .saturating_add(T::DbWeight::get().writes(1_u64)))] + // pub fn sudo_set_subnet_owner_hotkey( + // origin: OriginFor, + // netuid: NetUid, + // hotkey: ::AccountId, + // ) -> DispatchResult { + // pallet_subtensor::Pallet::::do_set_sn_owner_hotkey(origin, netuid, &hotkey) + // } /// /// diff --git a/pallets/admin-utils/src/tests/mock.rs b/pallets/admin-utils/src/tests/mock.rs index e3b658a0a8..06e4e611a1 100644 --- a/pallets/admin-utils/src/tests/mock.rs +++ b/pallets/admin-utils/src/tests/mock.rs @@ -381,7 +381,12 @@ impl PrivilegeCmp for OriginPrivilegeCmp { pub struct CommitmentsI; impl pallet_subtensor::CommitmentsInterface for CommitmentsI { - fn purge_netuid(_netuid: NetUid) {} + fn purge_netuid( + _netuid: NetUid, + _weight_meter: &mut frame_support::weights::WeightMeter, + ) -> bool { + true + } } pub struct GrandpaInterfaceImpl; diff --git a/pallets/admin-utils/src/tests/mod.rs b/pallets/admin-utils/src/tests/mod.rs index 735e784ad8..89494e154f 100644 --- a/pallets/admin-utils/src/tests/mod.rs +++ b/pallets/admin-utils/src/tests/mod.rs @@ -1196,6 +1196,7 @@ fn test_sudo_set_liquid_alpha_enabled() { new_test_ext().execute_with(|| { let netuid = NetUid::from(1); let enabled: bool = true; + NetworksAdded::::insert(netuid, true); assert_eq!(!enabled, SubtensorModule::get_liquid_alpha_enabled(netuid)); assert_ok!(AdminUtils::sudo_set_liquid_alpha_enabled( @@ -2718,27 +2719,17 @@ fn test_trim_to_max_allowed_uids() { let now = frame_system::Pallet::::block_number(); BlockAtRegistration::::set(netuid, 6, now); - // Set some evm addresses (include both kept + trimmed uids) - AssociatedEvmAddress::::insert( - netuid, - 6, - (sp_core::H160::from_slice(b"12345678901234567891"), now), - ); - AssociatedEvmAddress::::insert( - netuid, - 10, - (sp_core::H160::from_slice(b"12345678901234567892"), now), - ); - AssociatedEvmAddress::::insert( - netuid, - 12, - (sp_core::H160::from_slice(b"12345678901234567893"), now), - ); - AssociatedEvmAddress::::insert( - netuid, - 14, - (sp_core::H160::from_slice(b"12345678901234567894"), now), - ); + // Set some evm addresses (include both kept + trimmed uids). Go through the normal + // setter so both the forward map and the reverse index are populated, exactly as the + // association extrinsic does in production. + let evm_addr_uid6 = sp_core::H160::from_slice(b"12345678901234567891"); + let evm_addr_uid10 = sp_core::H160::from_slice(b"12345678901234567892"); + let evm_addr_uid12 = sp_core::H160::from_slice(b"12345678901234567893"); + let evm_addr_uid14 = sp_core::H160::from_slice(b"12345678901234567894"); + SubtensorModule::set_associated_evm_address(netuid, 6, evm_addr_uid6, now); + SubtensorModule::set_associated_evm_address(netuid, 10, evm_addr_uid10, now); + SubtensorModule::set_associated_evm_address(netuid, 12, evm_addr_uid12, now); + SubtensorModule::set_associated_evm_address(netuid, 14, evm_addr_uid14, now); // Populate Weights and Bonds storage items to test trimming for uid in 0..max_n { @@ -2879,11 +2870,30 @@ fn test_trim_to_max_allowed_uids() { // EVM association have been remapped correctly (uids: 6 -> 2, 14 -> 7) assert_eq!( AssociatedEvmAddress::::get(netuid, 2), - Some((sp_core::H160::from_slice(b"12345678901234567891"), now)) + Some((evm_addr_uid6, now)) ); assert_eq!( AssociatedEvmAddress::::get(netuid, 7), - Some((sp_core::H160::from_slice(b"12345678901234567894"), now)) + Some((evm_addr_uid14, now)) + ); + + // The reverse index has been remapped in place to the new UIDs (6 -> 2, 14 -> 7), + // without rebuilding it from scratch. + assert_eq!( + AssociatedUidsByEvmAddress::::get(netuid, evm_addr_uid6).into_inner(), + vec![(2u16, now)] + ); + assert_eq!( + AssociatedUidsByEvmAddress::::get(netuid, evm_addr_uid14).into_inner(), + vec![(7u16, now)] + ); + // Trimmed UIDs (10, 12) were dropped from the reverse index entirely. + assert!(AssociatedUidsByEvmAddress::::get(netuid, evm_addr_uid10).is_empty()); + assert!(AssociatedUidsByEvmAddress::::get(netuid, evm_addr_uid12).is_empty()); + // uid_lookup resolves the remapped UID. + assert_eq!( + SubtensorModule::uid_lookup(netuid, evm_addr_uid6, u16::MAX), + vec![(2u16, now)] ); // Non existent subnet diff --git a/pallets/commitments/Cargo.toml b/pallets/commitments/Cargo.toml index 47bbaec7cd..1c81807fc4 100644 --- a/pallets/commitments/Cargo.toml +++ b/pallets/commitments/Cargo.toml @@ -7,7 +7,7 @@ homepage = "https://bittensor.com" edition.workspace = true license = "Unlicense" publish = false -repository = "https://github.com/opentensor/subtensor" +repository = "https://github.com/RaoFoundation/subtensor" [lints] workspace = true diff --git a/pallets/commitments/src/lib.rs b/pallets/commitments/src/lib.rs index b5f28a0968..bcbf908bc8 100644 --- a/pallets/commitments/src/lib.rs +++ b/pallets/commitments/src/lib.rs @@ -13,6 +13,7 @@ pub mod weights; use ark_serialize::CanonicalDeserialize; use codec::Encode; use frame_support::IterableStorageDoubleMap; +use frame_support::weights::WeightMeter; use frame_support::{ BoundedVec, traits::{Currency, Get}, @@ -23,7 +24,7 @@ use scale_info::prelude::collections::BTreeSet; use sp_runtime::SaturatedConversion; use sp_runtime::{Saturating, Weight, traits::Zero}; use sp_std::{boxed::Box, vec::Vec}; -use subtensor_runtime_common::NetUid; +use subtensor_runtime_common::{NetUid, clear_prefix_with_meter}; use tle::{ curves::drand::TinyBLS381, stream_ciphers::AESGCMStreamCipherProvider, @@ -584,16 +585,34 @@ impl Pallet { commitments } - pub fn purge_netuid(netuid: NetUid) { - let _ = CommitmentOf::::clear_prefix(netuid, u32::MAX, None); - let _ = LastCommitment::::clear_prefix(netuid, u32::MAX, None); - let _ = LastBondsReset::::clear_prefix(netuid, u32::MAX, None); - let _ = RevealedCommitments::::clear_prefix(netuid, u32::MAX, None); - let _ = UsedSpaceOf::::clear_prefix(netuid, u32::MAX, None); - - TimelockedIndex::::mutate(|index| { - index.retain(|(n, _)| *n != netuid); + pub fn purge_netuid(netuid: NetUid, weight_meter: &mut WeightMeter) -> bool { + let write_weight = T::DbWeight::get().writes(1); + + let result = clear_prefix_with_meter(weight_meter, write_weight, |limit| { + CommitmentOf::::clear_prefix(netuid, limit, None) + }) && clear_prefix_with_meter(weight_meter, write_weight, |limit| { + LastCommitment::::clear_prefix(netuid, limit, None) + }) && clear_prefix_with_meter(weight_meter, write_weight, |limit| { + LastBondsReset::::clear_prefix(netuid, limit, None) + }) && clear_prefix_with_meter(weight_meter, write_weight, |limit| { + RevealedCommitments::::clear_prefix(netuid, limit, None) + }) && clear_prefix_with_meter(weight_meter, write_weight, |limit| { + UsedSpaceOf::::clear_prefix(netuid, limit, None) }); + + if !result { + return false; + } + + if weight_meter.can_consume(write_weight) { + TimelockedIndex::::mutate(|index| { + index.retain(|(n, _)| *n != netuid); + }); + weight_meter.consume(write_weight); + true + } else { + false + } } } diff --git a/pallets/commitments/src/mock.rs b/pallets/commitments/src/mock.rs index 10bbf7bbb1..7b61b5625f 100644 --- a/pallets/commitments/src/mock.rs +++ b/pallets/commitments/src/mock.rs @@ -4,6 +4,7 @@ use frame_support::{ derive_impl, pallet_prelude::{Get, TypeInfo}, traits::{ConstU32, ConstU64}, + weights::constants::RocksDbWeight, }; use sp_core::H256; use sp_runtime::{ @@ -34,7 +35,7 @@ impl frame_system::Config for Test { type BaseCallFilter = frame_support::traits::Everything; type BlockWeights = (); type BlockLength = (); - type DbWeight = (); + type DbWeight = RocksDbWeight; type RuntimeOrigin = RuntimeOrigin; type RuntimeCall = RuntimeCall; type Hash = H256; diff --git a/pallets/commitments/src/tests.rs b/pallets/commitments/src/tests.rs index 8050b455fa..fa067506d2 100644 --- a/pallets/commitments/src/tests.rs +++ b/pallets/commitments/src/tests.rs @@ -18,9 +18,15 @@ use frame_support::pallet_prelude::Hooks; use frame_support::{ BoundedVec, assert_noop, assert_ok, traits::{Currency, Get, ReservableCurrency}, + weights::Weight, }; use frame_system::{Pallet as System, RawOrigin}; +fn purge_netuid_with_meter(netuid: NetUid, limit: Weight) -> bool { + let mut weight_meter = frame_support::weights::WeightMeter::with_limit(limit); + Pallet::::purge_netuid(netuid, &mut weight_meter) +} + #[test] fn manual_data_type_info() { let mut registry = scale_info::Registry::new(); @@ -2265,7 +2271,7 @@ fn purge_netuid_clears_only_that_netuid() { assert!(TimelockedIndex::::get().contains(&(net_a, who_a1))); // Act - Pallet::::purge_netuid(net_a); + purge_netuid_with_meter(net_a, Weight::from_parts(u64::MAX, u64::MAX)); // NET A: everything cleared assert_eq!(CommitmentOf::::iter_prefix(net_a).count(), 0); @@ -2298,8 +2304,68 @@ fn purge_netuid_clears_only_that_netuid() { assert!(idx_after.contains(&(net_b, who_b))); // Idempotency - Pallet::::purge_netuid(net_a); + purge_netuid_with_meter(net_a, Weight::from_parts(u64::MAX, u64::MAX)); assert_eq!(CommitmentOf::::iter_prefix(net_a).count(), 0); assert!(!TimelockedIndex::::get().contains(&(net_a, who_a1))); }); } + +/// `purge_netuid` runs weighted prefix clears **before** the timelock-index update. The macro batch +/// sizing uses the meter's **limit** (not accumulated consumption), so maps may already be empty +/// when the weight budget runs out; `done == false` must still mean the timelock index +/// row for this netuid survives until a later call with enough budget. +#[test] +fn purge_netuid_under_budget_may_skip_timelock_update_while_clearing_maps() { + new_test_ext().execute_with(|| { + System::::set_block_number(1); + let net_a = NetUid::from(77); + let who_a: u64 = 4001; + + let empty_fields: BoundedVec::MaxFields> = BoundedVec::default(); + let info_empty: CommitmentInfo<::MaxFields> = CommitmentInfo { + fields: empty_fields, + }; + let bn = System::::block_number(); + let reg = Registration { + deposit: Default::default(), + block: bn, + info: info_empty, + }; + CommitmentOf::::insert(net_a, who_a, reg); + LastCommitment::::insert(net_a, who_a, bn); + LastBondsReset::::insert(net_a, who_a, bn); + RevealedCommitments::::insert(net_a, who_a, vec![(b"x".to_vec(), 1u64)]); + UsedSpaceOf::::insert( + net_a, + who_a, + UsageTracker { + last_epoch: 1, + used_space: 1, + }, + ); + TimelockedIndex::::mutate(|idx| { + idx.insert((net_a, who_a)); + }); + + let write1 = ::DbWeight::get().writes(1); + // Budget is strictly below one DB write, so the weighted prefix clears inside + // `purge_netuid` reliably run out of budget and report `done == false`. + let budget = write1.saturating_sub(Weight::from_parts(1, 1)); + + let done = purge_netuid_with_meter(net_a, budget); + assert!( + !done, + "purge_netuid must report not-done when under-budget" + ); + assert!( + TimelockedIndex::::get().contains(&(net_a, who_a)), + "timelock index is only trimmed after a successful final pass; stale index entries are expected if that write is skipped" + ); + + // Full budget finishes (including timelock index), even if prior pass already cleared maps. + let done = purge_netuid_with_meter(net_a, Weight::from_parts(u64::MAX, u64::MAX)); + assert!(done); + assert!(CommitmentOf::::get(net_a, who_a).is_none()); + assert!(!TimelockedIndex::::get().contains(&(net_a, who_a))); + }); +} diff --git a/pallets/crowdloan/Cargo.toml b/pallets/crowdloan/Cargo.toml index 1bba723fe2..0b3e1aa8ce 100644 --- a/pallets/crowdloan/Cargo.toml +++ b/pallets/crowdloan/Cargo.toml @@ -7,7 +7,7 @@ license = "Apache-2.0" homepage = "https://bittensor.com" description = "FRAME crowdloan pallet" publish = false -repository = "https://github.com/opentensor/subtensor" +repository = "https://github.com/RaoFoundation/subtensor" [lints] workspace = true diff --git a/pallets/crowdloan/src/lib.rs b/pallets/crowdloan/src/lib.rs index 8be80c0cc7..f7d771f4a7 100644 --- a/pallets/crowdloan/src/lib.rs +++ b/pallets/crowdloan/src/lib.rs @@ -265,6 +265,8 @@ pub mod pallet { InvalidOrigin, /// The crowdloan has already been finalized. AlreadyFinalized, + /// A crowdloan finalization is already in progress. + AlreadyFinalizing, /// The crowdloan contribution period has not ended yet. ContributionPeriodNotEnded, /// The contributor has no contribution for this crowdloan. @@ -619,6 +621,13 @@ pub mod pallet { ensure!(who == crowdloan.creator, Error::::InvalidOrigin); ensure!(crowdloan.raised == crowdloan.cap, Error::::CapNotRaised); ensure!(!crowdloan.finalized, Error::::AlreadyFinalized); + ensure!( + CurrentCrowdloanId::::get().is_none(), + Error::::AlreadyFinalizing + ); + + crowdloan.finalized = true; + Crowdloans::::insert(crowdloan_id, &crowdloan); match (&crowdloan.call, &crowdloan.target_address) { (Some(call), None) => { @@ -659,9 +668,6 @@ pub mod pallet { } } - crowdloan.finalized = true; - Crowdloans::::insert(crowdloan_id, &crowdloan); - Self::deposit_event(Event::::Finalized { crowdloan_id }); Ok(()) diff --git a/pallets/crowdloan/src/tests.rs b/pallets/crowdloan/src/tests.rs index a23de52198..863ca9ae60 100644 --- a/pallets/crowdloan/src/tests.rs +++ b/pallets/crowdloan/src/tests.rs @@ -1875,6 +1875,206 @@ fn test_finalize_fails_if_call_fails() { }); } +#[test] +fn test_finalize_fails_if_another_finalize_is_in_progress() { + TestState::default() + .with_balance(U256::from(1), 300.into()) + .with_balance(U256::from(2), 300.into()) + .build_and_execute(|| { + let creator: AccountOf = U256::from(1); + let contributor: AccountOf = U256::from(2); + let deposit: BalanceOf = 50.into(); + let min_contribution: BalanceOf = 10.into(); + let cap: BalanceOf = 100.into(); + let end: BlockNumberFor = 50; + let first_crowdloan_id: CrowdloanId = 0; + let second_crowdloan_id: CrowdloanId = 1; + + let nested_finalize_call = Box::new(RuntimeCall::Crowdloan(pallet_crowdloan::Call::< + Test, + >::finalize { + crowdloan_id: second_crowdloan_id, + })); + + assert_ok!(Crowdloan::create( + RuntimeOrigin::signed(creator), + deposit, + min_contribution, + cap, + end, + Some(nested_finalize_call), + None, + )); + assert_ok!(Crowdloan::create( + RuntimeOrigin::signed(creator), + deposit, + min_contribution, + cap, + end, + Some(noop_call()), + None, + )); + + run_to_block(10); + + assert_ok!(Crowdloan::contribute( + RuntimeOrigin::signed(contributor), + first_crowdloan_id, + 50.into() + )); + assert_ok!(Crowdloan::contribute( + RuntimeOrigin::signed(contributor), + second_crowdloan_id, + 50.into() + )); + + run_to_block(60); + + assert_err!( + Crowdloan::finalize(RuntimeOrigin::signed(creator), first_crowdloan_id), + pallet_crowdloan::Error::::AlreadyFinalizing + ); + + assert_eq!(pallet_crowdloan::CurrentCrowdloanId::::get(), None); + assert!( + pallet_crowdloan::Crowdloans::::get(first_crowdloan_id) + .is_some_and(|c| !c.finalized) + ); + assert!( + pallet_crowdloan::Crowdloans::::get(second_crowdloan_id) + .is_some_and(|c| !c.finalized) + ); + }); +} + +// The finalize `call` cannot re-enter `withdraw` on the same crowdloan: it is rejected and +// the extrinsic reverts, so no funds move and `raised` stays consistent with the real balance. +#[test] +fn test_finalize_blocks_reentrant_withdraw() { + TestState::default() + .with_balance(U256::from(1), 200.into()) // creator + .with_balance(U256::from(2), 200.into()) // contributor + .build_and_execute(|| { + let creator: AccountOf = U256::from(1); + let contributor: AccountOf = U256::from(2); + let deposit: BalanceOf = 50.into(); + let min_contribution: BalanceOf = 10.into(); + let cap: BalanceOf = 100.into(); + let end: BlockNumberFor = 50; + let crowdloan_id: CrowdloanId = 0; + + // The finalize call re-enters `withdraw` on the same crowdloan. + let reentrant_call = Box::new(RuntimeCall::Crowdloan( + pallet_crowdloan::Call::::withdraw { crowdloan_id }, + )); + + assert_ok!(Crowdloan::create( + RuntimeOrigin::signed(creator), + deposit, + min_contribution, + cap, + end, + Some(reentrant_call), + None, + )); + run_to_block(10); + + // Creator contributes 30 over the deposit (total 80); contributor fills the cap. + assert_ok!(Crowdloan::contribute( + RuntimeOrigin::signed(creator), + crowdloan_id, + 30.into() + )); + assert_ok!(Crowdloan::contribute( + RuntimeOrigin::signed(contributor), + crowdloan_id, + 20.into() + )); + + let funds_account = pallet_crowdloan::Pallet::::funds_account(crowdloan_id); + assert_eq!(Balances::free_balance(funds_account), cap); + let creator_balance_before = Balances::free_balance(creator); + + run_to_block(60); + + // Finalize dispatches the re-entrant withdraw, which is rejected with + // `AlreadyFinalized`. Wrap in a storage layer to model the per-extrinsic + // transaction the runtime applies in production, so the revert is observable. + let outcome = frame_support::storage::with_storage_layer(|| { + Crowdloan::finalize(RuntimeOrigin::signed(creator), crowdloan_id) + }); + assert_err!(outcome, pallet_crowdloan::Error::::AlreadyFinalized); + + // No funds were extracted and accounting is intact. + assert_eq!(Balances::free_balance(creator), creator_balance_before); + assert_eq!(Balances::free_balance(funds_account), cap); + assert_eq!(pallet_crowdloan::CurrentCrowdloanId::::get(), None); + let crowdloan = pallet_crowdloan::Crowdloans::::get(crowdloan_id).unwrap(); + assert!(!crowdloan.finalized); + assert_eq!(crowdloan.raised, cap); + + // Contributor funds are not frozen: the contributor can still withdraw. + assert_ok!(Crowdloan::withdraw( + RuntimeOrigin::signed(contributor), + crowdloan_id + )); + assert_eq!(Balances::free_balance(contributor), 200.into()); + }); +} + +// A re-entrant `refund` embedded as the finalize call is likewise rejected before moving funds. +#[test] +fn test_finalize_blocks_reentrant_refund() { + TestState::default() + .with_balance(U256::from(1), 200.into()) // creator + .with_balance(U256::from(2), 200.into()) // contributor + .build_and_execute(|| { + let creator: AccountOf = U256::from(1); + let contributor: AccountOf = U256::from(2); + let deposit: BalanceOf = 50.into(); + let min_contribution: BalanceOf = 10.into(); + let cap: BalanceOf = 100.into(); + let end: BlockNumberFor = 50; + let crowdloan_id: CrowdloanId = 0; + + let reentrant_call = Box::new(RuntimeCall::Crowdloan( + pallet_crowdloan::Call::::refund { crowdloan_id }, + )); + + assert_ok!(Crowdloan::create( + RuntimeOrigin::signed(creator), + deposit, + min_contribution, + cap, + end, + Some(reentrant_call), + None, + )); + run_to_block(10); + + assert_ok!(Crowdloan::contribute( + RuntimeOrigin::signed(creator), + crowdloan_id, + 30.into() + )); + assert_ok!(Crowdloan::contribute( + RuntimeOrigin::signed(contributor), + crowdloan_id, + 20.into() + )); + + let funds_account = pallet_crowdloan::Pallet::::funds_account(crowdloan_id); + run_to_block(60); + + // The re-entrant refund hits the `finalized` guard before transferring anything. + assert_err!( + Crowdloan::finalize(RuntimeOrigin::signed(creator), crowdloan_id), + pallet_crowdloan::Error::::AlreadyFinalized + ); + assert_eq!(Balances::free_balance(funds_account), cap); + }); +} + #[test] fn test_refund_succeeds() { TestState::default() diff --git a/pallets/crowdloan/src/weights.rs b/pallets/crowdloan/src/weights.rs index b639a42a8b..6ec23b2c23 100644 --- a/pallets/crowdloan/src/weights.rs +++ b/pallets/crowdloan/src/weights.rs @@ -2,9 +2,9 @@ //! Autogenerated weights for `pallet_crowdloan` //! //! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 49.1.0 -//! DATE: 2026-05-31, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! DATE: 2026-06-30, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` //! WORST CASE MAP SIZE: `1000000` -//! HOSTNAME: `runnervm3jyl0`, CPU: `AMD EPYC 9V74 80-Core Processor` +//! HOSTNAME: `runnervmmklqx`, CPU: `AMD EPYC 9V74 80-Core Processor` //! WASM-EXECUTION: `Compiled`, CHAIN: `None`, DB CACHE: `1024` // Executed Command: @@ -22,7 +22,7 @@ // --no-storage-info // --no-min-squares // --no-median-slopes -// --output=/tmp/tmp.QLzNE9hOoG +// --output=/tmp/tmp.CWtPZcdN9i // --template=/home/runner/work/subtensor/subtensor/.maintain/frame-weight-template.hbs #![cfg_attr(rustfmt, rustfmt_skip)] @@ -63,8 +63,8 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `119` // Estimated: `6148` - // Minimum execution time: 58_307_000 picoseconds. - Weight::from_parts(59_829_000, 6148) + // Minimum execution time: 59_888_000 picoseconds. + Weight::from_parts(61_110_000, 6148) .saturating_add(T::DbWeight::get().reads(3_u64)) .saturating_add(T::DbWeight::get().writes(5_u64)) } @@ -80,8 +80,8 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `448` // Estimated: `6148` - // Minimum execution time: 65_557_000 picoseconds. - Weight::from_parts(66_609_000, 6148) + // Minimum execution time: 65_807_000 picoseconds. + Weight::from_parts(66_819_000, 6148) .saturating_add(T::DbWeight::get().reads(5_u64)) .saturating_add(T::DbWeight::get().writes(4_u64)) } @@ -95,28 +95,28 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `408` // Estimated: `6148` - // Minimum execution time: 59_438_000 picoseconds. - Weight::from_parts(60_259_000, 6148) + // Minimum execution time: 59_447_000 picoseconds. + Weight::from_parts(61_220_000, 6148) .saturating_add(T::DbWeight::get().reads(4_u64)) .saturating_add(T::DbWeight::get().writes(4_u64)) } /// Storage: `Crowdloan::Crowdloans` (r:1 w:1) /// Proof: `Crowdloan::Crowdloans` (`max_values`: None, `max_size`: Some(282), added: 2757, mode: `MaxEncodedLen`) + /// Storage: `Crowdloan::CurrentCrowdloanId` (r:1 w:1) + /// Proof: `Crowdloan::CurrentCrowdloanId` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) /// Storage: `Preimage::PreimageFor` (r:1 w:0) /// Proof: `Preimage::PreimageFor` (`max_values`: None, `max_size`: Some(4194344), added: 4196819, mode: `MaxEncodedLen`) /// Storage: `SafeMode::EnteredUntil` (r:1 w:0) /// Proof: `SafeMode::EnteredUntil` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) /// Storage: `SubtensorModule::ColdkeySwapAnnouncements` (r:1 w:0) /// Proof: `SubtensorModule::ColdkeySwapAnnouncements` (`max_values`: None, `max_size`: None, mode: `Measured`) - /// Storage: `Crowdloan::CurrentCrowdloanId` (r:0 w:1) - /// Proof: `Crowdloan::CurrentCrowdloanId` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) fn finalize() -> Weight { // Proof Size summary in bytes: // Measured: `1181` // Estimated: `4197809` - // Minimum execution time: 30_264_000 picoseconds. - Weight::from_parts(31_507_000, 4197809) - .saturating_add(T::DbWeight::get().reads(4_u64)) + // Minimum execution time: 32_548_000 picoseconds. + Weight::from_parts(34_110_000, 4197809) + .saturating_add(T::DbWeight::get().reads(5_u64)) .saturating_add(T::DbWeight::get().writes(2_u64)) } /// Storage: `Crowdloan::Crowdloans` (r:1 w:1) @@ -130,10 +130,10 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `324 + k * (46 ±0)` // Estimated: `3747 + k * (2579 ±0)` - // Minimum execution time: 108_910_000 picoseconds. - Weight::from_parts(110_703_000, 3747) - // Standard Error: 96_515 - .saturating_add(Weight::from_parts(39_503_253, 0).saturating_mul(k.into())) + // Minimum execution time: 109_320_000 picoseconds. + Weight::from_parts(110_392_000, 3747) + // Standard Error: 94_672 + .saturating_add(Weight::from_parts(39_822_919, 0).saturating_mul(k.into())) .saturating_add(T::DbWeight::get().reads(2_u64)) .saturating_add(T::DbWeight::get().reads((2_u64).saturating_mul(k.into()))) .saturating_add(T::DbWeight::get().writes((2_u64).saturating_mul(k.into()))) @@ -151,8 +151,8 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `370` // Estimated: `6148` - // Minimum execution time: 66_138_000 picoseconds. - Weight::from_parts(66_939_000, 6148) + // Minimum execution time: 69_442_000 picoseconds. + Weight::from_parts(70_724_000, 6148) .saturating_add(T::DbWeight::get().reads(4_u64)) .saturating_add(T::DbWeight::get().writes(5_u64)) } @@ -164,8 +164,8 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `229` // Estimated: `3747` - // Minimum execution time: 12_639_000 picoseconds. - Weight::from_parts(13_259_000, 3747) + // Minimum execution time: 13_049_000 picoseconds. + Weight::from_parts(13_510_000, 3747) .saturating_add(T::DbWeight::get().reads(2_u64)) .saturating_add(T::DbWeight::get().writes(1_u64)) } @@ -175,8 +175,8 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `229` // Estimated: `3747` - // Minimum execution time: 11_637_000 picoseconds. - Weight::from_parts(12_048_000, 3747) + // Minimum execution time: 11_557_000 picoseconds. + Weight::from_parts(12_318_000, 3747) .saturating_add(T::DbWeight::get().reads(1_u64)) .saturating_add(T::DbWeight::get().writes(1_u64)) } @@ -186,8 +186,8 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `229` // Estimated: `3747` - // Minimum execution time: 11_026_000 picoseconds. - Weight::from_parts(11_357_000, 3747) + // Minimum execution time: 11_386_000 picoseconds. + Weight::from_parts(11_797_000, 3747) .saturating_add(T::DbWeight::get().reads(1_u64)) .saturating_add(T::DbWeight::get().writes(1_u64)) } @@ -201,8 +201,8 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `293` // Estimated: `3747` - // Minimum execution time: 15_263_000 picoseconds. - Weight::from_parts(16_024_000, 3747) + // Minimum execution time: 15_924_000 picoseconds. + Weight::from_parts(16_805_000, 3747) .saturating_add(T::DbWeight::get().reads(2_u64)) .saturating_add(T::DbWeight::get().writes(1_u64)) } @@ -222,8 +222,8 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `119` // Estimated: `6148` - // Minimum execution time: 58_307_000 picoseconds. - Weight::from_parts(59_829_000, 6148) + // Minimum execution time: 59_888_000 picoseconds. + Weight::from_parts(61_110_000, 6148) .saturating_add(RocksDbWeight::get().reads(3_u64)) .saturating_add(RocksDbWeight::get().writes(5_u64)) } @@ -239,8 +239,8 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `448` // Estimated: `6148` - // Minimum execution time: 65_557_000 picoseconds. - Weight::from_parts(66_609_000, 6148) + // Minimum execution time: 65_807_000 picoseconds. + Weight::from_parts(66_819_000, 6148) .saturating_add(RocksDbWeight::get().reads(5_u64)) .saturating_add(RocksDbWeight::get().writes(4_u64)) } @@ -254,28 +254,28 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `408` // Estimated: `6148` - // Minimum execution time: 59_438_000 picoseconds. - Weight::from_parts(60_259_000, 6148) + // Minimum execution time: 59_447_000 picoseconds. + Weight::from_parts(61_220_000, 6148) .saturating_add(RocksDbWeight::get().reads(4_u64)) .saturating_add(RocksDbWeight::get().writes(4_u64)) } /// Storage: `Crowdloan::Crowdloans` (r:1 w:1) /// Proof: `Crowdloan::Crowdloans` (`max_values`: None, `max_size`: Some(282), added: 2757, mode: `MaxEncodedLen`) + /// Storage: `Crowdloan::CurrentCrowdloanId` (r:1 w:1) + /// Proof: `Crowdloan::CurrentCrowdloanId` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) /// Storage: `Preimage::PreimageFor` (r:1 w:0) /// Proof: `Preimage::PreimageFor` (`max_values`: None, `max_size`: Some(4194344), added: 4196819, mode: `MaxEncodedLen`) /// Storage: `SafeMode::EnteredUntil` (r:1 w:0) /// Proof: `SafeMode::EnteredUntil` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) /// Storage: `SubtensorModule::ColdkeySwapAnnouncements` (r:1 w:0) /// Proof: `SubtensorModule::ColdkeySwapAnnouncements` (`max_values`: None, `max_size`: None, mode: `Measured`) - /// Storage: `Crowdloan::CurrentCrowdloanId` (r:0 w:1) - /// Proof: `Crowdloan::CurrentCrowdloanId` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) fn finalize() -> Weight { // Proof Size summary in bytes: // Measured: `1181` // Estimated: `4197809` - // Minimum execution time: 30_264_000 picoseconds. - Weight::from_parts(31_507_000, 4197809) - .saturating_add(RocksDbWeight::get().reads(4_u64)) + // Minimum execution time: 32_548_000 picoseconds. + Weight::from_parts(34_110_000, 4197809) + .saturating_add(RocksDbWeight::get().reads(5_u64)) .saturating_add(RocksDbWeight::get().writes(2_u64)) } /// Storage: `Crowdloan::Crowdloans` (r:1 w:1) @@ -289,10 +289,10 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `324 + k * (46 ±0)` // Estimated: `3747 + k * (2579 ±0)` - // Minimum execution time: 108_910_000 picoseconds. - Weight::from_parts(110_703_000, 3747) - // Standard Error: 96_515 - .saturating_add(Weight::from_parts(39_503_253, 0).saturating_mul(k.into())) + // Minimum execution time: 109_320_000 picoseconds. + Weight::from_parts(110_392_000, 3747) + // Standard Error: 94_672 + .saturating_add(Weight::from_parts(39_822_919, 0).saturating_mul(k.into())) .saturating_add(RocksDbWeight::get().reads(2_u64)) .saturating_add(RocksDbWeight::get().reads((2_u64).saturating_mul(k.into()))) .saturating_add(RocksDbWeight::get().writes((2_u64).saturating_mul(k.into()))) @@ -310,8 +310,8 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `370` // Estimated: `6148` - // Minimum execution time: 66_138_000 picoseconds. - Weight::from_parts(66_939_000, 6148) + // Minimum execution time: 69_442_000 picoseconds. + Weight::from_parts(70_724_000, 6148) .saturating_add(RocksDbWeight::get().reads(4_u64)) .saturating_add(RocksDbWeight::get().writes(5_u64)) } @@ -323,8 +323,8 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `229` // Estimated: `3747` - // Minimum execution time: 12_639_000 picoseconds. - Weight::from_parts(13_259_000, 3747) + // Minimum execution time: 13_049_000 picoseconds. + Weight::from_parts(13_510_000, 3747) .saturating_add(RocksDbWeight::get().reads(2_u64)) .saturating_add(RocksDbWeight::get().writes(1_u64)) } @@ -334,8 +334,8 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `229` // Estimated: `3747` - // Minimum execution time: 11_637_000 picoseconds. - Weight::from_parts(12_048_000, 3747) + // Minimum execution time: 11_557_000 picoseconds. + Weight::from_parts(12_318_000, 3747) .saturating_add(RocksDbWeight::get().reads(1_u64)) .saturating_add(RocksDbWeight::get().writes(1_u64)) } @@ -345,8 +345,8 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `229` // Estimated: `3747` - // Minimum execution time: 11_026_000 picoseconds. - Weight::from_parts(11_357_000, 3747) + // Minimum execution time: 11_386_000 picoseconds. + Weight::from_parts(11_797_000, 3747) .saturating_add(RocksDbWeight::get().reads(1_u64)) .saturating_add(RocksDbWeight::get().writes(1_u64)) } @@ -360,8 +360,8 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `293` // Estimated: `3747` - // Minimum execution time: 15_263_000 picoseconds. - Weight::from_parts(16_024_000, 3747) + // Minimum execution time: 15_924_000 picoseconds. + Weight::from_parts(16_805_000, 3747) .saturating_add(RocksDbWeight::get().reads(2_u64)) .saturating_add(RocksDbWeight::get().writes(1_u64)) } diff --git a/pallets/drand/README.md b/pallets/drand/README.md index d0bdf0b7e7..70f5181714 100644 --- a/pallets/drand/README.md +++ b/pallets/drand/README.md @@ -1,6 +1,6 @@ # Drand Bridge Pallet -This is a [FRAME](https://docs.substrate.io/reference/frame-pallets/) pallet that allows Substrate-based chains to bridge to drand. It only supports bridging to drand's [Quicknet](https://drand.love/blog/quicknet-is-live-on-the-league-of-entropy-mainnet), which provides fresh randomness every 3 seconds. Adding this pallet to a runtime allows it to acquire verifiable on-chain randomness which can be used in runtime modules or ink! smart contracts. +This is a [FRAME](https://docs.substrate.io/reference/frame-pallets/) pallet that allows Substrate-based chains to bridge to drand. It only supports bridging to drand's [Quicknet](https://drand.love/blog/quicknet-is-live-on-the-league-of-entropy-mainnet), which provides fresh randomness every 3 seconds. Adding this pallet to a runtime allows it to acquire verifiable on-chain randomness which can be used in runtime modules or ink! smart contracts. Read [here](https://github.com/ideal-lab5/pallet-drand/blob/main/docs/how_it_works.md) for a deep-dive into the pallet. @@ -13,14 +13,14 @@ Use this pallet in a Substrate runtime to acquire verifiable randomness from dra Usage of this pallet requires that the node support: - arkworks host functions - offchain workers -- (optional - in case of smart contracts) Contracts pallet and drand chain extension enabled +- (optional - in case of smart contracts) Contracts pallet and drand chain extension enabled We have included a node in this repo, [substrate-node-template](https://github.com/ideal-lab5/pallet-drand/tree/main/substrate-node-template), that meets these requirements that you can use to get started. See [here](https://github.com/ideal-lab5/pallet-drand/blob/main/docs/integration.md) for a detailed guide on integrating this pallet into a runtime. ### For Pallets -This pallet implements the [Randomness](https://paritytech.github.io/polkadot-sdk/master/frame_support/traits/trait.Randomness.html) trait. FRAME pallets can use it by configuring their runtimes +This pallet implements the [Randomness](https://paritytech.github.io/polkadot-sdk/master/frame_support/traits/trait.Randomness.html) trait. FRAME pallets can use it by configuring their runtimes ``` rust impl pallet_with_randomness for Runtime { @@ -48,7 +48,7 @@ cargo build ## Testing -We maintain a minimum of 85% coverage on all new code. You can check coverage with tarpauling by running +We maintain a minimum of 85% coverage on all new code. You can check coverage with tarpauling by running ``` shell cargo tarpaulin --rustflags="-C opt-level=0" diff --git a/pallets/limit-orders/src/weights.rs b/pallets/limit-orders/src/weights.rs index e8c24d30ba..c747bbc0bb 100644 --- a/pallets/limit-orders/src/weights.rs +++ b/pallets/limit-orders/src/weights.rs @@ -2,9 +2,9 @@ //! Autogenerated weights for `pallet_limit_orders` //! //! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 49.1.0 -//! DATE: 2026-06-11, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! DATE: 2026-07-06, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` //! WORST CASE MAP SIZE: `1000000` -//! HOSTNAME: `runnervm3jyl0`, CPU: `AMD EPYC 9V74 80-Core Processor` +//! HOSTNAME: `runnervmkkn4f`, CPU: `AMD EPYC 9V74 80-Core Processor` //! WASM-EXECUTION: `Compiled`, CHAIN: `None`, DB CACHE: `1024` // Executed Command: @@ -22,7 +22,7 @@ // --no-storage-info // --no-min-squares // --no-median-slopes -// --output=/tmp/tmp.h1ZElBJrCs +// --output=/tmp/tmp.imBMX5Abno // --template=/home/runner/work/subtensor/subtensor/.maintain/frame-weight-template.hbs #![cfg_attr(rustfmt, rustfmt_skip)] @@ -51,8 +51,8 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `66` // Estimated: `3522` - // Minimum execution time: 13_230_000 picoseconds. - Weight::from_parts(14_822_000, 3522) + // Minimum execution time: 13_881_000 picoseconds. + Weight::from_parts(15_082_000, 3522) .saturating_add(T::DbWeight::get().reads(1_u64)) .saturating_add(T::DbWeight::get().writes(1_u64)) } @@ -62,8 +62,8 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 4_006_000 picoseconds. - Weight::from_parts(4_266_000, 0) + // Minimum execution time: 4_086_000 picoseconds. + Weight::from_parts(4_476_000, 0) .saturating_add(T::DbWeight::get().writes(1_u64)) } /// Storage: `LimitOrders::LimitOrdersEnabled` (r:1 w:0) @@ -90,10 +90,10 @@ impl WeightInfo for SubstrateWeight { /// Proof: `SubtensorModule::Owner` (`max_values`: None, `max_size`: None, mode: `Measured`) /// Storage: `System::Account` (r:202 w:202) /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(104), added: 2579, mode: `MaxEncodedLen`) - /// Storage: `Swap::PalSwapInitialized` (r:1 w:1) - /// Proof: `Swap::PalSwapInitialized` (`max_values`: None, `max_size`: Some(11), added: 2486, mode: `MaxEncodedLen`) /// Storage: `Swap::FeeRate` (r:1 w:0) /// Proof: `Swap::FeeRate` (`max_values`: None, `max_size`: Some(12), added: 2487, mode: `MaxEncodedLen`) + /// Storage: `Swap::PalSwapInitialized` (r:1 w:1) + /// Proof: `Swap::PalSwapInitialized` (`max_values`: None, `max_size`: Some(11), added: 2486, mode: `MaxEncodedLen`) /// Storage: `SubtensorModule::SubnetAlphaOut` (r:1 w:1) /// Proof: `SubtensorModule::SubnetAlphaOut` (`max_values`: None, `max_size`: None, mode: `Measured`) /// Storage: `SubtensorModule::TotalStake` (r:1 w:1) @@ -123,10 +123,10 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `1134 + n * (283 ±0)` // Estimated: `6148 + n * (5158 ±0)` - // Minimum execution time: 597_854_000 picoseconds. - Weight::from_parts(61_768_457, 6148) - // Standard Error: 136_266 - .saturating_add(Weight::from_parts(520_180_782, 0).saturating_mul(n.into())) + // Minimum execution time: 630_559_000 picoseconds. + Weight::from_parts(158_034_794, 6148) + // Standard Error: 387_312 + .saturating_add(Weight::from_parts(553_591_799, 0).saturating_mul(n.into())) .saturating_add(T::DbWeight::get().reads(17_u64)) .saturating_add(T::DbWeight::get().reads((11_u64).saturating_mul(n.into()))) .saturating_add(T::DbWeight::get().writes(10_u64)) @@ -155,10 +155,10 @@ impl WeightInfo for SubstrateWeight { /// Proof: `SubtensorModule::NetworksAdded` (`max_values`: None, `max_size`: None, mode: `Measured`) /// Storage: `SubtensorModule::SubtokenEnabled` (r:1 w:0) /// Proof: `SubtensorModule::SubtokenEnabled` (`max_values`: None, `max_size`: None, mode: `Measured`) - /// Storage: `Swap::PalSwapInitialized` (r:1 w:1) - /// Proof: `Swap::PalSwapInitialized` (`max_values`: None, `max_size`: Some(11), added: 2486, mode: `MaxEncodedLen`) /// Storage: `Swap::FeeRate` (r:1 w:0) /// Proof: `Swap::FeeRate` (`max_values`: None, `max_size`: Some(12), added: 2487, mode: `MaxEncodedLen`) + /// Storage: `Swap::PalSwapInitialized` (r:1 w:1) + /// Proof: `Swap::PalSwapInitialized` (`max_values`: None, `max_size`: Some(11), added: 2486, mode: `MaxEncodedLen`) /// Storage: `SubtensorModule::SubnetAlphaOut` (r:1 w:1) /// Proof: `SubtensorModule::SubnetAlphaOut` (`max_values`: None, `max_size`: None, mode: `Measured`) /// Storage: `SubtensorModule::TotalStake` (r:1 w:1) @@ -190,10 +190,10 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `1263 + n * (283 ±0)` // Estimated: `8727 + n * (5158 ±0)` - // Minimum execution time: 747_334_000 picoseconds. - Weight::from_parts(499_718_496, 8727) - // Standard Error: 74_442 - .saturating_add(Weight::from_parts(259_134_004, 0).saturating_mul(n.into())) + // Minimum execution time: 785_823_000 picoseconds. + Weight::from_parts(492_996_306, 8727) + // Standard Error: 99_323 + .saturating_add(Weight::from_parts(278_547_407, 0).saturating_mul(n.into())) .saturating_add(T::DbWeight::get().reads(25_u64)) .saturating_add(T::DbWeight::get().reads((10_u64).saturating_mul(n.into()))) .saturating_add(T::DbWeight::get().writes(15_u64)) @@ -210,8 +210,8 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `66` // Estimated: `3522` - // Minimum execution time: 13_230_000 picoseconds. - Weight::from_parts(14_822_000, 3522) + // Minimum execution time: 13_881_000 picoseconds. + Weight::from_parts(15_082_000, 3522) .saturating_add(RocksDbWeight::get().reads(1_u64)) .saturating_add(RocksDbWeight::get().writes(1_u64)) } @@ -221,8 +221,8 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 4_006_000 picoseconds. - Weight::from_parts(4_266_000, 0) + // Minimum execution time: 4_086_000 picoseconds. + Weight::from_parts(4_476_000, 0) .saturating_add(RocksDbWeight::get().writes(1_u64)) } /// Storage: `LimitOrders::LimitOrdersEnabled` (r:1 w:0) @@ -249,10 +249,10 @@ impl WeightInfo for () { /// Proof: `SubtensorModule::Owner` (`max_values`: None, `max_size`: None, mode: `Measured`) /// Storage: `System::Account` (r:202 w:202) /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(104), added: 2579, mode: `MaxEncodedLen`) - /// Storage: `Swap::PalSwapInitialized` (r:1 w:1) - /// Proof: `Swap::PalSwapInitialized` (`max_values`: None, `max_size`: Some(11), added: 2486, mode: `MaxEncodedLen`) /// Storage: `Swap::FeeRate` (r:1 w:0) /// Proof: `Swap::FeeRate` (`max_values`: None, `max_size`: Some(12), added: 2487, mode: `MaxEncodedLen`) + /// Storage: `Swap::PalSwapInitialized` (r:1 w:1) + /// Proof: `Swap::PalSwapInitialized` (`max_values`: None, `max_size`: Some(11), added: 2486, mode: `MaxEncodedLen`) /// Storage: `SubtensorModule::SubnetAlphaOut` (r:1 w:1) /// Proof: `SubtensorModule::SubnetAlphaOut` (`max_values`: None, `max_size`: None, mode: `Measured`) /// Storage: `SubtensorModule::TotalStake` (r:1 w:1) @@ -282,10 +282,10 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `1134 + n * (283 ±0)` // Estimated: `6148 + n * (5158 ±0)` - // Minimum execution time: 597_854_000 picoseconds. - Weight::from_parts(61_768_457, 6148) - // Standard Error: 136_266 - .saturating_add(Weight::from_parts(520_180_782, 0).saturating_mul(n.into())) + // Minimum execution time: 630_559_000 picoseconds. + Weight::from_parts(158_034_794, 6148) + // Standard Error: 387_312 + .saturating_add(Weight::from_parts(553_591_799, 0).saturating_mul(n.into())) .saturating_add(RocksDbWeight::get().reads(17_u64)) .saturating_add(RocksDbWeight::get().reads((11_u64).saturating_mul(n.into()))) .saturating_add(RocksDbWeight::get().writes(10_u64)) @@ -314,10 +314,10 @@ impl WeightInfo for () { /// Proof: `SubtensorModule::NetworksAdded` (`max_values`: None, `max_size`: None, mode: `Measured`) /// Storage: `SubtensorModule::SubtokenEnabled` (r:1 w:0) /// Proof: `SubtensorModule::SubtokenEnabled` (`max_values`: None, `max_size`: None, mode: `Measured`) - /// Storage: `Swap::PalSwapInitialized` (r:1 w:1) - /// Proof: `Swap::PalSwapInitialized` (`max_values`: None, `max_size`: Some(11), added: 2486, mode: `MaxEncodedLen`) /// Storage: `Swap::FeeRate` (r:1 w:0) /// Proof: `Swap::FeeRate` (`max_values`: None, `max_size`: Some(12), added: 2487, mode: `MaxEncodedLen`) + /// Storage: `Swap::PalSwapInitialized` (r:1 w:1) + /// Proof: `Swap::PalSwapInitialized` (`max_values`: None, `max_size`: Some(11), added: 2486, mode: `MaxEncodedLen`) /// Storage: `SubtensorModule::SubnetAlphaOut` (r:1 w:1) /// Proof: `SubtensorModule::SubnetAlphaOut` (`max_values`: None, `max_size`: None, mode: `Measured`) /// Storage: `SubtensorModule::TotalStake` (r:1 w:1) @@ -349,10 +349,10 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `1263 + n * (283 ±0)` // Estimated: `8727 + n * (5158 ±0)` - // Minimum execution time: 747_334_000 picoseconds. - Weight::from_parts(499_718_496, 8727) - // Standard Error: 74_442 - .saturating_add(Weight::from_parts(259_134_004, 0).saturating_mul(n.into())) + // Minimum execution time: 785_823_000 picoseconds. + Weight::from_parts(492_996_306, 8727) + // Standard Error: 99_323 + .saturating_add(Weight::from_parts(278_547_407, 0).saturating_mul(n.into())) .saturating_add(RocksDbWeight::get().reads(25_u64)) .saturating_add(RocksDbWeight::get().reads((10_u64).saturating_mul(n.into()))) .saturating_add(RocksDbWeight::get().writes(15_u64)) diff --git a/pallets/proxy/src/weights.rs b/pallets/proxy/src/weights.rs index 1885d8b3f6..5f6db1d84d 100644 --- a/pallets/proxy/src/weights.rs +++ b/pallets/proxy/src/weights.rs @@ -2,9 +2,9 @@ //! Autogenerated weights for `pallet_subtensor_proxy` //! //! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 49.1.0 -//! DATE: 2026-06-23, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! DATE: 2026-07-06, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` //! WORST CASE MAP SIZE: `1000000` -//! HOSTNAME: `runnervm7b5n9`, CPU: `AMD EPYC 7763 64-Core Processor` +//! HOSTNAME: `runnervmkkn4f`, CPU: `AMD EPYC 9V74 80-Core Processor` //! WASM-EXECUTION: `Compiled`, CHAIN: `None`, DB CACHE: `1024` // Executed Command: @@ -22,7 +22,7 @@ // --no-storage-info // --no-min-squares // --no-median-slopes -// --output=/tmp/tmp.y6dm1DHMCB +// --output=/tmp/tmp.F3PokA3kSP // --template=/home/runner/work/subtensor/subtensor/.maintain/frame-weight-template.hbs #![cfg_attr(rustfmt, rustfmt_skip)] @@ -66,10 +66,10 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `637 + p * (37 ±0)` // Estimated: `4254 + p * (37 ±0)` - // Minimum execution time: 26_629_000 picoseconds. - Weight::from_parts(28_026_265, 4254) - // Standard Error: 4_888 - .saturating_add(Weight::from_parts(53_205, 0).saturating_mul(p.into())) + // Minimum execution time: 22_934_000 picoseconds. + Weight::from_parts(24_359_160, 4254) + // Standard Error: 2_948 + .saturating_add(Weight::from_parts(43_205, 0).saturating_mul(p.into())) .saturating_add(T::DbWeight::get().reads(3_u64)) .saturating_add(T::DbWeight::get().writes(1_u64)) .saturating_add(Weight::from_parts(0, 37).saturating_mul(p.into())) @@ -92,12 +92,12 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `894 + a * (68 ±0) + p * (37 ±0)` // Estimated: `8615 + a * (68 ±0) + p * (37 ±0)` - // Minimum execution time: 52_588_000 picoseconds. - Weight::from_parts(53_234_507, 8615) - // Standard Error: 1_933 - .saturating_add(Weight::from_parts(228_831, 0).saturating_mul(a.into())) - // Standard Error: 7_745 - .saturating_add(Weight::from_parts(54_037, 0).saturating_mul(p.into())) + // Minimum execution time: 48_171_000 picoseconds. + Weight::from_parts(49_373_534, 8615) + // Standard Error: 1_688 + .saturating_add(Weight::from_parts(222_342, 0).saturating_mul(a.into())) + // Standard Error: 6_764 + .saturating_add(Weight::from_parts(43_307, 0).saturating_mul(p.into())) .saturating_add(T::DbWeight::get().reads(5_u64)) .saturating_add(T::DbWeight::get().writes(3_u64)) .saturating_add(Weight::from_parts(0, 68).saturating_mul(a.into())) @@ -113,12 +113,12 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `299 + a * (68 ±0)` // Estimated: `8615` - // Minimum execution time: 26_049_000 picoseconds. - Weight::from_parts(25_595_226, 8615) - // Standard Error: 1_041 - .saturating_add(Weight::from_parts(205_163, 0).saturating_mul(a.into())) - // Standard Error: 4_172 - .saturating_add(Weight::from_parts(40_277, 0).saturating_mul(p.into())) + // Minimum execution time: 22_724_000 picoseconds. + Weight::from_parts(23_803_558, 8615) + // Standard Error: 930 + .saturating_add(Weight::from_parts(192_952, 0).saturating_mul(a.into())) + // Standard Error: 3_725 + .saturating_add(Weight::from_parts(9_464, 0).saturating_mul(p.into())) .saturating_add(T::DbWeight::get().reads(2_u64)) .saturating_add(T::DbWeight::get().writes(2_u64)) } @@ -132,12 +132,12 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `299 + a * (68 ±0)` // Estimated: `8615` - // Minimum execution time: 26_269_000 picoseconds. - Weight::from_parts(26_388_332, 8615) - // Standard Error: 1_181 - .saturating_add(Weight::from_parts(199_703, 0).saturating_mul(a.into())) - // Standard Error: 4_732 - .saturating_add(Weight::from_parts(17_846, 0).saturating_mul(p.into())) + // Minimum execution time: 22_493_000 picoseconds. + Weight::from_parts(23_435_113, 8615) + // Standard Error: 1_036 + .saturating_add(Weight::from_parts(196_646, 0).saturating_mul(a.into())) + // Standard Error: 4_152 + .saturating_add(Weight::from_parts(23_846, 0).saturating_mul(p.into())) .saturating_add(T::DbWeight::get().reads(2_u64)) .saturating_add(T::DbWeight::get().writes(2_u64)) } @@ -153,12 +153,12 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `308 + a * (68 ±0) + p * (37 ±0)` // Estimated: `8615` - // Minimum execution time: 33_873_000 picoseconds. - Weight::from_parts(34_286_251, 8615) - // Standard Error: 2_040 - .saturating_add(Weight::from_parts(194_092, 0).saturating_mul(a.into())) - // Standard Error: 8_174 - .saturating_add(Weight::from_parts(53_615, 0).saturating_mul(p.into())) + // Minimum execution time: 30_095_000 picoseconds. + Weight::from_parts(31_077_340, 8615) + // Standard Error: 1_265 + .saturating_add(Weight::from_parts(193_963, 0).saturating_mul(a.into())) + // Standard Error: 5_066 + .saturating_add(Weight::from_parts(35_501, 0).saturating_mul(p.into())) .saturating_add(T::DbWeight::get().reads(3_u64)) .saturating_add(T::DbWeight::get().writes(2_u64)) } @@ -169,10 +169,10 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `119 + p * (37 ±0)` // Estimated: `4254` - // Minimum execution time: 24_907_000 picoseconds. - Weight::from_parts(25_770_804, 4254) - // Standard Error: 2_400 - .saturating_add(Weight::from_parts(80_892, 0).saturating_mul(p.into())) + // Minimum execution time: 22_193_000 picoseconds. + Weight::from_parts(22_948_451, 4254) + // Standard Error: 2_276 + .saturating_add(Weight::from_parts(50_581, 0).saturating_mul(p.into())) .saturating_add(T::DbWeight::get().reads(1_u64)) .saturating_add(T::DbWeight::get().writes(1_u64)) } @@ -185,10 +185,10 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `119 + p * (37 ±0)` // Estimated: `4254` - // Minimum execution time: 26_690_000 picoseconds. - Weight::from_parts(27_827_049, 4254) - // Standard Error: 2_885 - .saturating_add(Weight::from_parts(64_054, 0).saturating_mul(p.into())) + // Minimum execution time: 23_265_000 picoseconds. + Weight::from_parts(24_396_165, 4254) + // Standard Error: 2_158 + .saturating_add(Weight::from_parts(62_587, 0).saturating_mul(p.into())) .saturating_add(T::DbWeight::get().reads(1_u64)) .saturating_add(T::DbWeight::get().writes(2_u64)) } @@ -199,10 +199,10 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `119 + p * (37 ±0)` // Estimated: `4254` - // Minimum execution time: 26_159_000 picoseconds. - Weight::from_parts(27_407_028, 4254) - // Standard Error: 3_009 - .saturating_add(Weight::from_parts(55_808, 0).saturating_mul(p.into())) + // Minimum execution time: 23_094_000 picoseconds. + Weight::from_parts(24_017_718, 4254) + // Standard Error: 2_417 + .saturating_add(Weight::from_parts(40_784, 0).saturating_mul(p.into())) .saturating_add(T::DbWeight::get().reads(1_u64)) .saturating_add(T::DbWeight::get().writes(1_u64)) } @@ -213,10 +213,10 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `139` // Estimated: `4254` - // Minimum execution time: 26_699_000 picoseconds. - Weight::from_parts(27_853_436, 4254) - // Standard Error: 3_475 - .saturating_add(Weight::from_parts(20_160, 0).saturating_mul(p.into())) + // Minimum execution time: 23_465_000 picoseconds. + Weight::from_parts(24_438_326, 4254) + // Standard Error: 2_078 + .saturating_add(Weight::from_parts(21_719, 0).saturating_mul(p.into())) .saturating_add(T::DbWeight::get().reads(1_u64)) .saturating_add(T::DbWeight::get().writes(1_u64)) } @@ -227,10 +227,10 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `156 + p * (37 ±0)` // Estimated: `4254` - // Minimum execution time: 25_437_000 picoseconds. - Weight::from_parts(26_529_087, 4254) - // Standard Error: 2_476 - .saturating_add(Weight::from_parts(48_014, 0).saturating_mul(p.into())) + // Minimum execution time: 22_533_000 picoseconds. + Weight::from_parts(23_452_280, 4254) + // Standard Error: 2_241 + .saturating_add(Weight::from_parts(44_967, 0).saturating_mul(p.into())) .saturating_add(T::DbWeight::get().reads(1_u64)) .saturating_add(T::DbWeight::get().writes(1_u64)) } @@ -244,8 +244,8 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `412` // Estimated: `8615` - // Minimum execution time: 46_066_000 picoseconds. - Weight::from_parts(46_757_000, 8615) + // Minimum execution time: 41_261_000 picoseconds. + Weight::from_parts(42_122_000, 8615) .saturating_add(T::DbWeight::get().reads(3_u64)) .saturating_add(T::DbWeight::get().writes(3_u64)) } @@ -258,10 +258,10 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `119 + p * (37 ±0)` // Estimated: `4254` - // Minimum execution time: 13_585_000 picoseconds. - Weight::from_parts(14_259_931, 4254) - // Standard Error: 1_733 - .saturating_add(Weight::from_parts(42_799, 0).saturating_mul(p.into())) + // Minimum execution time: 11_638_000 picoseconds. + Weight::from_parts(12_213_324, 4254) + // Standard Error: 1_529 + .saturating_add(Weight::from_parts(33_564, 0).saturating_mul(p.into())) .saturating_add(T::DbWeight::get().reads(1_u64)) .saturating_add(T::DbWeight::get().writes(1_u64)) } @@ -282,10 +282,10 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `637 + p * (37 ±0)` // Estimated: `4254 + p * (37 ±0)` - // Minimum execution time: 26_629_000 picoseconds. - Weight::from_parts(28_026_265, 4254) - // Standard Error: 4_888 - .saturating_add(Weight::from_parts(53_205, 0).saturating_mul(p.into())) + // Minimum execution time: 22_934_000 picoseconds. + Weight::from_parts(24_359_160, 4254) + // Standard Error: 2_948 + .saturating_add(Weight::from_parts(43_205, 0).saturating_mul(p.into())) .saturating_add(RocksDbWeight::get().reads(3_u64)) .saturating_add(RocksDbWeight::get().writes(1_u64)) .saturating_add(Weight::from_parts(0, 37).saturating_mul(p.into())) @@ -308,12 +308,12 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `894 + a * (68 ±0) + p * (37 ±0)` // Estimated: `8615 + a * (68 ±0) + p * (37 ±0)` - // Minimum execution time: 52_588_000 picoseconds. - Weight::from_parts(53_234_507, 8615) - // Standard Error: 1_933 - .saturating_add(Weight::from_parts(228_831, 0).saturating_mul(a.into())) - // Standard Error: 7_745 - .saturating_add(Weight::from_parts(54_037, 0).saturating_mul(p.into())) + // Minimum execution time: 48_171_000 picoseconds. + Weight::from_parts(49_373_534, 8615) + // Standard Error: 1_688 + .saturating_add(Weight::from_parts(222_342, 0).saturating_mul(a.into())) + // Standard Error: 6_764 + .saturating_add(Weight::from_parts(43_307, 0).saturating_mul(p.into())) .saturating_add(RocksDbWeight::get().reads(5_u64)) .saturating_add(RocksDbWeight::get().writes(3_u64)) .saturating_add(Weight::from_parts(0, 68).saturating_mul(a.into())) @@ -329,12 +329,12 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `299 + a * (68 ±0)` // Estimated: `8615` - // Minimum execution time: 26_049_000 picoseconds. - Weight::from_parts(25_595_226, 8615) - // Standard Error: 1_041 - .saturating_add(Weight::from_parts(205_163, 0).saturating_mul(a.into())) - // Standard Error: 4_172 - .saturating_add(Weight::from_parts(40_277, 0).saturating_mul(p.into())) + // Minimum execution time: 22_724_000 picoseconds. + Weight::from_parts(23_803_558, 8615) + // Standard Error: 930 + .saturating_add(Weight::from_parts(192_952, 0).saturating_mul(a.into())) + // Standard Error: 3_725 + .saturating_add(Weight::from_parts(9_464, 0).saturating_mul(p.into())) .saturating_add(RocksDbWeight::get().reads(2_u64)) .saturating_add(RocksDbWeight::get().writes(2_u64)) } @@ -348,12 +348,12 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `299 + a * (68 ±0)` // Estimated: `8615` - // Minimum execution time: 26_269_000 picoseconds. - Weight::from_parts(26_388_332, 8615) - // Standard Error: 1_181 - .saturating_add(Weight::from_parts(199_703, 0).saturating_mul(a.into())) - // Standard Error: 4_732 - .saturating_add(Weight::from_parts(17_846, 0).saturating_mul(p.into())) + // Minimum execution time: 22_493_000 picoseconds. + Weight::from_parts(23_435_113, 8615) + // Standard Error: 1_036 + .saturating_add(Weight::from_parts(196_646, 0).saturating_mul(a.into())) + // Standard Error: 4_152 + .saturating_add(Weight::from_parts(23_846, 0).saturating_mul(p.into())) .saturating_add(RocksDbWeight::get().reads(2_u64)) .saturating_add(RocksDbWeight::get().writes(2_u64)) } @@ -369,12 +369,12 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `308 + a * (68 ±0) + p * (37 ±0)` // Estimated: `8615` - // Minimum execution time: 33_873_000 picoseconds. - Weight::from_parts(34_286_251, 8615) - // Standard Error: 2_040 - .saturating_add(Weight::from_parts(194_092, 0).saturating_mul(a.into())) - // Standard Error: 8_174 - .saturating_add(Weight::from_parts(53_615, 0).saturating_mul(p.into())) + // Minimum execution time: 30_095_000 picoseconds. + Weight::from_parts(31_077_340, 8615) + // Standard Error: 1_265 + .saturating_add(Weight::from_parts(193_963, 0).saturating_mul(a.into())) + // Standard Error: 5_066 + .saturating_add(Weight::from_parts(35_501, 0).saturating_mul(p.into())) .saturating_add(RocksDbWeight::get().reads(3_u64)) .saturating_add(RocksDbWeight::get().writes(2_u64)) } @@ -385,10 +385,10 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `119 + p * (37 ±0)` // Estimated: `4254` - // Minimum execution time: 24_907_000 picoseconds. - Weight::from_parts(25_770_804, 4254) - // Standard Error: 2_400 - .saturating_add(Weight::from_parts(80_892, 0).saturating_mul(p.into())) + // Minimum execution time: 22_193_000 picoseconds. + Weight::from_parts(22_948_451, 4254) + // Standard Error: 2_276 + .saturating_add(Weight::from_parts(50_581, 0).saturating_mul(p.into())) .saturating_add(RocksDbWeight::get().reads(1_u64)) .saturating_add(RocksDbWeight::get().writes(1_u64)) } @@ -401,10 +401,10 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `119 + p * (37 ±0)` // Estimated: `4254` - // Minimum execution time: 26_690_000 picoseconds. - Weight::from_parts(27_827_049, 4254) - // Standard Error: 2_885 - .saturating_add(Weight::from_parts(64_054, 0).saturating_mul(p.into())) + // Minimum execution time: 23_265_000 picoseconds. + Weight::from_parts(24_396_165, 4254) + // Standard Error: 2_158 + .saturating_add(Weight::from_parts(62_587, 0).saturating_mul(p.into())) .saturating_add(RocksDbWeight::get().reads(1_u64)) .saturating_add(RocksDbWeight::get().writes(2_u64)) } @@ -415,10 +415,10 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `119 + p * (37 ±0)` // Estimated: `4254` - // Minimum execution time: 26_159_000 picoseconds. - Weight::from_parts(27_407_028, 4254) - // Standard Error: 3_009 - .saturating_add(Weight::from_parts(55_808, 0).saturating_mul(p.into())) + // Minimum execution time: 23_094_000 picoseconds. + Weight::from_parts(24_017_718, 4254) + // Standard Error: 2_417 + .saturating_add(Weight::from_parts(40_784, 0).saturating_mul(p.into())) .saturating_add(RocksDbWeight::get().reads(1_u64)) .saturating_add(RocksDbWeight::get().writes(1_u64)) } @@ -429,10 +429,10 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `139` // Estimated: `4254` - // Minimum execution time: 26_699_000 picoseconds. - Weight::from_parts(27_853_436, 4254) - // Standard Error: 3_475 - .saturating_add(Weight::from_parts(20_160, 0).saturating_mul(p.into())) + // Minimum execution time: 23_465_000 picoseconds. + Weight::from_parts(24_438_326, 4254) + // Standard Error: 2_078 + .saturating_add(Weight::from_parts(21_719, 0).saturating_mul(p.into())) .saturating_add(RocksDbWeight::get().reads(1_u64)) .saturating_add(RocksDbWeight::get().writes(1_u64)) } @@ -443,10 +443,10 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `156 + p * (37 ±0)` // Estimated: `4254` - // Minimum execution time: 25_437_000 picoseconds. - Weight::from_parts(26_529_087, 4254) - // Standard Error: 2_476 - .saturating_add(Weight::from_parts(48_014, 0).saturating_mul(p.into())) + // Minimum execution time: 22_533_000 picoseconds. + Weight::from_parts(23_452_280, 4254) + // Standard Error: 2_241 + .saturating_add(Weight::from_parts(44_967, 0).saturating_mul(p.into())) .saturating_add(RocksDbWeight::get().reads(1_u64)) .saturating_add(RocksDbWeight::get().writes(1_u64)) } @@ -460,8 +460,8 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `412` // Estimated: `8615` - // Minimum execution time: 46_066_000 picoseconds. - Weight::from_parts(46_757_000, 8615) + // Minimum execution time: 41_261_000 picoseconds. + Weight::from_parts(42_122_000, 8615) .saturating_add(RocksDbWeight::get().reads(3_u64)) .saturating_add(RocksDbWeight::get().writes(3_u64)) } @@ -474,10 +474,10 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `119 + p * (37 ±0)` // Estimated: `4254` - // Minimum execution time: 13_585_000 picoseconds. - Weight::from_parts(14_259_931, 4254) - // Standard Error: 1_733 - .saturating_add(Weight::from_parts(42_799, 0).saturating_mul(p.into())) + // Minimum execution time: 11_638_000 picoseconds. + Weight::from_parts(12_213_324, 4254) + // Standard Error: 1_529 + .saturating_add(Weight::from_parts(33_564, 0).saturating_mul(p.into())) .saturating_add(RocksDbWeight::get().reads(1_u64)) .saturating_add(RocksDbWeight::get().writes(1_u64)) } diff --git a/pallets/shield/Cargo.toml b/pallets/shield/Cargo.toml index 8888b249b9..dad58e09b3 100644 --- a/pallets/shield/Cargo.toml +++ b/pallets/shield/Cargo.toml @@ -1,12 +1,12 @@ [package] name = "pallet-shield" description = "FRAME pallet for opt-in, per-block ephemeral-key encrypted transactions, MEV-shielded." -authors = ["Subtensor Contributors "] +authors = ["Subtensor Contributors "] version = "0.0.1" license = "Unlicense" edition.workspace = true -homepage = "https://github.com/opentensor/subtensor" -repository = "https://github.com/opentensor/subtensor" +homepage = "https://github.com/RaoFoundation/subtensor" +repository = "https://github.com/RaoFoundation/subtensor" publish = false [package.metadata.docs.rs] diff --git a/pallets/shield/README.md b/pallets/shield/README.md index d3e67d82f5..6923099bae 100644 --- a/pallets/shield/README.md +++ b/pallets/shield/README.md @@ -54,5 +54,5 @@ This gives users a full 12-24s submission window (two block periods) instead of ## Dependencies -- [`stp-shield`](https://github.com/opentensor/polkadot-sdk) — shared types (`ShieldedTransaction`, `ShieldEncKey`, `InherentType`) +- [`stp-shield`](https://github.com/RaoFoundation/polkadot-sdk) — shared types (`ShieldedTransaction`, `ShieldEncKey`, `InherentType`) - `ml-kem` / `chacha20poly1305` — cryptographic primitives for in-WASM decryption diff --git a/pallets/subtensor/Cargo.toml b/pallets/subtensor/Cargo.toml index 27f86564d7..6cad5e5b42 100644 --- a/pallets/subtensor/Cargo.toml +++ b/pallets/subtensor/Cargo.toml @@ -7,7 +7,7 @@ homepage = "https://bittensor.com" edition.workspace = true license = "Unlicense" publish = false -repository = "https://github.com/opentensor/subtensor" +repository = "https://github.com/RaoFoundation/subtensor" [lints] workspace = true diff --git a/pallets/subtensor/rpc/Cargo.toml b/pallets/subtensor/rpc/Cargo.toml index 80fc0eb908..47ed4cc6f8 100644 --- a/pallets/subtensor/rpc/Cargo.toml +++ b/pallets/subtensor/rpc/Cargo.toml @@ -3,7 +3,7 @@ name = "subtensor-custom-rpc" version = "0.0.2" edition.workspace = true authors = ['Cameron Fairchild '] -repository = 'https://github.com/opentensor/subtensor' +repository = 'https://github.com/RaoFoundation/subtensor' description = "A pallet that adds custom RPC calls to subtensor" license = "MIT" publish = false diff --git a/pallets/subtensor/rpc/src/lib.rs b/pallets/subtensor/rpc/src/lib.rs index e00729151f..0c11869704 100644 --- a/pallets/subtensor/rpc/src/lib.rs +++ b/pallets/subtensor/rpc/src/lib.rs @@ -83,6 +83,9 @@ pub trait SubtensorCustomApi { ) -> RpcResult>; #[method(name = "subnetInfo_getSubnetState")] fn get_subnet_state(&self, netuid: NetUid, at: Option) -> RpcResult>; + + #[method(name = "subnetInfo_getBlockEmission")] + fn get_block_emission(&self, at: Option) -> RpcResult; #[method(name = "subnetInfo_getLockCost")] fn get_network_lock_cost(&self, at: Option) -> RpcResult; #[method(name = "subnetInfo_getSelectiveMetagraph")] @@ -467,6 +470,14 @@ where } } + fn get_block_emission(&self, at: Option<::Hash>) -> RpcResult { + let api = self.client.runtime_api(); + let at = at.unwrap_or_else(|| self.client.info().best_hash); + + api.get_block_emission(at) + .map_err(|e| Error::RuntimeError(format!("Unable to get block emission: {e:?}")).into()) + } + fn get_network_lock_cost(&self, at: Option<::Hash>) -> RpcResult { let api = self.client.runtime_api(); let at = at.unwrap_or_else(|| self.client.info().best_hash); diff --git a/pallets/subtensor/runtime-api/Cargo.toml b/pallets/subtensor/runtime-api/Cargo.toml index a83c3b3178..c295f3e5ca 100644 --- a/pallets/subtensor/runtime-api/Cargo.toml +++ b/pallets/subtensor/runtime-api/Cargo.toml @@ -3,7 +3,7 @@ name = "subtensor-custom-rpc-runtime-api" version = "0.0.2" edition.workspace = true authors = ['Cameron Fairchild '] -repository = 'https://github.com/opentensor/subtensor' +repository = 'https://github.com/RaoFoundation/subtensor' description = "A pallet that adds a custom runtime API to Subtensor" license = "MIT" publish = false diff --git a/pallets/subtensor/runtime-api/src/lib.rs b/pallets/subtensor/runtime-api/src/lib.rs index 2b95d59dc3..174cd7b553 100644 --- a/pallets/subtensor/runtime-api/src/lib.rs +++ b/pallets/subtensor/runtime-api/src/lib.rs @@ -61,6 +61,7 @@ sp_api::decl_runtime_apis! { fn get_subnet_to_prune() -> Option; fn get_subnet_account_id(netuid: NetUid) -> Option; fn get_next_epoch_start_block(netuid: NetUid) -> Option; + fn get_block_emission() -> TaoBalance; } pub trait StakeInfoRuntimeApi { @@ -80,6 +81,6 @@ sp_api::decl_runtime_apis! { pub trait ProxyFilterRuntimeApi { fn get_proxy_types() -> Vec; - fn get_proxy_filter(proxy_type: Option) -> Vec; + fn get_proxy_filters(proxy_types: Option>) -> Vec; } } diff --git a/pallets/subtensor/src/benchmarks.rs b/pallets/subtensor/src/benchmarks.rs index 809babaae9..d0c7c46b52 100644 --- a/pallets/subtensor/src/benchmarks.rs +++ b/pallets/subtensor/src/benchmarks.rs @@ -1536,6 +1536,7 @@ mod pallet_benchmarks { let logo_url = vec![]; let add = vec![]; + Subtensor::::init_new_network(netuid, 1); SubnetOwner::::insert(netuid, coldkey.clone()); SubtokenEnabled::::insert(netuid, true); @@ -2092,6 +2093,24 @@ mod pallet_benchmarks { ); } + #[benchmark] + fn dissolve_network() { + let netuid = NetUid::from(1); + let tempo: u16 = 1; + let coldkey: T::AccountId = account("Owner", 0, 1); + + // Initialize network + Subtensor::::init_new_network(netuid, tempo); + + // Set network owner + SubnetOwner::::insert(netuid, coldkey.clone()); + + #[extrinsic_call] + _(RawOrigin::Root, coldkey.clone(), netuid); + + assert!(DissolveCleanupQueue::::get().contains(&netuid)); + } + #[benchmark] fn set_pending_childkey_cooldown() { let cooldown: u64 = 7200; @@ -2277,6 +2296,10 @@ mod pallet_benchmarks { AssociatedEvmAddress::::get(netuid, uid), Some((evm_key, block_number)) ); + assert_eq!( + AssociatedUidsByEvmAddress::::get(netuid, evm_key).into_inner(), + vec![(uid, block_number)] + ); } #[benchmark] diff --git a/pallets/subtensor/src/coinbase/block_emission.rs b/pallets/subtensor/src/coinbase/block_emission.rs index d4adcddbee..96e44dbcc4 100644 --- a/pallets/subtensor/src/coinbase/block_emission.rs +++ b/pallets/subtensor/src/coinbase/block_emission.rs @@ -77,9 +77,6 @@ impl Pallet { .saturating_mul(I96F32::saturating_from_num(DefaultBlockEmission::::get())); // Convert to u64 let block_emission_u64: u64 = block_emission.saturating_to_num::(); - if BlockEmission::::get() != block_emission_u64 { - BlockEmission::::put(block_emission_u64); - } Ok(block_emission_u64) } } diff --git a/pallets/subtensor/src/coinbase/root.rs b/pallets/subtensor/src/coinbase/root.rs index b4db388621..02df14c6b5 100644 --- a/pallets/subtensor/src/coinbase/root.rs +++ b/pallets/subtensor/src/coinbase/root.rs @@ -16,12 +16,9 @@ // DEALINGS IN THE SOFTWARE. use super::*; -use crate::CommitmentsInterface; use safe_math::*; use substrate_fixed::types::{I64F64, U64F64}; -use subtensor_runtime_common::{AlphaBalance, NetUid, NetUidStorageIndex, TaoBalance, Token}; -use subtensor_swap_interface::SwapHandler; - +use subtensor_runtime_common::{AlphaBalance, NetUid, TaoBalance, Token}; impl Pallet { /// Fetches the total count of root network validators /// @@ -190,296 +187,6 @@ impl Pallet { Ok(()) } - /// Facilitates the removal of a user's subnetwork. - /// - /// # Args: - /// * 'origin': ('T::RuntimeOrigin'): The calling origin. Must be signed. - /// * 'netuid': ('u16'): The unique identifier of the network to be removed. - /// - /// # Event: - /// * 'NetworkRemoved': Emitted when a network is successfully removed. - /// - /// # Raises: - /// * 'MechanismDoesNotExist': If the specified network does not exist. - /// * 'NotSubnetOwner': If the caller does not own the specified subnet. - /// - pub fn do_dissolve_network(netuid: NetUid) -> dispatch::DispatchResult { - // --- The network exists? - ensure!( - Self::if_subnet_exist(netuid) && netuid != NetUid::ROOT, - Error::::SubnetNotExists - ); - - Self::finalize_all_subnet_root_dividends(netuid); - - // --- Perform the cleanup before removing the network. - Self::destroy_alpha_in_out_stakes(netuid)?; - T::SwapInterface::clear_protocol_liquidity(netuid)?; - T::CommitmentsInterface::purge_netuid(netuid); - - // --- Remove the network - Self::remove_network(netuid); - - // --- Emit the NetworkRemoved event - log::info!("NetworkRemoved( netuid:{netuid:?} )"); - Self::deposit_event(Event::NetworkRemoved(netuid)); - - Ok(()) - } - - pub fn remove_network(netuid: NetUid) { - // --- 1. Get the owner and remove from SubnetOwner. - let owner_coldkey: T::AccountId = SubnetOwner::::get(netuid); - SubnetOwner::::remove(netuid); - - // --- 2. Remove network count. - SubnetworkN::::remove(netuid); - - // --- 3. Remove netuid from added networks. - NetworksAdded::::remove(netuid); - - // --- 4. Decrement the network counter. - TotalNetworks::::mutate(|n: &mut u16| *n = n.saturating_sub(1)); - - // --- 5. Remove various network-related storages. - NetworkRegisteredAt::::remove(netuid); - - // --- 6. 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); - - // --- 8. Iterate over stored weights and fill the matrix. - for (uid_i, weights_i) in Weights::::iter_prefix(NetUidStorageIndex::ROOT) { - // Create a new vector to hold modified weights. - let mut modified_weights = weights_i.clone(); - for (subnet_id, weight) in modified_weights.iter_mut() { - // If the root network had a weight pointing to this netuid, set it to 0 - if subnet_id == &u16::from(netuid) { - *weight = 0; - } - } - Weights::::insert(NetUidStorageIndex::ROOT, uid_i, modified_weights); - } - - // --- 9. Remove various network-related parameters. - Active::::remove(netuid); - Emission::::remove(netuid); - - Consensus::::remove(netuid); - Dividends::::remove(netuid); - ValidatorPermit::::remove(netuid); - ValidatorTrust::::remove(netuid); - - for (_uid, key) in keys { - IsNetworkMember::::remove(key, netuid); - } - - // --- 10. Erase network parameters. - Tempo::::remove(netuid); - Kappa::::remove(netuid); - Difficulty::::remove(netuid); - MaxAllowedUids::::remove(netuid); - ImmunityPeriod::::remove(netuid); - ActivityCutoff::::remove(netuid); - ActivityCutoffFactorMilli::::remove(netuid); - LastEpochBlock::::remove(netuid); - PendingEpochAt::::remove(netuid); - SubnetEpochIndex::::remove(netuid); - MinAllowedWeights::::remove(netuid); - RegistrationsThisInterval::::remove(netuid); - POWRegistrationsThisInterval::::remove(netuid); - BurnRegistrationsThisInterval::::remove(netuid); - - // --- 11. AMM / price / accounting. - // SubnetTAO, SubnetAlpha{In,InProvided,Out} are already cleared during dissolve/destroy. - SubnetAlphaInEmission::::remove(netuid); - SubnetAlphaOutEmission::::remove(netuid); - SubnetTaoInEmission::::remove(netuid); - SubnetVolume::::remove(netuid); - SubnetMovingPrice::::remove(netuid); - SubnetTaoFlow::::remove(netuid); - SubnetEmaTaoFlow::::remove(netuid); - SubnetProtocolFlow::::remove(netuid); - SubnetEmaProtocolFlow::::remove(netuid); - SubnetExcessTao::::remove(netuid); - SubnetRootSellTao::::remove(netuid); - - // --- 13. Token / mechanism / registration toggles. - TokenSymbol::::remove(netuid); - SubnetMechanism::::remove(netuid); - SubnetOwnerHotkey::::remove(netuid); - NetworkRegistrationAllowed::::remove(netuid); - NetworkPowRegistrationAllowed::::remove(netuid); - - // --- 14. Locks & toggles. - TransferToggle::::remove(netuid); - SubnetLocked::::remove(netuid); - LargestLocked::::remove(netuid); - - // --- 15. Mechanism step / emissions bookkeeping. - FirstEmissionBlockNumber::::remove(netuid); - PendingValidatorEmission::::remove(netuid); - PendingServerEmission::::remove(netuid); - PendingRootAlphaDivs::::remove(netuid); - PendingOwnerCut::::remove(netuid); - MinerBurned::::remove(netuid); - BlocksSinceLastStep::::remove(netuid); - LastMechansimStepBlock::::remove(netuid); - LastAdjustmentBlock::::remove(netuid); - - // --- 16. Serving / rho / curves, and other per-net controls. - ServingRateLimit::::remove(netuid); - Rho::::remove(netuid); - AlphaSigmoidSteepness::::remove(netuid); - - MaxAllowedValidators::::remove(netuid); - BondsMovingAverage::::remove(netuid); - BondsPenalty::::remove(netuid); - BondsResetOn::::remove(netuid); - WeightsSetRateLimit::::remove(netuid); - ValidatorPruneLen::::remove(netuid); - ScalingLawPower::::remove(netuid); - TargetRegistrationsPerInterval::::remove(netuid); - CommitRevealWeightsEnabled::::remove(netuid); - - BurnHalfLife::::remove(netuid); - BurnIncreaseMult::::remove(netuid); - - Burn::::remove(netuid); - MinBurn::::remove(netuid); - MaxBurn::::remove(netuid); - MinDifficulty::::remove(netuid); - MaxDifficulty::::remove(netuid); - RegistrationsThisBlock::::remove(netuid); - EMAPriceHalvingBlocks::::remove(netuid); - RAORecycledForRegistration::::remove(netuid); - MaxRegistrationsPerBlock::::remove(netuid); - WeightsVersionKey::::remove(netuid); - - // --- 17. Subtoken / feature flags. - LiquidAlphaOn::::remove(netuid); - Yuma3On::::remove(netuid); - AlphaValues::::remove(netuid); - SubtokenEnabled::::remove(netuid); - OwnerCutAutoLockEnabled::::remove(netuid); - ImmuneOwnerUidsLimit::::remove(netuid); - - // --- 18. Consensus aux vectors. - StakeWeight::::remove(netuid); - LoadedEmission::::remove(netuid); - - // --- 19. DMAPs where netuid is the FIRST key: clear by prefix. - let _ = BlockAtRegistration::::clear_prefix(netuid, u32::MAX, None); - let _ = Axons::::clear_prefix(netuid, u32::MAX, None); - let _ = NeuronCertificates::::clear_prefix(netuid, u32::MAX, None); - let _ = Prometheus::::clear_prefix(netuid, u32::MAX, None); - let _ = AlphaDividendsPerSubnet::::clear_prefix(netuid, u32::MAX, None); - let _ = PendingChildKeys::::clear_prefix(netuid, u32::MAX, None); - let _ = AssociatedEvmAddress::::clear_prefix(netuid, u32::MAX, None); - - // Commit-reveal / weights commits (all per-net prefixes): - let mechanisms: u8 = MechanismCountCurrent::::get(netuid).into(); - for subid in 0..mechanisms { - let netuid_index = Self::get_mechanism_storage_index(netuid, subid.into()); - LastUpdate::::remove(netuid_index); - Incentive::::remove(netuid_index); - let _ = WeightCommits::::clear_prefix(netuid_index, u32::MAX, None); - let _ = TimelockedWeightCommits::::clear_prefix(netuid_index, u32::MAX, None); - let _ = CRV3WeightCommits::::clear_prefix(netuid_index, u32::MAX, None); - let _ = CRV3WeightCommitsV2::::clear_prefix(netuid_index, u32::MAX, None); - let _ = Bonds::::clear_prefix(netuid_index, u32::MAX, None); - let _ = Weights::::clear_prefix(netuid_index, u32::MAX, None); - } - RevealPeriodEpochs::::remove(netuid); - MechanismCountCurrent::::remove(netuid); - MechanismEmissionSplit::::remove(netuid); - - // Last hotkey swap (DMAP where netuid is FIRST key → easy) - let _ = LastHotkeySwapOnNetuid::::clear_prefix(netuid, u32::MAX, None); - - // --- 20. Identity maps across versions (netuid-scoped). - if SubnetIdentitiesV3::::contains_key(netuid) { - SubnetIdentitiesV3::::remove(netuid); - Self::deposit_event(Event::SubnetIdentityRemoved(netuid)); - } - - // --- 21. DMAP / NMAP where netuid is NOT the first key → iterate & remove. - - // ChildkeyTake: (hot, netuid) → u16 - { - let to_rm: sp_std::vec::Vec = ChildkeyTake::::iter() - .filter_map(|(hot, n, _)| if n == netuid { Some(hot) } else { None }) - .collect(); - for hot in to_rm { - ChildkeyTake::::remove(&hot, netuid); - } - } - // ChildKeys: (parent, netuid) → Vec<...> - { - let to_rm: sp_std::vec::Vec = ChildKeys::::iter() - .filter_map(|(parent, n, _)| if n == netuid { Some(parent) } else { None }) - .collect(); - for parent in to_rm { - ChildKeys::::remove(&parent, netuid); - } - } - // ParentKeys: (child, netuid) → Vec<...> - { - let to_rm: sp_std::vec::Vec = ParentKeys::::iter() - .filter_map(|(child, n, _)| if n == netuid { Some(child) } else { None }) - .collect(); - for child in to_rm { - ParentKeys::::remove(&child, netuid); - } - } - // LastHotkeyEmissionOnNetuid: (hot, netuid) → α - { - let to_rm: sp_std::vec::Vec = LastHotkeyEmissionOnNetuid::::iter() - .filter_map(|(hot, n, _)| if n == netuid { Some(hot) } else { None }) - .collect(); - for hot in to_rm { - LastHotkeyEmissionOnNetuid::::remove(&hot, netuid); - } - } - // TotalHotkeyAlphaLastEpoch: (hot, netuid) → ... - // (TotalHotkeyAlpha and TotalHotkeyShares were already removed during dissolve.) - { - let to_rm_alpha_last: sp_std::vec::Vec = - TotalHotkeyAlphaLastEpoch::::iter() - .filter_map(|(hot, n, _)| if n == netuid { Some(hot) } else { None }) - .collect(); - for hot in to_rm_alpha_last { - TotalHotkeyAlphaLastEpoch::::remove(&hot, netuid); - } - } - // TransactionKeyLastBlock NMAP: (hot, netuid, name) → u64 - { - let to_rm: sp_std::vec::Vec<(T::AccountId, u16)> = TransactionKeyLastBlock::::iter() - .filter_map( - |((hot, n, name), _)| if n == netuid { Some((hot, name)) } else { None }, - ) - .collect(); - for (hot, name) in to_rm { - TransactionKeyLastBlock::::remove((hot, netuid, name)); - } - } - // --- 22. Subnet leasing: remove mapping and any lease-scoped state linked to this netuid. - if let Some(lease_id) = SubnetUidToLeaseId::::take(netuid) { - SubnetLeases::::remove(lease_id); - let _ = SubnetLeaseShares::::clear_prefix(lease_id, u32::MAX, None); - AccumulatedLeaseDividends::::remove(lease_id); - } - - // --- 23: Locks cleanup - Self::destroy_lock_maps(netuid); - - // --- Final removal logging. - log::debug!( - "remove_network: netuid={netuid}, owner={owner_coldkey:?} removed successfully" - ); - } - #[allow(clippy::arithmetic_side_effects)] /// This function calculates the lock cost for a network based on the last lock amount, minimum lock cost, last lock block, and current block. /// The lock cost is calculated using the formula: diff --git a/pallets/subtensor/src/coinbase/run_coinbase.rs b/pallets/subtensor/src/coinbase/run_coinbase.rs index d7c75964b2..84fdac3012 100644 --- a/pallets/subtensor/src/coinbase/run_coinbase.rs +++ b/pallets/subtensor/src/coinbase/run_coinbase.rs @@ -411,9 +411,8 @@ impl Pallet { ); epochs_run_this_block = epochs_run_this_block.saturating_add(1); - // Reserved for potential future enhancements. - // Ownership update logic based on conviction is currently inactive by design. - // Self::change_subnet_owner_if_needed(netuid); + // Change subnet owner based on conviction. + Self::change_subnet_owner_if_needed(netuid); } else { // Schedule advances below; execution skipped. Pending emissions accumulate // and will be drained by the next successful epoch. diff --git a/pallets/subtensor/src/coinbase/subnet_emissions.rs b/pallets/subtensor/src/coinbase/subnet_emissions.rs index f378e3e930..b82bb2b0fb 100644 --- a/pallets/subtensor/src/coinbase/subnet_emissions.rs +++ b/pallets/subtensor/src/coinbase/subnet_emissions.rs @@ -36,7 +36,7 @@ impl Pallet { block_emission: U96F32, ) -> BTreeMap { // Disabled subnets get zero TAO-side emission, redistributed to enabled subnets. - // They stay in the map so the normal alpha_out/root-prop path still runs. + // They stay in the map so the normal alpha_out path still runs. let shares = Self::get_shares(subnets_to_emit_to); log::debug!("Subnet emission shares = {shares:?}"); @@ -354,11 +354,9 @@ impl Pallet { pub(crate) fn get_shares(subnets_to_emit_to: &[NetUid]) -> BTreeMap { let price_shares = Self::get_shares_price_ema(subnets_to_emit_to); - // Weight each subnet's price share by root_proportion * (1 - miner_burned), then - // renormalize. The effective emission is therefore proportional to - // root_proportion_i * price_i * (1 - miner_burned_i). - // - root_proportion shrinks as a subnet's alpha issuance grows, so emission is - // reallocated away from older subnets toward newer ones (easier entrance). + // Weight each subnet's price share by (1 - miner_burned), then + // renormalize. The effective emission is proportional to + // price_i * (1 - miner_burned_i). // - (1 - miner_burned) reallocates away from subnets that withhold miner emission. let zero = U64F64::saturating_from_num(0); let one = U64F64::saturating_from_num(1); @@ -366,8 +364,8 @@ impl Pallet { .iter() .map(|(netuid, share)| { let burned = U64F64::saturating_from_num(MinerBurned::::get(netuid)).min(one); - let root_prop = U64F64::saturating_from_num(Self::root_proportion(*netuid)); - let factor = root_prop.saturating_mul(one.saturating_sub(burned)); + let factor = one.saturating_sub(burned); + (*netuid, share.saturating_mul(factor)) }) .collect(); diff --git a/pallets/subtensor/src/coinbase/tao.rs b/pallets/subtensor/src/coinbase/tao.rs index 0dee496c3b..833cc493fb 100644 --- a/pallets/subtensor/src/coinbase/tao.rs +++ b/pallets/subtensor/src/coinbase/tao.rs @@ -5,7 +5,7 @@ /// - Access to subnet TAO reserves /// use frame_support::traits::{ - Imbalance, + Imbalance, LockableCurrency, WithdrawReasons, fungible::Mutate, tokens::{ Fortitude, Precision, Preservation, @@ -25,6 +25,9 @@ pub type CreditOf = Credit<::AccountId, Pallet { /// Returns Subnet TAO reserve using SubnetTAO map. /// Do not use subnet account balance because it may also contain @@ -305,4 +308,46 @@ impl Pallet { pub fn get_total_issuance() -> TaoBalance { TotalIssuance::::get() } + + fn get_network_registration_lock_identifier(lock_id: u32) -> [u8; 8] { + let mut id: frame_support::traits::LockIdentifier = [0; 8]; + id[..4].copy_from_slice(&TAO_REGISTRATION_LOCK_PREFIX); + id[4..8].copy_from_slice(&lock_id.to_le_bytes()); + id + } + + pub fn lock_network_registration_cost( + coldkey: &T::AccountId, + amount: BalanceOf, + lock_id: u32, + ) -> DispatchResult { + ensure!( + Self::can_remove_balance_from_coldkey_account(coldkey, amount), + Error::::InsufficientBalance + ); + + let identifier = Self::get_network_registration_lock_identifier(lock_id); + + <::Currency as LockableCurrency<::AccountId>>::set_lock( + identifier, + coldkey, + amount, + WithdrawReasons::all(), + ); + + Ok(()) + } + + pub fn unlock_network_registration_cost( + coldkey: &T::AccountId, + lock_id: u32, + ) -> DispatchResult { + let identifier = Self::get_network_registration_lock_identifier(lock_id); + <::Currency as LockableCurrency<::AccountId>>::remove_lock( + identifier, + coldkey, + ); + + Ok(()) + } } diff --git a/pallets/subtensor/src/extensions/subtensor.rs b/pallets/subtensor/src/extensions/subtensor.rs index ea91c87c6e..7899ed855e 100644 --- a/pallets/subtensor/src/extensions/subtensor.rs +++ b/pallets/subtensor/src/extensions/subtensor.rs @@ -1,6 +1,6 @@ use crate::{ Call, CheckColdkeySwap, CheckDelegateTake, CheckEvmKeyAssociation, CheckRateLimits, - CheckServingEndpoints, CheckWeights, Config, Error, + CheckServingEndpoints, CheckWeights, Config, Error, guards::applicable_call, }; use codec::{Decode, DecodeWithMemTracking, Encode}; use frame_support::{ @@ -89,15 +89,23 @@ impl SubtensorTransactionExtension { CheckColdkeySwap::::check(who, call)?; - let Some(call) = call.is_sub_type() else { - return Ok(()); - }; + if let Some(call) = applicable_call(call, CheckWeights::::applies_to) { + CheckWeights::::check(who, call)?; + } + if let Some(call) = applicable_call(call, CheckRateLimits::::applies_to) { + CheckRateLimits::::check(who, call)?; + } + if let Some(call) = applicable_call(call, CheckDelegateTake::::applies_to) { + CheckDelegateTake::::check(who, call)?; + } + if let Some(call) = applicable_call(call, CheckServingEndpoints::::applies_to) { + CheckServingEndpoints::::check(who, call)?; + } + if let Some(call) = applicable_call(call, CheckEvmKeyAssociation::::applies_to) { + CheckEvmKeyAssociation::::check(who, call)?; + } - CheckWeights::::check(who, call)?; - CheckRateLimits::::check(who, call)?; - CheckDelegateTake::::check(who, call)?; - CheckServingEndpoints::::check(who, call)?; - CheckEvmKeyAssociation::::check(who, call) + Ok(()) } } diff --git a/pallets/subtensor/src/guards/check_coldkey_swap.rs b/pallets/subtensor/src/guards/check_coldkey_swap.rs index 5f124be219..907fed1d3b 100644 --- a/pallets/subtensor/src/guards/check_coldkey_swap.rs +++ b/pallets/subtensor/src/guards/check_coldkey_swap.rs @@ -1,3 +1,4 @@ +use super::{CallOf, DispatchableOriginOf}; use crate::weights::WeightInfo; use crate::{Call, ColdkeySwapAnnouncements, ColdkeySwapDisputes, Config, Error}; use frame_support::{ @@ -8,9 +9,6 @@ use frame_support::{ use sp_runtime::traits::Dispatchable; use sp_std::marker::PhantomData; -type CallOf = ::RuntimeCall; -type DispatchableOriginOf = as Dispatchable>::RuntimeOrigin; - /// Dispatch extension that blocks most calls when a coldkey swap is active. /// /// When a coldkey swap has been announced for the signing account: @@ -96,9 +94,14 @@ where #[allow(clippy::expect_used, clippy::unwrap_used)] mod tests { use super::CheckColdkeySwap; - use crate::{ColdkeySwapAnnouncements, ColdkeySwapDisputes, Error, tests::mock::*}; + use crate::{ + ColdkeySwapAnnouncements, ColdkeySwapDisputes, Error, tests::mock::*, + weights::WeightInfo as _, + }; use frame_support::{ - BoundedVec, assert_ok, dispatch::DispatchResultWithPostInfo, traits::ExtendedDispatchable, + BoundedVec, assert_ok, + dispatch::{DispatchExtension, DispatchResultWithPostInfo}, + traits::ExtendedDispatchable, }; use frame_system::Call as SystemCall; use pallet_subtensor_proxy::Call as ProxyCall; @@ -176,6 +179,18 @@ mod tests { ) } + #[test] + fn weight_charges_all_calls_because_swap_state_can_block_any_signed_call() { + let expected = ::WeightInfo::check_coldkey_swap_extension(); + + for call in forbidden_calls().into_iter().chain(authorized_calls()) { + assert_eq!( + as DispatchExtension>::weight(&call), + expected + ); + } + } + #[test] fn no_active_swap_allows_calls() { new_test_ext(1).execute_with(|| { diff --git a/pallets/subtensor/src/guards/check_delegate_take.rs b/pallets/subtensor/src/guards/check_delegate_take.rs index c9f54d4cb5..c80d969afc 100644 --- a/pallets/subtensor/src/guards/check_delegate_take.rs +++ b/pallets/subtensor/src/guards/check_delegate_take.rs @@ -1,3 +1,4 @@ +use super::{CallOf, DispatchableOriginOf, applicable_call}; use crate::weights::WeightInfo; use crate::{Call, Config, Error, Pallet}; use frame_support::{ @@ -8,9 +9,6 @@ use frame_support::{ use sp_runtime::traits::Dispatchable; use sp_std::marker::PhantomData; -type CallOf = ::RuntimeCall; -type DispatchableOriginOf = as Dispatchable>::RuntimeOrigin; - /// Dispatch extension for delegate-take bounds and ownership preconditions. /// /// Signed increase/decrease take calls are checked before dispatch; unrelated @@ -18,6 +16,13 @@ type DispatchableOriginOf = as Dispatchable>::RuntimeOrigin; pub struct CheckDelegateTake(PhantomData); impl CheckDelegateTake { + pub(crate) fn applies_to(call: &Call) -> bool { + matches!( + call, + Call::increase_take { .. } | Call::decrease_take { .. } + ) + } + pub fn check(who: &T::AccountId, call: &Call) -> Result<(), Error> { match call { Call::increase_take { hotkey, take } | Call::decrease_take { hotkey, take } => { @@ -42,8 +47,10 @@ where { type Pre = (); - fn weight(_call: &CallOf) -> Weight { - ::WeightInfo::check_delegate_take_extension() + fn weight(call: &CallOf) -> Weight { + applicable_call(call, Self::applies_to) + .map(|_| ::WeightInfo::check_delegate_take_extension()) + .unwrap_or(Weight::zero()) } fn pre_dispatch( @@ -54,7 +61,7 @@ where return Ok(()); }; - let Some(call) = call.is_sub_type() else { + let Some(call) = applicable_call(call, Self::applies_to) else { return Ok(()); }; @@ -68,7 +75,10 @@ mod tests { use super::*; use crate::{Error, tests::mock::*}; use frame_support::{ - assert_ok, dispatch::DispatchResultWithPostInfo, traits::ExtendedDispatchable, + assert_ok, + dispatch::{DispatchExtension, DispatchResultWithPostInfo}, + traits::ExtendedDispatchable, + weights::Weight, }; use sp_core::U256; use sp_runtime::DispatchError; @@ -91,6 +101,39 @@ mod tests { result.unwrap_err().error } + fn add_stake_call() -> RuntimeCall { + RuntimeCall::SubtensorModule(SubtensorCall::add_stake { + hotkey: U256::from(1), + netuid: 1u16.into(), + amount_staked: 1_000u64.into(), + }) + } + + #[test] + fn weight_only_charges_delegate_take_calls() { + let expected = ::WeightInfo::check_delegate_take_extension(); + + for call in [ + RuntimeCall::System(frame_system::Call::remark { remark: vec![] }), + add_stake_call(), + ] { + assert_eq!( + as DispatchExtension>::weight(&call), + Weight::zero() + ); + } + + for call in [ + increase_take_call(U256::from(1), 0), + decrease_take_call(U256::from(1), 0), + ] { + assert_eq!( + as DispatchExtension>::weight(&call), + expected + ); + } + } + #[test] fn accepts_owner_with_valid_take() { new_test_ext(0).execute_with(|| { diff --git a/pallets/subtensor/src/guards/check_evm_key_association.rs b/pallets/subtensor/src/guards/check_evm_key_association.rs index d7e2847e99..d9b69e1a7d 100644 --- a/pallets/subtensor/src/guards/check_evm_key_association.rs +++ b/pallets/subtensor/src/guards/check_evm_key_association.rs @@ -1,3 +1,4 @@ +use super::{CallOf, DispatchableOriginOf, applicable_call}; use crate::weights::WeightInfo; use crate::{Call, Config, Error, Pallet}; use frame_support::{ @@ -8,9 +9,6 @@ use frame_support::{ use sp_runtime::traits::Dispatchable; use sp_std::marker::PhantomData; -type CallOf = ::RuntimeCall; -type DispatchableOriginOf = as Dispatchable>::RuntimeOrigin; - /// Dispatch extension for EVM-key association preconditions. /// /// Signed EVM-key association calls are checked for subnet registration and @@ -18,6 +16,10 @@ type DispatchableOriginOf = as Dispatchable>::RuntimeOrigin; pub struct CheckEvmKeyAssociation(PhantomData); impl CheckEvmKeyAssociation { + pub(crate) fn applies_to(call: &Call) -> bool { + matches!(call, Call::associate_evm_key { .. }) + } + pub fn check(who: &T::AccountId, call: &Call) -> Result<(), Error> { match call { Call::associate_evm_key { netuid, .. } => { @@ -40,8 +42,10 @@ where { type Pre = (); - fn weight(_call: &CallOf) -> Weight { - ::WeightInfo::check_evm_key_association_extension() + fn weight(call: &CallOf) -> Weight { + applicable_call(call, Self::applies_to) + .map(|_| ::WeightInfo::check_evm_key_association_extension()) + .unwrap_or(Weight::zero()) } fn pre_dispatch( @@ -52,7 +56,7 @@ where return Ok(()); }; - let Some(call) = call.is_sub_type() else { + let Some(call) = applicable_call(call, Self::applies_to) else { return Ok(()); }; @@ -64,10 +68,13 @@ where #[allow(clippy::unwrap_used, clippy::arithmetic_side_effects)] mod tests { use super::CheckEvmKeyAssociation; - use crate::{AssociatedEvmAddress, Error, tests::mock::*}; + use crate::{AssociatedEvmAddress, Error, tests::mock::*, weights::WeightInfo as _}; use codec::Encode; use frame_support::{ - assert_ok, dispatch::DispatchResultWithPostInfo, traits::ExtendedDispatchable, + assert_ok, + dispatch::{DispatchExtension, DispatchResultWithPostInfo}, + traits::ExtendedDispatchable, + weights::Weight, }; use frame_system::Call as SystemCall; use sp_core::{H160, Pair, U256, ecdsa, keccak_256}; @@ -139,6 +146,37 @@ mod tests { ) } + fn add_stake_call() -> RuntimeCall { + RuntimeCall::SubtensorModule(SubtensorCall::add_stake { + hotkey: U256::from(1), + netuid: 1u16.into(), + amount_staked: 1_000u64.into(), + }) + } + + #[test] + fn weight_only_charges_evm_key_association_calls() { + let netuid = NetUid::from(1); + let expected = ::WeightInfo::check_evm_key_association_extension(); + + for call in [ + RuntimeCall::System(SystemCall::remark { remark: vec![] }), + add_stake_call(), + ] { + assert_eq!( + as DispatchExtension>::weight(&call), + Weight::zero() + ); + } + + assert_eq!( + as DispatchExtension>::weight( + &dummy_associate_call(netuid) + ), + expected + ); + } + #[test] fn unrelated_calls_pass_through() { new_test_ext(0).execute_with(|| { diff --git a/pallets/subtensor/src/guards/check_rate_limits.rs b/pallets/subtensor/src/guards/check_rate_limits.rs index d2c021dd5d..e12c9d064b 100644 --- a/pallets/subtensor/src/guards/check_rate_limits.rs +++ b/pallets/subtensor/src/guards/check_rate_limits.rs @@ -1,3 +1,4 @@ +use super::{CallOf, DispatchableOriginOf, applicable_call}; use crate::weights::WeightInfo; use crate::{Call, Config, Error, Pallet, TransactionType}; use frame_support::{ @@ -9,9 +10,6 @@ use sp_runtime::traits::Dispatchable; use sp_std::marker::PhantomData; use subtensor_runtime_common::{NetUid, NetUidStorageIndex}; -type CallOf = ::RuntimeCall; -type DispatchableOriginOf = as Dispatchable>::RuntimeOrigin; - /// Dispatch extension for rate-limit checks that are safe to reject before dispatch. /// /// Signed weight and network-registration calls are checked before dispatch; @@ -19,6 +17,17 @@ type DispatchableOriginOf = as Dispatchable>::RuntimeOrigin; pub struct CheckRateLimits(PhantomData); impl CheckRateLimits { + pub(crate) fn applies_to(call: &Call) -> bool { + matches!( + call, + Call::commit_weights { .. } + | Call::commit_mechanism_weights { .. } + | Call::set_weights { .. } + | Call::set_mechanism_weights { .. } + | Call::register_network { .. } + ) + } + fn check_weights_rate_limit( who: &T::AccountId, netuid: NetUid, @@ -89,8 +98,10 @@ where { type Pre = (); - fn weight(_call: &CallOf) -> Weight { - ::WeightInfo::check_rate_limits_extension() + fn weight(call: &CallOf) -> Weight { + applicable_call(call, Self::applies_to) + .map(|_| ::WeightInfo::check_rate_limits_extension()) + .unwrap_or(Weight::zero()) } fn pre_dispatch( @@ -101,7 +112,7 @@ where return Ok(()); }; - let Some(call) = call.is_sub_type() else { + let Some(call) = applicable_call(call, Self::applies_to) else { return Ok(()); }; @@ -113,9 +124,12 @@ where #[allow(clippy::unwrap_used)] mod tests { use super::CheckRateLimits; - use crate::{Error, tests::mock::*}; + use crate::{Error, tests::mock::*, weights::WeightInfo as _}; use frame_support::{ - assert_ok, dispatch::DispatchResultWithPostInfo, traits::ExtendedDispatchable, + assert_ok, + dispatch::{DispatchExtension, DispatchResultWithPostInfo}, + traits::ExtendedDispatchable, + weights::Weight, }; use frame_system::Call as SystemCall; use sp_core::U256; @@ -155,6 +169,57 @@ mod tests { add_balance_to_coldkey_account(&coldkey, amount); } + fn add_stake_call() -> RuntimeCall { + RuntimeCall::SubtensorModule(SubtensorCall::add_stake { + hotkey: U256::from(1), + netuid: 1u16.into(), + amount_staked: 1_000u64.into(), + }) + } + + #[test] + fn weight_only_charges_rate_limited_calls() { + let netuid = NetUid::from(1); + let expected = ::WeightInfo::check_rate_limits_extension(); + let charged_calls = [ + RuntimeCall::SubtensorModule(SubtensorCall::commit_weights { + netuid, + commit_hash: sp_core::H256::zero(), + }), + RuntimeCall::SubtensorModule(SubtensorCall::commit_mechanism_weights { + netuid, + mecid: MechId::MAIN, + commit_hash: sp_core::H256::zero(), + }), + set_weights_call(netuid, 0), + RuntimeCall::SubtensorModule(SubtensorCall::set_mechanism_weights { + netuid, + mecid: MechId::MAIN, + dests: vec![0], + weights: vec![1], + version_key: 0, + }), + register_network_call(U256::from(1)), + ]; + + for call in [ + RuntimeCall::System(SystemCall::remark { remark: vec![] }), + add_stake_call(), + ] { + assert_eq!( + as DispatchExtension>::weight(&call), + Weight::zero() + ); + } + + for call in charged_calls { + assert_eq!( + as DispatchExtension>::weight(&call), + expected + ); + } + } + #[test] fn unrelated_calls_pass_through() { new_test_ext(0).execute_with(|| { diff --git a/pallets/subtensor/src/guards/check_serving_endpoints.rs b/pallets/subtensor/src/guards/check_serving_endpoints.rs index 46304d337f..f8b2da64ed 100644 --- a/pallets/subtensor/src/guards/check_serving_endpoints.rs +++ b/pallets/subtensor/src/guards/check_serving_endpoints.rs @@ -1,3 +1,4 @@ +use super::{CallOf, DispatchableOriginOf, applicable_call}; use crate::weights::WeightInfo; use crate::{Call, Config, Error, Pallet}; use frame_support::{ @@ -8,9 +9,6 @@ use frame_support::{ use sp_runtime::traits::Dispatchable; use sp_std::marker::PhantomData; -type CallOf = ::RuntimeCall; -type DispatchableOriginOf = as Dispatchable>::RuntimeOrigin; - /// Dispatch extension for axon/prometheus endpoint validation. /// /// Signed serving calls are checked before dispatch; unrelated calls and @@ -18,6 +16,13 @@ type DispatchableOriginOf = as Dispatchable>::RuntimeOrigin; pub struct CheckServingEndpoints(PhantomData); impl CheckServingEndpoints { + pub(crate) fn applies_to(call: &Call) -> bool { + matches!( + call, + Call::serve_axon { .. } | Call::serve_axon_tls { .. } | Call::serve_prometheus { .. } + ) + } + pub fn check(who: &T::AccountId, call: &Call) -> Result<(), Error> { match call { Call::serve_axon { @@ -74,8 +79,10 @@ where { type Pre = (); - fn weight(_call: &CallOf) -> Weight { - ::WeightInfo::check_serving_endpoints_extension() + fn weight(call: &CallOf) -> Weight { + applicable_call(call, Self::applies_to) + .map(|_| ::WeightInfo::check_serving_endpoints_extension()) + .unwrap_or(Weight::zero()) } fn pre_dispatch( @@ -86,7 +93,7 @@ where return Ok(()); }; - let Some(call) = call.is_sub_type() else { + let Some(call) = applicable_call(call, Self::applies_to) else { return Ok(()); }; @@ -98,9 +105,12 @@ where #[allow(clippy::unwrap_used)] mod tests { use super::CheckServingEndpoints; - use crate::{Error, tests::mock::*}; + use crate::{Error, tests::mock::*, weights::WeightInfo as _}; use frame_support::{ - assert_ok, dispatch::DispatchResultWithPostInfo, traits::ExtendedDispatchable, + assert_ok, + dispatch::{DispatchExtension, DispatchResultWithPostInfo}, + traits::ExtendedDispatchable, + weights::Weight, }; use frame_system::Call as SystemCall; use sp_core::U256; @@ -160,6 +170,41 @@ mod tests { register_ok_neuron(netuid, hotkey, coldkey, 0); } + fn add_stake_call() -> RuntimeCall { + RuntimeCall::SubtensorModule(SubtensorCall::add_stake { + hotkey: U256::from(1), + netuid: 1u16.into(), + amount_staked: 1_000u64.into(), + }) + } + + #[test] + fn weight_only_charges_serving_endpoint_calls() { + let netuid = NetUid::from(1); + let expected = ::WeightInfo::check_serving_endpoints_extension(); + + for call in [ + RuntimeCall::System(SystemCall::remark { remark: vec![] }), + add_stake_call(), + ] { + assert_eq!( + as DispatchExtension>::weight(&call), + Weight::zero() + ); + } + + for call in [ + serve_axon_call(netuid), + serve_axon_tls_call(netuid), + serve_prometheus_call(netuid), + ] { + assert_eq!( + as DispatchExtension>::weight(&call), + expected + ); + } + } + #[test] fn unrelated_calls_pass_through() { new_test_ext(0).execute_with(|| { diff --git a/pallets/subtensor/src/guards/check_weights.rs b/pallets/subtensor/src/guards/check_weights.rs index d10e7b8b8c..c116071ba6 100644 --- a/pallets/subtensor/src/guards/check_weights.rs +++ b/pallets/subtensor/src/guards/check_weights.rs @@ -1,3 +1,4 @@ +use super::{CallOf, DispatchableOriginOf, applicable_call}; use crate::weights::WeightInfo; use crate::{Call, Config, Error, Pallet, WeightCommits}; use frame_support::{ @@ -10,8 +11,6 @@ use sp_runtime::traits::Dispatchable; use sp_std::{collections::vec_deque::VecDeque, marker::PhantomData, vec::Vec}; use subtensor_runtime_common::{NetUid, NetUidStorageIndex}; -type CallOf = ::RuntimeCall; -type DispatchableOriginOf = as Dispatchable>::RuntimeOrigin; type WeightCommitQueue = VecDeque<(H256, u64, u64, u64)>; /// Dispatch extension for weight-setting preconditions. @@ -21,6 +20,24 @@ type WeightCommitQueue = VecDeque<(H256, u64, u64, u64)>; pub struct CheckWeights(PhantomData); impl CheckWeights { + pub(crate) fn applies_to(call: &Call) -> bool { + matches!( + call, + Call::batch_commit_weights { .. } + | Call::batch_reveal_weights { .. } + | Call::batch_set_weights { .. } + | Call::commit_weights { .. } + | Call::commit_mechanism_weights { .. } + | Call::reveal_weights { .. } + | Call::reveal_mechanism_weights { .. } + | Call::set_weights { .. } + | Call::set_mechanism_weights { .. } + | Call::commit_timelocked_weights { .. } + | Call::commit_timelocked_mechanism_weights { .. } + | Call::commit_crv3_mechanism_weights { .. } + ) + } + pub fn check(who: &T::AccountId, call: &Call) -> Result<(), Error> { Self::check_input_lengths(call)?; Self::check_min_stake(who, call)?; @@ -227,8 +244,10 @@ where { type Pre = (); - fn weight(_call: &CallOf) -> Weight { - ::WeightInfo::check_weights_extension() + fn weight(call: &CallOf) -> Weight { + applicable_call(call, Self::applies_to) + .map(|_| ::WeightInfo::check_weights_extension()) + .unwrap_or(Weight::zero()) } fn pre_dispatch( @@ -239,7 +258,7 @@ where return Ok(()); }; - let Some(call) = call.is_sub_type() else { + let Some(call) = applicable_call(call, Self::applies_to) else { return Ok(()); }; @@ -251,11 +270,14 @@ where #[allow(clippy::unwrap_used)] mod tests { use super::CheckWeights; - use crate::{Error, MAX_CRV3_COMMIT_SIZE_BYTES, tests::mock::*}; + use crate::{Error, MAX_CRV3_COMMIT_SIZE_BYTES, tests::mock::*, weights::WeightInfo as _}; use codec::Compact; use frame_support::{ - BoundedVec, assert_ok, dispatch::DispatchResultWithPostInfo, traits::ConstU32, + BoundedVec, assert_ok, + dispatch::{DispatchExtension, DispatchResultWithPostInfo}, + traits::ConstU32, traits::ExtendedDispatchable, + weights::Weight, }; use frame_system::Call as SystemCall; use pallet_drand::LastStoredRound; @@ -309,6 +331,99 @@ mod tests { }) } + fn add_stake_call() -> RuntimeCall { + RuntimeCall::SubtensorModule(SubtensorCall::add_stake { + hotkey: U256::from(1), + netuid: 1u16.into(), + amount_staked: 1_000u64.into(), + }) + } + + fn checked_weight_calls(netuid: NetUid) -> Vec { + let bounded_commit = + BoundedVec::>::try_from(vec![0]).unwrap(); + + vec![ + set_weights_call(netuid, 0), + RuntimeCall::SubtensorModule(SubtensorCall::set_mechanism_weights { + netuid, + mecid: MechId::MAIN, + dests: vec![0], + weights: vec![1], + version_key: 0, + }), + RuntimeCall::SubtensorModule(SubtensorCall::batch_set_weights { + netuids: vec![Compact(netuid)], + weights: vec![vec![(Compact(0_u16), Compact(1_u16))]], + version_keys: vec![Compact(0_u64)], + }), + RuntimeCall::SubtensorModule(SubtensorCall::commit_weights { + netuid, + commit_hash: H256::zero(), + }), + RuntimeCall::SubtensorModule(SubtensorCall::commit_mechanism_weights { + netuid, + mecid: MechId::MAIN, + commit_hash: H256::zero(), + }), + RuntimeCall::SubtensorModule(SubtensorCall::batch_commit_weights { + netuids: vec![Compact(netuid)], + commit_hashes: vec![H256::zero()], + }), + reveal_weights_call(netuid), + reveal_mechanism_weights_call(netuid, MechId::MAIN), + RuntimeCall::SubtensorModule(SubtensorCall::batch_reveal_weights { + netuid, + uids_list: vec![vec![0]], + values_list: vec![vec![1]], + salts_list: vec![vec![1]], + version_keys: vec![0], + }), + RuntimeCall::SubtensorModule(SubtensorCall::commit_timelocked_weights { + netuid, + commit: bounded_commit.clone(), + reveal_round: 0, + commit_reveal_version: 0, + }), + RuntimeCall::SubtensorModule(SubtensorCall::commit_timelocked_mechanism_weights { + netuid, + mecid: MechId::MAIN, + commit: bounded_commit.clone(), + reveal_round: 0, + commit_reveal_version: 0, + }), + RuntimeCall::SubtensorModule(SubtensorCall::commit_crv3_mechanism_weights { + netuid, + mecid: MechId::MAIN, + commit: bounded_commit, + reveal_round: 0, + }), + ] + } + + #[test] + fn weight_only_charges_weight_related_calls() { + let netuid = NetUid::from(1); + let expected = ::WeightInfo::check_weights_extension(); + + for call in [ + RuntimeCall::System(SystemCall::remark { remark: vec![] }), + add_stake_call(), + ] { + assert_eq!( + as DispatchExtension>::weight(&call), + Weight::zero() + ); + } + + for call in checked_weight_calls(netuid) { + assert_eq!( + as DispatchExtension>::weight(&call), + expected + ); + } + } + #[test] fn unrelated_calls_pass_through() { new_test_ext(0).execute_with(|| { @@ -360,72 +475,13 @@ mod tests { let netuid = NetUid::from(1); let hotkey = U256::from(1); let coldkey = U256::from(2); - let bounded_commit = - BoundedVec::>::try_from(vec![0]).unwrap(); add_network_disable_commit_reveal(netuid, 1, 0); setup_reserves(netuid, DEFAULT_RESERVE.into(), DEFAULT_RESERVE.into()); SubtensorModule::append_neuron(netuid, &hotkey, 0); crate::Owner::::insert(hotkey, coldkey); SubtensorModule::set_stake_threshold(1_000_000_000_000_u64); - let calls = [ - set_weights_call(netuid, 0), - RuntimeCall::SubtensorModule(SubtensorCall::set_mechanism_weights { - netuid, - mecid: MechId::MAIN, - dests: vec![0], - weights: vec![1], - version_key: 0, - }), - RuntimeCall::SubtensorModule(SubtensorCall::batch_set_weights { - netuids: vec![Compact(netuid)], - weights: vec![vec![(Compact(0_u16), Compact(1_u16))]], - version_keys: vec![Compact(0_u64)], - }), - RuntimeCall::SubtensorModule(SubtensorCall::commit_weights { - netuid, - commit_hash: H256::zero(), - }), - RuntimeCall::SubtensorModule(SubtensorCall::commit_mechanism_weights { - netuid, - mecid: MechId::MAIN, - commit_hash: H256::zero(), - }), - RuntimeCall::SubtensorModule(SubtensorCall::batch_commit_weights { - netuids: vec![Compact(netuid)], - commit_hashes: vec![H256::zero()], - }), - reveal_weights_call(netuid), - reveal_mechanism_weights_call(netuid, MechId::MAIN), - RuntimeCall::SubtensorModule(SubtensorCall::batch_reveal_weights { - netuid, - uids_list: vec![vec![0]], - values_list: vec![vec![1]], - salts_list: vec![vec![1]], - version_keys: vec![0], - }), - RuntimeCall::SubtensorModule(SubtensorCall::commit_timelocked_weights { - netuid, - commit: bounded_commit.clone(), - reveal_round: 0, - commit_reveal_version: 0, - }), - RuntimeCall::SubtensorModule(SubtensorCall::commit_timelocked_mechanism_weights { - netuid, - mecid: MechId::MAIN, - commit: bounded_commit.clone(), - reveal_round: 0, - commit_reveal_version: 0, - }), - RuntimeCall::SubtensorModule(SubtensorCall::commit_crv3_mechanism_weights { - netuid, - mecid: MechId::MAIN, - commit: bounded_commit, - reveal_round: 0, - }), - ]; - - for call in calls { + for call in checked_weight_calls(netuid) { assert_eq!( err(dispatch_with_ext(call, RuntimeOrigin::signed(hotkey))), Error::::NotEnoughStakeToSetWeights.into() diff --git a/pallets/subtensor/src/guards/mod.rs b/pallets/subtensor/src/guards/mod.rs index 485fc65a04..3865352858 100644 --- a/pallets/subtensor/src/guards/mod.rs +++ b/pallets/subtensor/src/guards/mod.rs @@ -5,9 +5,28 @@ mod check_rate_limits; mod check_serving_endpoints; mod check_weights; +use crate::{Call, Config}; +use frame_support::traits::IsSubType; +use sp_runtime::traits::Dispatchable; + pub use check_coldkey_swap::*; pub use check_delegate_take::*; pub use check_evm_key_association::*; pub use check_rate_limits::*; pub use check_serving_endpoints::*; pub use check_weights::*; + +pub(crate) type CallOf = ::RuntimeCall; +pub(crate) type DispatchableOriginOf = as Dispatchable>::RuntimeOrigin; + +pub(crate) fn applicable_call( + call: &CallOf, + applies_to: impl FnOnce(&Call) -> bool, +) -> Option<&Call> +where + T: Config, + CallOf: IsSubType>, +{ + let call = call.is_sub_type()?; + applies_to(call).then_some(call) +} diff --git a/pallets/subtensor/src/lib.rs b/pallets/subtensor/src/lib.rs index b1afef3401..f19b8086a3 100644 --- a/pallets/subtensor/src/lib.rs +++ b/pallets/subtensor/src/lib.rs @@ -16,6 +16,7 @@ use frame_support::{ pallet_macros::import_section, pallet_prelude::*, traits::tokens::fungible, + weights::WeightMeter, }; use scale_info::TypeInfo; use sp_core::Get; @@ -67,6 +68,15 @@ pub const MAX_SUBNET_CLAIMS: usize = 5; pub const MAX_ROOT_CLAIM_THRESHOLD: u64 = 10_000_000; +/// Maximum number of UIDs (per subnet) that may be associated with a single EVM address. +/// +/// This bounds the size of the `AssociatedUidsByEvmAddress` reverse-index value, keeping +/// `uid_lookup` reads and association writes cheap and their PoV footprint small. Only the +/// holder of an EVM key's private key can grow its bucket (each association requires a +/// signature from that key), so this only limits how many of one's own UIDs may point at a +/// single EVM address. +pub const MAX_ASSOCIATED_UIDS_PER_EVM_ADDRESS: u32 = 32; + /// Account flag bit that opts into receiving locked alpha transfers. pub const ACCOUNT_FLAGS_ACCEPT_LOCKED_ALPHA: u128 = 1u128 << 0; @@ -81,10 +91,13 @@ pub const ACCOUNT_FLAGS_ACCEPT_LOCKED_ALPHA: u128 = 1u128 << 0; #[frame_support::pallet] #[allow(clippy::expect_used)] pub mod pallet { - use crate::RateLimitKey; use crate::migrations; use crate::staking::lock::LockState; + use crate::subnets::dissolution::DissolveCleanupStatus; use crate::subnets::leasing::{LeaseId, SubnetLeaseOf}; + use crate::subnets::subnet::NetworkRegistrationInfo; + use crate::weights::WeightInfo; + use crate::{MAX_ASSOCIATED_UIDS_PER_EVM_ADDRESS, RateLimitKey}; use frame_support::Twox64Concat; use frame_support::{ BoundedVec, @@ -93,6 +106,7 @@ pub mod pallet { traits::{ OriginTrait, QueryPreimage, StorePreimage, UnfilteredDispatchable, tokens::fungible, }, + weights::Weight, }; use frame_system::pallet_prelude::*; use pallet_drand::types::RoundNumber; @@ -1284,6 +1298,7 @@ pub mod pallet { /// ==== Coinbase ==== /// ================== /// --- ITEM ( global_block_emission ) + #[deprecated(note = "Use calculate_block_emission() or the block emission RPC instead.")] #[pallet::storage] pub type BlockEmission = StorageValue<_, u64, ValueQuery, DefaultBlockEmission>; @@ -2194,6 +2209,23 @@ pub mod pallet { pub type SubtokenEnabled = StorageMap<_, Identity, NetUid, bool, ValueQuery, DefaultFalse>; + /// --- ITEM ( dissolve_cleanup_queue ) Networks dissolved but some storage not removed yet + #[pallet::storage] + pub type DissolveCleanupQueue = StorageValue<_, Vec, ValueQuery>; + + /// --- ITEM ( current_dissolve_cleanup_status ) dissolve status for the network + #[pallet::storage] + pub type CurrentDissolveCleanupStatus = StorageValue<_, DissolveCleanupStatus, OptionQuery>; + + /// --- ITEM ( network_registration_queue ) Network registrations waiting to be executed. + #[pallet::storage] + pub type NetworkRegistrationQueue = + StorageValue<_, Vec>>, ValueQuery>; + + /// --- MAP ( coldkey ) --> lock_id + #[pallet::storage] + pub type NetworkRegistrationLockId = StorageValue<_, u32, ValueQuery>; + // ======================================= // ==== VotingPower Storage ==== // ======================================= @@ -2593,6 +2625,18 @@ pub mod pallet { pub type AssociatedEvmAddress = StorageDoubleMap<_, Twox64Concat, NetUid, Twox64Concat, u16, (H160, u64), OptionQuery>; + /// --- DMAP (netuid, H160) --> associated UIDs and last block where ownership was proven. + #[pallet::storage] + pub type AssociatedUidsByEvmAddress = StorageDoubleMap< + _, + Twox64Concat, + NetUid, + Twox64Concat, + H160, + BoundedVec<(u16, u64), ConstU32>, + ValueQuery, + >; + /// ======================== /// ==== Subnet Leasing ==== /// ======================== @@ -2987,5 +3031,5 @@ impl ProxyInterface for () { /// Pallets that hold per-subnet commitments implement this to purge all state for `netuid`. pub trait CommitmentsInterface { - fn purge_netuid(netuid: NetUid); + fn purge_netuid(netuid: NetUid, weight_meter: &mut WeightMeter) -> bool; } diff --git a/pallets/subtensor/src/macros/config.rs b/pallets/subtensor/src/macros/config.rs index ad372d1e0e..41ba2cd6ae 100644 --- a/pallets/subtensor/src/macros/config.rs +++ b/pallets/subtensor/src/macros/config.rs @@ -8,6 +8,7 @@ mod config { use crate::{CommitmentsInterface, GetAlphaForTao, GetTaoForAlpha}; use frame_support::PalletId; + use frame_support::traits::LockableCurrency; use pallet_alpha_assets::AlphaAssetsInterface; use pallet_commitments::GetCommitments; use subtensor_runtime_common::AuthorshipInfo; @@ -35,7 +36,8 @@ mod config { /// Currency type that will be used to place deposits on neurons type Currency: fungible::Balanced - + fungible::Mutate; + + fungible::Mutate + + LockableCurrency; /// The scheduler type used for scheduling delayed calls. type Scheduler: ScheduleAnon< diff --git a/pallets/subtensor/src/macros/dispatches.rs b/pallets/subtensor/src/macros/dispatches.rs index 08e5bb8fdf..f06e42201e 100644 --- a/pallets/subtensor/src/macros/dispatches.rs +++ b/pallets/subtensor/src/macros/dispatches.rs @@ -5,7 +5,7 @@ use frame_support::pallet_macros::pallet_section; /// This can later be imported into the pallet using [`import_section`]. #[pallet_section] mod dispatches { - use crate::weights::WeightInfo; + use frame_support::pallet_prelude::DispatchResultWithPostInfo; use frame_support::traits::schedule::v3::Anon as ScheduleAnon; use frame_system::pallet_prelude::BlockNumberFor; use sp_core::ecdsa::Signature; @@ -1231,9 +1231,7 @@ mod dispatches { /// Remove a user's subnetwork /// The caller must be the owner of the network #[pallet::call_index(61)] - #[pallet::weight(Weight::from_parts(119_000_000, 0) - .saturating_add(T::DbWeight::get().reads(6)) - .saturating_add(T::DbWeight::get().writes(31)))] + #[pallet::weight(::WeightInfo::dissolve_network())] pub fn dissolve_network( origin: OriginFor, _coldkey: T::AccountId, @@ -1982,6 +1980,7 @@ mod dispatches { symbol: Vec, ) -> DispatchResult { Self::ensure_subnet_owner_or_root(origin, netuid)?; + ensure!(Self::if_subnet_exist(netuid), Error::::SubnetNotExists); Self::ensure_symbol_exists(&symbol)?; Self::ensure_symbol_available(&symbol)?; @@ -2233,6 +2232,7 @@ mod dispatches { new_value: u64, ) -> DispatchResult { Self::ensure_subnet_owner_or_root(origin, netuid)?; + ensure!(Self::if_subnet_exist(netuid), Error::::SubnetNotExists); ensure!( new_value <= I96F32::from(MAX_ROOT_CLAIM_THRESHOLD), @@ -2639,15 +2639,7 @@ mod dispatches { ))] pub fn set_reject_locked_alpha(origin: OriginFor, enabled: bool) -> DispatchResult { let coldkey = ensure_signed(origin)?; - AccountFlags::::mutate_exists(&coldkey, |maybe_flags| { - let mut flags = maybe_flags.unwrap_or_default(); - if enabled { - flags &= !crate::ACCOUNT_FLAGS_ACCEPT_LOCKED_ALPHA; - } else { - flags |= crate::ACCOUNT_FLAGS_ACCEPT_LOCKED_ALPHA; - } - *maybe_flags = if flags == 0 { None } else { Some(flags) }; - }); + Self::set_accept_locked_alpha(&coldkey, !enabled); Self::deposit_event(Event::RejectLockedAlphaUpdated { coldkey, enabled }); Ok(()) } diff --git a/pallets/subtensor/src/macros/errors.rs b/pallets/subtensor/src/macros/errors.rs index 6401b5846d..434951856f 100644 --- a/pallets/subtensor/src/macros/errors.rs +++ b/pallets/subtensor/src/macros/errors.rs @@ -257,6 +257,8 @@ mod errors { CannotAffordLockCost, /// exceeded the rate limit for associating an EVM key. EvmKeyAssociateRateLimitExceeded, + /// The EVM address already has the maximum number of associated UIDs on this subnet. + EvmKeyAssociationLimitExceeded, /// Same auto stake hotkey already set SameAutoStakeHotkeyAlreadySet, /// The UID map for the subnet could not be cleared @@ -279,6 +281,10 @@ mod errors { InvalidVotingPowerEmaAlpha, /// Deprecated call. Deprecated, + /// Subnet buyback exceeded the operation rate limit + SubnetBuybackRateLimitExceeded, + /// Network already in dissolved queue + NetworkDissolveAlreadyQueued, /// "Add stake and burn" exceeded the operation rate limit AddStakeBurnRateLimitExceeded, /// A coldkey swap has been announced for this account. @@ -303,6 +309,8 @@ mod errors { CannotUseSystemAccount, /// Trying to unlock more than locked UnlockAmountTooHigh, + /// Waiting for dissolved subnet cleanup. + WaitingForDissolvedSubnetCleanup, /// The supplied tempo is outside the allowed range. TempoOutOfBounds, /// The supplied activity-cutoff factor is outside the allowed range. @@ -319,5 +327,9 @@ mod errors { DynamicTempoBlockedByCommitReveal, /// The destination coldkey rejects incoming locked alpha. AccountRejectsLockedAlpha, + /// The coldkey has already registered too many subnets + LockIdOverFlow, + /// Need to wait more blocks to do the start call. + StartCallNotReady, } } diff --git a/pallets/subtensor/src/macros/events.rs b/pallets/subtensor/src/macros/events.rs index 7bc9bf450a..698cc4bc16 100644 --- a/pallets/subtensor/src/macros/events.rs +++ b/pallets/subtensor/src/macros/events.rs @@ -534,6 +534,12 @@ mod events { alpha: AlphaBalance, }, + /// data for a dissolved network has been cleaned up. + NetworkDissolveCleanupCompleted { + /// The subnet ID + netuid: NetUid, + }, + /// A coldkey swap announcement has been cleared. ColdkeySwapCleared { /// The account ID of the coldkey that cleared the announcement. @@ -670,6 +676,24 @@ mod events { enabled: bool, }, + /// A network registration cost has been queued. + NetworkRegistrationQueued { + /// The network registration information. + coldkey: T::AccountId, + /// The hotkey that registered the network. + hotkey: T::AccountId, + /// The mechanism that registered the network. + mechid: u16, + /// The identity that registered the network. + identity: Option, + /// The lock amount that registered the network. + lock_amount: TaoBalance, + /// The median subnet alpha price that registered the network. + median_subnet_alpha_price: U64F64, + /// The block at which the network was registered. + registration_block: u64, + }, + /// A coldkey's reject locked alpha account flag was updated. RejectLockedAlphaUpdated { /// The coldkey whose flag changed. diff --git a/pallets/subtensor/src/macros/hooks.rs b/pallets/subtensor/src/macros/hooks.rs index 6371f30e46..ed0b46314b 100644 --- a/pallets/subtensor/src/macros/hooks.rs +++ b/pallets/subtensor/src/macros/hooks.rs @@ -177,22 +177,34 @@ mod hooks { // Capture the runtime-upgrade block for TAO-in refund cutover. .saturating_add(migrations::migrate_tao_in_refund_deployment_block::migrate_tao_in_refund_deployment_block::()) // Fix lock state left behind by subnet-scoped hotkey swaps. - .saturating_add(migrations::migrate_fix_subnet_hotkey_lock_swaps::migrate_fix_subnet_hotkey_lock_swaps::()); + .saturating_add(migrations::migrate_fix_subnet_hotkey_lock_swaps::migrate_fix_subnet_hotkey_lock_swaps::()) + // Populate reverse lookup index for EVM address associations. + .saturating_add(migrations::migrate_associated_evm_address_index::migrate_associated_evm_address_index::()); weight } #[cfg(feature = "try-runtime")] fn try_state(_n: BlockNumberFor) -> Result<(), sp_runtime::TryRuntimeError> { - // Disabled: https://github.com/opentensor/subtensor/pull/1166 + // Disabled: https://github.com/RaoFoundation/subtensor/pull/1166 // Self::check_total_stake()?; Ok(()) } + + fn on_idle(_block: BlockNumberFor, limit: Weight) -> Weight { + let mut weight = Self::remove_data_for_dissolved_networks(limit); + + if weight.all_lt(limit) { + weight.saturating_accrue(Self::process_network_registration_queue()); + } + + weight + } } impl Pallet { // This function is to clean up the old hotkey swap records // It just clean up for one subnet at a time, according to the block number - fn clean_up_hotkey_swap_records(block_number: BlockNumberFor) -> Weight { + pub(crate) fn clean_up_hotkey_swap_records(block_number: BlockNumberFor) -> Weight { let mut weight = Weight::from_parts(0, 0); let hotkey_swap_on_subnet_interval = T::HotkeySwapOnSubnetInterval::get(); let block_number: u64 = TryInto::try_into(block_number) diff --git a/pallets/subtensor/src/migrations/migrate_associated_evm_address_index.rs b/pallets/subtensor/src/migrations/migrate_associated_evm_address_index.rs new file mode 100644 index 0000000000..5de0ce0f85 --- /dev/null +++ b/pallets/subtensor/src/migrations/migrate_associated_evm_address_index.rs @@ -0,0 +1,91 @@ +use super::*; +use frame_support::{traits::Get, weights::Weight}; +use scale_info::prelude::string::String; +use sp_std::vec::Vec; + +/// Backfill the reverse index `AssociatedUidsByEvmAddress` from the existing +/// `AssociatedEvmAddress` forward map. One-time, idempotent, guarded by `HasMigrationRun`. +/// +/// This scans the whole forward map in a single block. That is safe because the map is tiny: it +/// grows only through `do_associate_evm_key`, an opt-in, signature-gated, rate-limited extrinsic. +/// Measured on 2026-07-07, the entire map holds **100 entries on Finney and 20 on testnet**, with a +/// largest single-`(netuid, evm_key)` bucket of **3** (against the cap of 32). There is no realistic +/// chain state in which this scan is expensive, so no chunked / multi-block migration is warranted. +pub fn migrate_associated_evm_address_index() -> Weight { + let migration_name = b"migrate_associated_evm_address_index".to_vec(); + let mut weight = T::DbWeight::get().reads(1); + + if HasMigrationRun::::get(&migration_name) { + log::info!( + "Migration '{:?}' has already run. Skipping.", + String::from_utf8_lossy(&migration_name) + ); + return weight; + } + + log::info!( + "Running migration '{}'", + String::from_utf8_lossy(&migration_name) + ); + + let mut migrated = 0_u64; + + // Forward-map entries whose address bucket is already full and therefore cannot be represented + // in the bounded reverse index. Collected here and pruned from the forward map after the scan + // so both maps agree on the pallet's cap (see the reconciliation loop below). + let mut overflow = Vec::new(); + + for (netuid, uid, (evm_key, block_associated)) in AssociatedEvmAddress::::iter() { + weight.saturating_accrue(T::DbWeight::get().reads(1)); + + let mut overflowed = false; + AssociatedUidsByEvmAddress::::mutate(netuid, evm_key, |uids| { + if let Some((_, stored_block)) = + uids.iter_mut().find(|(stored_uid, _)| *stored_uid == uid) + { + *stored_block = block_associated; + return; + } + + if uids.try_push((uid, block_associated)).is_err() { + overflowed = true; + } + }); + weight.saturating_accrue(T::DbWeight::get().reads_writes(1, 1)); + + if overflowed { + overflow.push((netuid, uid)); + } else { + migrated = migrated.saturating_add(1); + } + } + + // Reconcile over-cap buckets. An address that already holds + // `MAX_ASSOCIATED_UIDS_PER_EVM_ADDRESS` UIDs cannot index any further ones. Leaving those extra + // UIDs in the forward map would make the two maps disagree: `uid_lookup` would silently miss + // them, and the capacity check would see a full bucket and refuse to let them refresh — so they + // could never recover. Instead we drop the excess from the forward map too, so both maps agree + // on the cap the pallet now enforces; a dropped UID can re-associate later, reusing a freed + // slot. This branch is unreachable for any real chain state (observed peak reuse of a single + // address is 3, far below the cap); it exists so the migration can never silently produce an + // inconsistent index. + for (netuid, uid) in &overflow { + AssociatedEvmAddress::::remove(*netuid, *uid); + weight.saturating_accrue(T::DbWeight::get().writes(1)); + log::warn!( + "migrate_associated_evm_address_index: dropped over-cap association (netuid={netuid:?}, uid={uid}) to keep the forward map and reverse index consistent" + ); + } + + HasMigrationRun::::insert(&migration_name, true); + weight.saturating_accrue(T::DbWeight::get().writes(1)); + + log::info!( + "Migration '{:?}' completed successfully. {} associations indexed, {} over-cap associations dropped.", + String::from_utf8_lossy(&migration_name), + migrated, + overflow.len(), + ); + + weight +} diff --git a/pallets/subtensor/src/migrations/migrate_fix_bad_hk_swap.rs b/pallets/subtensor/src/migrations/migrate_fix_bad_hk_swap.rs index 9ecf7b73d7..380232e499 100644 --- a/pallets/subtensor/src/migrations/migrate_fix_bad_hk_swap.rs +++ b/pallets/subtensor/src/migrations/migrate_fix_bad_hk_swap.rs @@ -29,7 +29,7 @@ pub fn try_restore_shares() -> Weight { let effected_netuid = 59.into(); #[rustfmt::skip] - let diffs: [(&str, i64); 112] = [ + let diffs: [(&str, i64); 112] = [ ("5Fn9SqQhx5bhDua7AGgkKxxk3gfZ75WWBGCMPeKH1WBgPaMQ", -2375685930981_i64), ("5Fnhtm7cpxEbZaChnRZ8yWoF8MXVxmobkmLRehh5bkYtyZA9", -4090996138227), ("5C7j3w2zz1SVejRuFrb2zFWHXT7UfG7eWA87KXL1WyV5KLVR", -607494031), diff --git a/pallets/subtensor/src/migrations/mod.rs b/pallets/subtensor/src/migrations/mod.rs index e846d325dc..98699ccbce 100644 --- a/pallets/subtensor/src/migrations/mod.rs +++ b/pallets/subtensor/src/migrations/mod.rs @@ -4,6 +4,7 @@ use frame_support::pallet_prelude::Weight; use sp_io::KillStorageResult; use sp_io::hashing::twox_128; use sp_io::storage::clear_prefix; +pub mod migrate_associated_evm_address_index; pub mod migrate_auto_stake_destination; pub mod migrate_cleanup_swap_v3; pub mod migrate_clear_deprecated_registration_maps; diff --git a/pallets/subtensor/src/rpc_info/delegate_info.rs b/pallets/subtensor/src/rpc_info/delegate_info.rs index b69ea6f778..0a57dc4150 100644 --- a/pallets/subtensor/src/rpc_info/delegate_info.rs +++ b/pallets/subtensor/src/rpc_info/delegate_info.rs @@ -188,4 +188,8 @@ impl Pallet { pub fn get_coldkey_for_hotkey(hotkey: &T::AccountId) -> T::AccountId { Owner::::get(hotkey) } + + pub fn maybe_coldkey_for_hotkey(hotkey: &T::AccountId) -> Option { + Owner::::try_get(hotkey).ok() + } } diff --git a/pallets/subtensor/src/staking/add_stake.rs b/pallets/subtensor/src/staking/add_stake.rs index 33cadf241b..d2bad04787 100644 --- a/pallets/subtensor/src/staking/add_stake.rs +++ b/pallets/subtensor/src/staking/add_stake.rs @@ -48,6 +48,8 @@ impl Pallet { "do_add_stake( origin:{coldkey:?} hotkey:{hotkey:?}, netuid:{netuid:?}, stake_to_be_added:{stake_to_be_added:?} )" ); + Self::ensure_add_stake_input_within_swap_limit(netuid, stake_to_be_added)?; + // 2. Validate user input Self::validate_add_stake( &coldkey, @@ -124,6 +126,8 @@ impl Pallet { "do_add_stake( origin:{coldkey:?} hotkey:{hotkey:?}, netuid:{netuid:?}, stake_to_be_added:{stake_to_be_added:?} )" ); + Self::ensure_add_stake_input_within_swap_limit(netuid, stake_to_be_added)?; + // 2. Calculate the maximum amount that can be executed with price limit let max_amount: TaoBalance = Self::get_max_amount_add(netuid, limit_price)?.into(); let mut possible_stake = stake_to_be_added; @@ -175,11 +179,27 @@ impl Pallet { } } - // Use reverting swap to estimate max limit amount - let order = GetAlphaForTao::::with_amount(u64::MAX); + // Use the largest supported input instead of probing the swap path with u64::MAX. + let max_supported_input = SubnetTAO::::get(netuid).saturating_mul(1_000.into()); + let order = GetAlphaForTao::::with_amount(max_supported_input); let result = T::SwapInterface::swap(netuid.into(), order, limit_price, false, true) .map(|r| r.amount_paid_in.saturating_add(r.fee_paid))?; Ok(result.into()) } + + fn ensure_add_stake_input_within_swap_limit( + netuid: NetUid, + amount: TaoBalance, + ) -> Result<(), Error> { + if !netuid.is_root() && SubnetMechanism::::get(netuid) == 1 { + let max_supported_input = SubnetTAO::::get(netuid).saturating_mul(1_000.into()); + ensure!( + amount <= max_supported_input, + Error::::InsufficientLiquidity + ); + } + + Ok(()) + } } diff --git a/pallets/subtensor/src/staking/claim_root.rs b/pallets/subtensor/src/staking/claim_root.rs index 7001369a63..c8b95bcb29 100644 --- a/pallets/subtensor/src/staking/claim_root.rs +++ b/pallets/subtensor/src/staking/claim_root.rs @@ -4,8 +4,10 @@ use frame_support::storage::{TransactionOutcome, with_transaction}; use frame_support::weights::Weight; use sp_core::Get; use sp_runtime::DispatchError; +use sp_std::collections::btree_map::BTreeMap; use sp_std::collections::btree_set::BTreeSet; use substrate_fixed::types::I96F32; +use subtensor_runtime_common::clear_prefix_with_meter; use subtensor_swap_interface::SwapHandler; impl Pallet { @@ -134,6 +136,11 @@ impl Pallet { root_claim_type: RootClaimTypeEnum, ignore_minimum_condition: bool, ) -> DispatchResult { + if DissolveCleanupQueue::::get().contains(&netuid) { + log::debug!("root claim on subnet {netuid} is skipped, network is dissolved"); + return Ok(()); + } + // Subtract the root claimed. let owed: I96F32 = Self::get_root_owed_for_hotkey_coldkey_float(hotkey, coldkey, netuid); @@ -495,15 +502,54 @@ impl Pallet { } /// Claim all root dividends for subnet and remove all associated data. - pub fn finalize_all_subnet_root_dividends(netuid: NetUid) { - let hotkeys = RootClaimable::::iter_keys().collect::>(); + pub fn clean_up_root_claimable_for_subnet( + netuid: NetUid, + weight_meter: &mut WeightMeter, + last_key: Option>, + ) -> (bool, Option>) { + // let mut to_remove_map = BTreeMap::>::new(); - for hotkey in hotkeys.iter() { - RootClaimable::::mutate(hotkey, |claimable| { - claimable.remove(&netuid); - }); + // let mut read_all = true; + + let iter = match last_key { + Some(raw_key) => RootClaimable::::iter_from(raw_key), + None => RootClaimable::::iter(), + }; + + fn filter_claimable( + claimable: &BTreeMap, + netuid: NetUid, + ) -> BTreeMap { + let mut result = claimable.clone(); + if result.contains_key(&netuid) { + result.remove(&netuid); + } + result } - let _ = RootClaimed::::clear_prefix((netuid,), u32::MAX, None); + let (read_all, last_item) = Self::remove_storage_entries_for_netuid( + weight_meter, + iter, + |(_, _)| true, + |(hotkey, claimable)| (hotkey.clone(), claimable.clone()), + |(hotkey, claimable)| { + RootClaimable::::insert(hotkey, filter_claimable(claimable, netuid)) + }, + 1, + ); + + ( + read_all, + last_item.map(|(hotkey, _)| RootClaimable::::hashed_key_for(&hotkey)), + ) + } + + pub fn clean_up_root_claimed_for_subnet( + netuid: NetUid, + weight_meter: &mut WeightMeter, + ) -> bool { + clear_prefix_with_meter(weight_meter, T::DbWeight::get().writes(1), |limit| { + RootClaimed::::clear_prefix((netuid,), limit, None) + }) } } diff --git a/pallets/subtensor/src/staking/lock.rs b/pallets/subtensor/src/staking/lock.rs index fcf699619d..380b932efc 100644 --- a/pallets/subtensor/src/staking/lock.rs +++ b/pallets/subtensor/src/staking/lock.rs @@ -1,5 +1,6 @@ use super::*; use codec::{Decode, DecodeWithMemTracking, Encode}; +use frame_support::weights::WeightMeter; use safe_math::FixedExt; use scale_info::TypeInfo; use sp_std::collections::btree_map::BTreeMap; @@ -471,6 +472,18 @@ impl Pallet { AccountFlags::::get(coldkey) & crate::ACCOUNT_FLAGS_ACCEPT_LOCKED_ALPHA != 1 } + pub fn set_accept_locked_alpha(coldkey: &T::AccountId, enabled: bool) { + AccountFlags::::mutate_exists(coldkey, |maybe_flags| { + let mut flags = maybe_flags.unwrap_or_default(); + if enabled { + flags |= crate::ACCOUNT_FLAGS_ACCEPT_LOCKED_ALPHA; + } else { + flags &= !crate::ACCOUNT_FLAGS_ACCEPT_LOCKED_ALPHA; + } + *maybe_flags = if flags == 0 { None } else { Some(flags) }; + }); + } + pub fn ensure_can_receive_locked_alpha( coldkey: &T::AccountId, amount: AlphaBalance, @@ -628,6 +641,8 @@ impl Pallet { netuid: NetUid, enabled: bool, ) -> DispatchResult { + ensure!(Self::if_subnet_exist(netuid), Error::::SubnetNotExists); + let now = Self::get_current_block_as_u64(); let current_enabled = Self::is_perpetual_lock(coldkey, netuid); @@ -747,6 +762,7 @@ impl Pallet { hotkey: &T::AccountId, amount: AlphaBalance, ) -> dispatch::DispatchResult { + ensure!(Self::if_subnet_exist(netuid), Error::::SubnetNotExists); ensure!(!amount.is_zero(), Error::::AmountTooLow); ensure!( Self::hotkey_account_exists(hotkey), @@ -1434,6 +1450,14 @@ impl Pallet { } } + let flags = AccountFlags::::get(old_coldkey); + AccountFlags::::remove(old_coldkey); + if flags != 0 { + AccountFlags::::insert(new_coldkey, flags); + } else { + AccountFlags::::remove(new_coldkey); + } + // Insert locks for the new coldkey and add to the destination aggregate // buckets after the flags have moved. for (netuid, hotkey, old_lock, perpetual_lock) in rolled_locks_to_transfer { @@ -1695,6 +1719,7 @@ impl Pallet { destination_hotkey: &T::AccountId, netuid: NetUid, ) -> DispatchResult { + ensure!(Self::if_subnet_exist(netuid), Error::::SubnetNotExists); ensure!( Self::hotkey_account_exists(destination_hotkey), Error::::HotKeyAccountNotExists @@ -1928,39 +1953,57 @@ impl Pallet { Ok(()) } - /// Destroys all lock maps for network dissolution - pub fn destroy_lock_maps(netuid: NetUid) { - // LockingColdkeys: (netuid, hotkey, coldkey) - // Lock: (coldkey, netuid, hotkey) - { - let to_rm: sp_std::vec::Vec<((T::AccountId, T::AccountId), ())> = - LockingColdkeys::::iter_prefix((netuid,)).collect(); - - for ((hot, cold), _) in to_rm { - Lock::::remove((cold, netuid, hot)); - } - let _ = LockingColdkeys::::clear_prefix((netuid,), u32::MAX, None); - } + /// Removes `Lock` entries for `netuid`, resuming from `LastKeptRawKey` when weight is limited. + pub fn remove_network_lock( + netuid: NetUid, + weight_meter: &mut WeightMeter, + last_key: Option>, + ) -> (bool, Option>) { + let iter = match last_key { + Some(key) => Lock::::iter_from(key), + None => Lock::::iter(), + }; - // HotkeyLock: (netuid, hotkey) → LockState - let _ = HotkeyLock::::clear_prefix(netuid, u32::MAX, None); + let (read_all, last_item) = Self::remove_storage_entries_for_netuid( + weight_meter, + iter, + |((_, this_netuid, _), _)| *this_netuid == netuid, + |((coldkey, _this_netuid, hotkey), _)| (coldkey, hotkey), + |(coldkey, hotkey)| Lock::::remove((coldkey.clone(), netuid, hotkey.clone())), + 1, + ); - // DecayingHotkeyLock: (netuid, hotkey) - let _ = DecayingHotkeyLock::::clear_prefix(netuid, u32::MAX, None); + ( + read_all, + last_item.map(|((coldkey, _, hotkey), _)| { + Lock::::hashed_key_for((&coldkey, netuid, &hotkey)) + }), + ) + } - // OwnerLock / DecayingOwnerLock: (netuid) - OwnerLock::::remove(netuid); - DecayingOwnerLock::::remove(netuid); + /// Removes `DecayingLock` entries for `netuid`, resuming from `LastKeptRawKey` when weight is limited. + pub fn remove_network_decaying_lock( + netuid: NetUid, + weight_meter: &mut WeightMeter, + last_key: Option>, + ) -> (bool, Option>) { + let iter = match last_key { + Some(raw_key) => DecayingLock::::iter_from(raw_key), + None => DecayingLock::::iter(), + }; - // DecayingLock: (coldkey, netuid) - { - let to_rm: sp_std::vec::Vec = DecayingLock::::iter() - .filter_map(|(cold, n, _)| if n == netuid { Some(cold) } else { None }) - .collect(); + let (read_all, last_item) = Self::remove_storage_entries_for_netuid( + weight_meter, + iter, + |(_, nu, _)| *nu == netuid, + |(cold, nu, _)| (cold, nu), + |(cold, netuid)| DecayingLock::::remove(cold, netuid), + 1, + ); - for cold in to_rm { - DecayingLock::::remove(cold, netuid); - } - } + ( + read_all, + last_item.map(|(cold, nu, _)| DecayingLock::::hashed_key_for(&cold, nu)), + ) } } diff --git a/pallets/subtensor/src/staking/recycle_alpha.rs b/pallets/subtensor/src/staking/recycle_alpha.rs index 7152c48cdb..f817d25ede 100644 --- a/pallets/subtensor/src/staking/recycle_alpha.rs +++ b/pallets/subtensor/src/staking/recycle_alpha.rs @@ -1,5 +1,6 @@ use super::*; use crate::{Error, system::ensure_signed}; +use frame_support::storage::{TransactionOutcome, with_transaction}; use subtensor_runtime_common::{AlphaBalance, NetUid}; impl Pallet { @@ -132,22 +133,39 @@ impl Pallet { amount: TaoBalance, limit: Option, ) -> DispatchResult { - let alpha = if let Some(limit) = limit { - Self::do_add_stake_limit(origin.clone(), hotkey.clone(), netuid, amount, limit, false)? - } else { - Self::do_add_stake(origin.clone(), hotkey.clone(), netuid, amount)? - }; - - Self::do_burn_alpha(origin, hotkey.clone(), alpha, netuid)?; - - Self::deposit_event(Event::AddStakeBurn { - netuid, - hotkey, - amount, - alpha, - }); - - Ok(()) + ensure!(Self::if_subnet_exist(netuid), Error::::SubnetNotExists); + with_transaction(|| { + let result = (|| { + let alpha = if let Some(limit) = limit { + Self::do_add_stake_limit( + origin.clone(), + hotkey.clone(), + netuid, + amount, + limit, + false, + )? + } else { + Self::do_add_stake(origin.clone(), hotkey.clone(), netuid, amount)? + }; + + Self::do_burn_alpha(origin, hotkey.clone(), alpha, netuid)?; + + Self::deposit_event(Event::AddStakeBurn { + netuid, + hotkey, + amount, + alpha, + }); + + Ok(()) + })(); + + match result { + Ok(()) => TransactionOutcome::Commit(Ok(())), + Err(err) => TransactionOutcome::Rollback(Err(err)), + } + }) } /// Atomically stakes TAO and recycles the resulting alpha. @@ -160,8 +178,17 @@ impl Pallet { netuid: NetUid, amount: TaoBalance, ) -> Result { - let alpha = Self::do_add_stake(origin.clone(), hotkey.clone(), netuid, amount)?; - Self::do_recycle_alpha(origin, hotkey, alpha, netuid) + with_transaction(|| { + let result = (|| { + let alpha = Self::do_add_stake(origin.clone(), hotkey.clone(), netuid, amount)?; + Self::do_recycle_alpha(origin, hotkey, alpha, netuid) + })(); + + match result { + Ok(alpha) => TransactionOutcome::Commit(Ok(alpha)), + Err(err) => TransactionOutcome::Rollback(Err(err)), + } + }) } /// Atomically stakes TAO and burns the resulting alpha. Permissionless @@ -173,7 +200,16 @@ impl Pallet { netuid: NetUid, amount: TaoBalance, ) -> Result { - let alpha = Self::do_add_stake(origin.clone(), hotkey.clone(), netuid, amount)?; - Self::do_burn_alpha(origin, hotkey, alpha, netuid) + with_transaction(|| { + let result = (|| { + let alpha = Self::do_add_stake(origin.clone(), hotkey.clone(), netuid, amount)?; + Self::do_burn_alpha(origin, hotkey, alpha, netuid) + })(); + + match result { + Ok(alpha) => TransactionOutcome::Commit(Ok(alpha)), + Err(err) => TransactionOutcome::Rollback(Err(err)), + } + }) } } diff --git a/pallets/subtensor/src/staking/remove_stake.rs b/pallets/subtensor/src/staking/remove_stake.rs index cf640dc661..cd496b267a 100644 --- a/pallets/subtensor/src/staking/remove_stake.rs +++ b/pallets/subtensor/src/staking/remove_stake.rs @@ -1,4 +1,9 @@ use super::*; +use crate::subnets::dissolution::DissolveCleanupStatus; +use frame_support::weights::WeightMeter; +use num_traits::ToPrimitive; +use sp_std::collections::btree_map::BTreeMap; +use sp_std::collections::btree_set::BTreeSet; use substrate_fixed::types::U96F32; use subtensor_runtime_common::{AlphaBalance, NetUid, TaoBalance, Token}; use subtensor_swap_interface::{Order, SwapHandler}; @@ -55,6 +60,7 @@ impl Pallet { let alpha_available = Self::get_stake_for_hotkey_and_coldkey_on_subnet(&hotkey, &coldkey, netuid); let alpha_unstaked = alpha_unstaked.min(alpha_available); + Self::ensure_remove_stake_input_within_swap_limit(netuid, alpha_unstaked)?; // 2. Validate the user input Self::validate_remove_stake( @@ -336,6 +342,8 @@ impl Pallet { "do_remove_stake( origin:{coldkey:?} hotkey:{hotkey:?}, netuid: {netuid:?}, alpha_unstaked:{alpha_unstaked:?} )" ); + Self::ensure_remove_stake_input_within_swap_limit(netuid, alpha_unstaked)?; + // 2. Calculate the maximum amount that can be executed with price limit let max_amount = Self::get_max_amount_remove(netuid, limit_price)?; let mut possible_alpha = alpha_unstaked; @@ -394,20 +402,37 @@ impl Pallet { } } - // Use reverting swap to estimate max limit amount - let order = GetTaoForAlpha::::with_amount(u64::MAX); + // Use the largest supported input instead of probing the swap path with u64::MAX. + let max_supported_input = SubnetAlphaIn::::get(netuid).saturating_mul(1_000.into()); + let order = GetTaoForAlpha::::with_amount(max_supported_input); let result = T::SwapInterface::swap(netuid.into(), order, limit_price.into(), false, true) .map(|r| r.amount_paid_in.saturating_add(r.fee_paid))?; Ok(result) } + fn ensure_remove_stake_input_within_swap_limit( + netuid: NetUid, + amount: AlphaBalance, + ) -> Result<(), Error> { + if !netuid.is_root() && SubnetMechanism::::get(netuid) == 1 { + let max_supported_input = SubnetAlphaIn::::get(netuid).saturating_mul(1_000.into()); + ensure!( + amount <= max_supported_input, + Error::::InsufficientLiquidity + ); + } + + Ok(()) + } + pub fn do_remove_stake_full_limit( origin: OriginFor, hotkey: T::AccountId, netuid: NetUid, limit_price: Option, ) -> DispatchResult { + ensure!(Self::if_subnet_exist(netuid), Error::::SubnetNotExists); let coldkey = ensure_signed(origin.clone())?; let alpha_unstaked = @@ -420,20 +445,44 @@ impl Pallet { } } - pub fn destroy_alpha_in_out_stakes(netuid: NetUid) -> DispatchResult { - // 1) Ensure the subnet exists. - ensure!(Self::if_subnet_exist(netuid), Error::::SubnetNotExists); + pub fn destroy_alpha_in_out_stakes( + netuid: NetUid, + weight_meter: &mut WeightMeter, + status: &mut DissolveCleanupStatus, + ) -> bool { + let Some(total_alpha_value_u128) = status.subnet_total_alpha_value else { + log::warn!("DissolveCleanupStatus.subnet_total_alpha_value not set"); + return false; + }; - // 2) Owner / lock cost. + let Some(mut distributed_tao_value_u128) = status.subnet_distributed_tao else { + log::warn!("DissolveCleanupStatus.subnet_distributed_tao not set"); + return false; + }; + + // Check if there is enought weight to complete all the operations in this function + // It is the maximum weight that can be consumed by the function. including all potential reads and writes. + let max_weight = T::DbWeight::get().reads_writes(20, 12); + if !weight_meter.can_consume(max_weight) { + return false; + } + weight_meter.consume(max_weight); let owner_coldkey: T::AccountId = SubnetOwner::::get(netuid); let lock_cost: TaoBalance = Self::get_subnet_locked_balance(netuid); // Determine if this subnet is eligible for a lock refund (legacy). let reg_at: u64 = NetworkRegisteredAt::::get(netuid); + let start_block: u64 = NetworkRegistrationStartBlock::::get(); let should_refund_owner: bool = reg_at < start_block; - // 3) Compute owner's received emission in TAO at current price (ONLY if we may refund). + let protocol_alpha_value_u128: u128 = + SubnetProtocolAlpha::::get(netuid).to_u64() as u128; + + let pot_tao: TaoBalance = SubnetTAO::::get(netuid); + let pot_u128: u128 = pot_tao.into(); + + // Compute owner's received emission in TAO at current price (ONLY if we may refund). // We: // - get the current alpha issuance, // - apply owner fraction to get owner α, @@ -464,39 +513,138 @@ impl Pallet { } } - // 4) Enumerate all α entries on this subnet to build distribution weights and cleanup lists. - // - collect keys to remove, - // - per (hot,cold) α VALUE (not shares) with fallback to raw share if pool uninitialized, - // - track hotkeys to clear pool totals. - let mut keys_to_remove: Vec<(T::AccountId, T::AccountId)> = Vec::new(); - let mut stakers: Vec<(T::AccountId, T::AccountId, u128)> = Vec::new(); + let mut protocol_tao_share = TaoBalance::ZERO; + if protocol_alpha_value_u128 > 0 { + let prod: u128 = pot_u128.saturating_mul(protocol_alpha_value_u128); + let share_u128: u128 = prod.checked_div(total_alpha_value_u128).unwrap_or_default(); + protocol_tao_share = (share_u128.min(u128::from(u64::MAX)) as u64).into(); + } - let tao_in_refund_deployment_block: u64 = TaoInRefundDeploymentBlock::::get(); + // Remove α‑in/α‑out counters (fully destroyed). + SubnetAlphaIn::::remove(netuid); + SubnetAlphaOut::::remove(netuid); + SubnetProtocolAlpha::::remove(netuid); + + // Clear the locked balance on the subnet. + Self::set_subnet_locked_balance(netuid, TaoBalance::ZERO); - // Legacy subnets keep the old dereg behavior: ignore SubnetAlphaIn. - // New subnets include SubnetAlphaIn. - let protocol_alpha_value_u128: u128 = if reg_at > tao_in_refund_deployment_block { - SubnetAlphaIn::::get(netuid) - .saturating_add(SubnetProtocolAlpha::::get(netuid)) - .to_u64() as u128 + // Finalize lock handling: + // - Legacy subnets (registered before NetworkRegistrationStartBlock) receive: + // refund = max(0, lock_cost(τ) − owner_received_emission_in_τ). + // - New subnets: no refund. + let mut refund: TaoBalance = if should_refund_owner { + lock_cost.saturating_sub(owner_emission_tao) } else { - SubnetProtocolAlpha::::get(netuid).to_u64() as u128 + TaoBalance::ZERO }; - let mut total_alpha_value_u128: u128 = protocol_alpha_value_u128; - let mut protocol_tao_share = TaoBalance::ZERO; + if !refund.is_zero() + && let Some(subnet_account) = Self::get_subnet_account_id(netuid) + { + // Transfer maximum transferrable up to refund to owner + let transferrable = + Self::get_coldkey_balance(&subnet_account).saturating_sub(protocol_tao_share); + + distributed_tao_value_u128 = distributed_tao_value_u128.saturating_add(refund.into()); + + if distributed_tao_value_u128 < pot_u128 { + let final_leftover: u128 = pot_u128.saturating_sub(distributed_tao_value_u128); + + refund = refund.saturating_add(final_leftover.into()); + } + // We do our best effort to refund owner to as full amount of refund as possible, but + // we cannot fail new subnet registration, so the result is ignored. + let _ = Self::transfer_tao(&subnet_account, &owner_coldkey, refund.min(transferrable)); + } + + // 9) Recycle TAO remaining on the subnet account, forgive errors. + if let Some(subnet_account) = Self::get_subnet_account_id(netuid) { + let remaining_subnet_balance = Self::get_keep_alive_balance(&subnet_account); + if Self::recycle_tao(&subnet_account, remaining_subnet_balance).is_ok() { + RAORecycledForRegistration::::insert(netuid, remaining_subnet_balance); + } + } + + status.subnet_total_alpha_value = None; + status.subnet_distributed_tao = None; + SubnetTAO::::remove(netuid); + + true + } + + /// This function calculates the total alpha value for a subnet. + /// It iterates through all hotkeys in the subnet and calculates the total alpha value. + /// It returns true if all hotkeys are iterated, otherwise false. + /// + /// # Args: + /// * 'netuid' (NetUid): + /// - The subnet to calculate the total alpha value for. + /// + /// * 'weight_meter' (WeightMeter): + /// - The weight meter to consume the weight for the operation. + /// + /// # Returns: + /// * 'bool': + /// - True if all hotkeys are iterated, otherwise false. + /// + pub fn destroy_alpha_in_out_stakes_get_total_alpha_value( + netuid: NetUid, + weight_meter: &mut WeightMeter, + last_key: Option>, + status: &mut DissolveCleanupStatus, + ) -> (bool, Option>) { + let r = T::DbWeight::get().reads(1); + let mut read_all = true; - let hotkeys_in_subnet: Vec = TotalHotkeyAlpha::::iter_keys() - .filter(|(_, this_netuid)| *this_netuid == netuid) - .map(|(hot, _)| hot.clone()) - .collect::>(); + let mut total_alpha_value_u128: u128; + + if let Some(value) = status.subnet_total_alpha_value { + total_alpha_value_u128 = value; + } else { + let reg_at: u64 = NetworkRegisteredAt::::get(netuid); + let tao_in_refund_deployment_block: u64 = TaoInRefundDeploymentBlock::::get(); + + // Legacy subnets keep the old dereg behavior: ignore SubnetAlphaIn. + // New subnets include SubnetAlphaIn. + let protocol_alpha_value_u128: u128 = if reg_at > tao_in_refund_deployment_block { + SubnetAlphaIn::::get(netuid) + .saturating_add(SubnetProtocolAlpha::::get(netuid)) + .to_u64() as u128 + } else { + SubnetProtocolAlpha::::get(netuid).to_u64() as u128 + }; + total_alpha_value_u128 = protocol_alpha_value_u128; + } + + let iter = match last_key { + Some(key) => TotalHotkeyAlpha::::iter_from(key), + None => TotalHotkeyAlpha::::iter(), + }; + + let mut last_hot = None; + + for (hot, this_netuid, _) in iter { + if !weight_meter.can_consume(r) { + read_all = false; + break; + } + weight_meter.consume(r); + + if this_netuid != netuid { + continue; + } + + let mut iterate_all = true; + for (cold, this_netuid, share_u64f64) in Self::alpha_iter_single_prefix(&hot) { + if !weight_meter.can_consume(r) { + iterate_all = false; + break; + } + weight_meter.consume(r); - for hot in hotkeys_in_subnet.iter() { - for (cold, this_netuid, share_u64f64) in Self::alpha_iter_single_prefix(hot) { if this_netuid != netuid { continue; } - keys_to_remove.push((hot.clone(), cold.clone())); // Primary: actual α value via share pool. let pool = Self::get_alpha_share_pool(hot.clone(), netuid); @@ -512,35 +660,146 @@ impl Pallet { if val_u64 > 0 { let val_u128 = val_u64 as u128; total_alpha_value_u128 = total_alpha_value_u128.saturating_add(val_u128); - stakers.push((hot.clone(), cold, val_u128)); } } + + if !iterate_all { + read_all = false; + break; + } else { + last_hot = Some(hot); + } } - // 5) Determine the TAO pot and pre-adjust accounting to avoid double counting. + status.subnet_total_alpha_value = Some(total_alpha_value_u128); + + ( + read_all, + last_hot.map(|hot| TotalHotkeyAlpha::::hashed_key_for(&hot, netuid)), + ) + } + + pub fn destroy_alpha_in_out_stakes_settle_stakes( + netuid: NetUid, + weight_meter: &mut WeightMeter, + last_key: Option>, + status: &mut DissolveCleanupStatus, + ) -> (bool, Option>) { + let r = T::DbWeight::get().reads(1); + let w = T::DbWeight::get().writes(1); + let weight_for_tansfer_tao = T::DbWeight::get().reads_writes(11, 3); + let mut read_all = true; + + let mut stakers: Vec<(T::AccountId, T::AccountId, u128)> = Vec::new(); + let Some(total_alpha_value_u128) = status.subnet_total_alpha_value else { + log::warn!("DissolveCleanupStatus.subnet_total_alpha_value not set"); + return (false, None); + }; + let Some(mut distributed_tao_value_u128) = status.subnet_distributed_tao else { + log::warn!("DissolveCleanupStatus.subnet_distributed_tao not set"); + return (false, None); + }; + + let mut hotkeys_in_subnet: Vec = Vec::new(); + let mut coldkeys = BTreeSet::::new(); + let mut last_hot = None; + + let iter = match last_key { + Some(key) => TotalHotkeyAlpha::::iter_from(key), + None => TotalHotkeyAlpha::::iter(), + }; + + for (hot, this_netuid, _) in iter { + if !weight_meter.can_consume(r) { + read_all = false; + break; + } + weight_meter.consume(r); + + if this_netuid != netuid { + continue; + } + hotkeys_in_subnet.push(hot.clone()); + + let mut inner_read_all = true; + let mut coldkey_value_vec: Vec<(T::AccountId, u128)> = Vec::new(); + + // Handle one hotkey and all its coldkeys or skip the hotkey if the weight is not enough + // Then we just need to record the hotkey as checkpoint + for (cold, this_netuid, share_u64f64) in Self::alpha_iter_single_prefix(&hot) { + if !weight_meter.can_consume(r.saturating_mul(2_u64)) { + inner_read_all = false; + break; + } + + weight_meter.consume(r.saturating_mul(2_u64)); + if this_netuid != netuid { + continue; + } + + // Primary: actual α value via share pool. + let pool = Self::get_alpha_share_pool(hot.clone(), netuid); + let actual_val_u64 = pool.try_get_value(&cold).unwrap_or(0); + + // Fallback: if pool uninitialized, treat raw Alpha share as value. + let val_u64 = if actual_val_u64 == 0 { + u64::from(share_u64f64) + } else { + actual_val_u64 + }; + + if val_u64 > 0 { + let mut need_to_consume_weight = w; + + // if the coldkey is not in the set, we need to consume the weight for the transfer_tao_from_subnet function call + if !coldkeys.contains(&cold) { + need_to_consume_weight = + need_to_consume_weight.saturating_add(weight_for_tansfer_tao); + coldkeys.insert(cold.clone()); + } + + // reserve the weight for the add_balance_to_coldkey_account function call later + if !weight_meter.can_consume(need_to_consume_weight) { + inner_read_all = false; + last_hot = Some(hot.clone()); + break; + } + weight_meter.consume(need_to_consume_weight); + let val_u128 = val_u64 as u128; + coldkey_value_vec.push((cold.clone(), val_u128)); + } + } + + if !inner_read_all { + read_all = false; + break; + } else { + for (cold, value) in coldkey_value_vec { + stakers.push((hot.clone(), cold, value)); + } + last_hot = Some(hot.clone()); + } + } + + // total TAO in the subnet pool let pot_tao: TaoBalance = SubnetTAO::::get(netuid); let pot_u64: u64 = pot_tao.into(); - if pot_u64 > 0 { - SubnetTAO::::remove(netuid); - TotalStake::::mutate(|total| *total = total.saturating_sub(pot_tao)); + struct Portion { + _hot: A, + cold: C, + share: u64, // TAO to credit to coldkey balance + rem: u128, // remainder for largest‑remainder method } + let mut portions: Vec> = Vec::with_capacity(stakers.len()); - // 6) Pro‑rata distribution of the pot by α value (largest‑remainder), + // Pro‑rata distribution of the pot by α value (largest‑remainder), // **credited directly to each staker's COLDKEY free balance**. - if pot_u64 > 0 && total_alpha_value_u128 > 0 { - struct Portion { - _hot: A, - cold: C, - is_protocol: bool, - share: u64, // TAO to credit to coldkey balance - rem: u128, // remainder for largest‑remainder method - } - + if pot_u64 > 0 && total_alpha_value_u128 > 0 && !stakers.is_empty() { let pot_u128: u128 = pot_u64 as u128; - let mut portions: Vec> = - Vec::with_capacity(stakers.len().saturating_add(1)); + let mut distributed: u128 = 0; + let mut total_rem: u128 = 0; for (hot, cold, alpha_val) in &stakers { let prod: u128 = pot_u128.saturating_mul(*alpha_val); @@ -549,31 +808,18 @@ impl Pallet { distributed = distributed.saturating_add(u128::from(share_u64)); let rem: u128 = prod.checked_rem(total_alpha_value_u128).unwrap_or_default(); + total_rem = total_rem.saturating_add(rem); portions.push(Portion { _hot: hot.clone(), cold: cold.clone(), - is_protocol: false, - share: share_u64, - rem, - }); - } - - if protocol_alpha_value_u128 > 0 { - let prod: u128 = pot_u128.saturating_mul(protocol_alpha_value_u128); - let share_u128: u128 = prod.checked_div(total_alpha_value_u128).unwrap_or_default(); - let share_u64: u64 = share_u128.min(u128::from(u64::MAX)) as u64; - distributed = distributed.saturating_add(u128::from(share_u64)); - let rem: u128 = prod.checked_rem(total_alpha_value_u128).unwrap_or_default(); - portions.push(Portion { - _hot: owner_coldkey.clone(), - cold: owner_coldkey.clone(), - is_protocol: true, share: share_u64, rem, }); } - let leftover: u128 = pot_u128.saturating_sub(distributed); + let leftover: u128 = total_rem + .checked_div(total_alpha_value_u128) + .unwrap_or_default(); if leftover > 0 { portions.sort_by(|a, b| b.rem.cmp(&a.rem)); let give: usize = core::cmp::min(leftover, portions.len() as u128) as usize; @@ -582,69 +828,193 @@ impl Pallet { } } - // Credit each share directly to coldkey free balance. + portions = portions + .into_iter() + .filter(|p| p.share > 0) + .collect::>(); + + // Aggregate the transfer amount for each coldkey + let mut transfer_map = BTreeMap::::new(); for p in portions { - if p.is_protocol { - protocol_tao_share = protocol_tao_share.saturating_add(p.share.into()); - } else if p.share > 0 { - // Cannot fail the whole transaction if this transfer fails - let _ = Self::transfer_tao_from_subnet(netuid, &p.cold, p.share.into()); + if transfer_map.contains_key(&p.cold) { + transfer_map.insert( + p.cold.clone(), + transfer_map + .get(&p.cold) + .unwrap_or(&TaoBalance::ZERO) + .saturating_add(p.share.into()), + ); + } else { + transfer_map.insert(p.cold.clone(), p.share.into()); } } - } - // 7) Destroy all α-in/α-out state for this subnet. - // 7.a) Remove every (hot, cold, netuid) α entry. - for (hot, cold) in keys_to_remove { - Alpha::::remove((hot.clone(), cold.clone(), netuid)); - AlphaV2::::remove((hot, cold, netuid)); - } - // 7.b) Clear share‑pool totals for each hotkey on this subnet. - for hot in hotkeys_in_subnet { - TotalHotkeyAlpha::::remove(&hot, netuid); - TotalHotkeyShares::::remove(&hot, netuid); - TotalHotkeySharesV2::::remove(&hot, netuid); + // Credit each share directly to coldkey free balance. + for transfer in transfer_map.iter() { + // Cannot fail the whole transaction if this transfer fails + distributed_tao_value_u128 = distributed_tao_value_u128 + .saturating_add(transfer.1.to_u128().unwrap_or(0_u128)); + let _ = Self::transfer_tao_from_subnet(netuid, transfer.0, *transfer.1); + } } - // 7.c) Remove α‑in/α‑out counters (fully destroyed). - SubnetAlphaIn::::remove(netuid); - SubnetAlphaOut::::remove(netuid); - SubnetProtocolAlpha::::remove(netuid); - // Clear the locked balance on the subnet. - Self::set_subnet_locked_balance(netuid, TaoBalance::ZERO); + // ignore the weight for handling the final operation, we must set the correct status for the next run + status.subnet_distributed_tao = Some(distributed_tao_value_u128); - // 8) Finalize lock handling: - // - Legacy subnets (registered before NetworkRegistrationStartBlock) receive: - // refund = max(0, lock_cost(τ) − owner_received_emission_in_τ). - // - New subnets: no refund. - let refund: TaoBalance = if should_refund_owner { - lock_cost.saturating_sub(owner_emission_tao) - } else { - TaoBalance::ZERO + ( + read_all, + last_hot.map(|hot| TotalHotkeyAlpha::::hashed_key_for(&hot, netuid)), + ) + } + + pub fn destroy_alpha_in_out_stakes_clean_alpha( + netuid: NetUid, + weight_meter: &mut WeightMeter, + last_key: Option>, + ) -> (bool, Option>) { + let r = T::DbWeight::get().reads(1); + let w = T::DbWeight::get().writes(1); + let mut read_all = true; + + let iter = match last_key { + Some(key) => TotalHotkeyAlpha::::iter_from(key), + None => TotalHotkeyAlpha::::iter(), }; - if !refund.is_zero() - && let Some(subnet_account) = Self::get_subnet_account_id(netuid) - { - // Transfer maximum transferrable up to refund to owner - let transferrable = - Self::get_coldkey_balance(&subnet_account).saturating_sub(protocol_tao_share); - // We do our best effort to refund owner to as full amount of refund as possible, but - // we cannot fail new subnet registration, so the result is ignored. - let _ = Self::transfer_tao(&subnet_account, &owner_coldkey, refund.min(transferrable)); - } + let mut last_hot = None; - // 9) Recycle TAO remaining on the subnet account, forgive errors. - if let Some(subnet_account) = Self::get_subnet_account_id(netuid) { - let remaining_subnet_balance = Self::get_keep_alive_balance(&subnet_account); - if Self::recycle_tao(&subnet_account, remaining_subnet_balance).is_ok() { - RAORecycledForRegistration::::insert(netuid, remaining_subnet_balance); + for (hot, this_netuid, _) in iter { + let mut coldkeys: Vec = Vec::new(); + if !weight_meter.can_consume(r) { + read_all = false; + break; + } + weight_meter.consume(r); + + if this_netuid != netuid { + continue; + } + + let mut iterate_all = true; + // handle all coldkeys for the hotkey as transactional, it is overdesigned to record two layers of checkpoints + for (cold, this_netuid, _) in Self::alpha_iter_single_prefix(&hot) { + if !weight_meter.can_consume(r) { + read_all = false; + iterate_all = false; + break; + } + weight_meter.consume(r); + if this_netuid != netuid { + continue; + } + coldkeys.push(cold.clone()); + } + + if !iterate_all { + read_all = false; + break; + } + + let weight_for_all_remove = w.saturating_mul(coldkeys.len() as u64); + + if !weight_meter.can_consume(weight_for_all_remove) { + read_all = false; + break; + } + weight_meter.consume(weight_for_all_remove); + + last_hot = Some(hot.clone()); + + for cold in coldkeys { + Alpha::::remove((&hot, &cold, netuid)); + AlphaV2::::remove((&hot, &cold, netuid)); } } - // 10) Cleanup all subnet stake locks and lock aggregates if any. - Self::destroy_lock_maps(netuid); + ( + read_all, + last_hot.map(|hot| TotalHotkeyAlpha::::hashed_key_for(&hot, netuid)), + ) + } - Ok(()) + pub fn destroy_alpha_in_out_stakes_clear_hotkey_totals( + netuid: NetUid, + weight_meter: &mut WeightMeter, + last_key: Option>, + ) -> (bool, Option>) { + let iter = match last_key { + Some(key) => TotalHotkeyAlpha::::iter_from(key), + None => TotalHotkeyAlpha::::iter(), + }; + + let (read_all, last_item) = Self::remove_storage_entries_for_netuid( + weight_meter, + iter, + |(_, nu, _)| *nu == netuid, + |(hotkey, _, _)| hotkey, + |hotkey| { + TotalHotkeyAlpha::::remove(hotkey, netuid); + TotalHotkeyShares::::remove(hotkey, netuid); + TotalHotkeySharesV2::::remove(hotkey, netuid); + }, + 3, + ); + + ( + read_all, + last_item.map(|(hotkey, nu, _)| TotalHotkeyAlpha::::hashed_key_for(&hotkey, nu)), + ) + } + + pub fn destroy_alpha_in_out_stakes_clear_locks( + netuid: NetUid, + weight_meter: &mut WeightMeter, + last_key: Option>, + ) -> (bool, Option>) { + let iter = match last_key { + Some(key) => Lock::::iter_from(key), + None => Lock::::iter(), + }; + + let (read_all, last_item) = Self::remove_storage_entries_for_netuid( + weight_meter, + iter, + |((_, this_netuid, _), _)| *this_netuid == netuid, + |((coldkey, _this_netuid, hotkey), _)| (coldkey, hotkey), + |(coldkey, hotkey)| Lock::::remove((coldkey.clone(), netuid, hotkey.clone())), + 1, + ); + + ( + read_all, + last_item.map(|((coldkey, _, hotkey), _)| { + Lock::::hashed_key_for((&coldkey, netuid, &hotkey)) + }), + ) + } + + pub fn destroy_alpha_in_out_stakes_clear_decaying_locks( + netuid: NetUid, + weight_meter: &mut WeightMeter, + last_key: Option>, + ) -> (bool, Option>) { + let iter = match last_key { + Some(key) => DecayingLock::::iter_from(key), + None => DecayingLock::::iter(), + }; + + let (read_all, last_item) = Self::remove_storage_entries_for_netuid( + weight_meter, + iter, + |(_, this_netuid, _)| *this_netuid == netuid, + |(coldkey, _, _)| coldkey, + |coldkey| DecayingLock::::remove(coldkey, netuid), + 1, + ); + + ( + read_all, + last_item.map(|(coldkey, nu, _)| DecayingLock::::hashed_key_for(&coldkey, nu)), + ) } } diff --git a/pallets/subtensor/src/staking/set_children.rs b/pallets/subtensor/src/staking/set_children.rs index 2bd2cf1b95..218a415d80 100644 --- a/pallets/subtensor/src/staking/set_children.rs +++ b/pallets/subtensor/src/staking/set_children.rs @@ -733,6 +733,8 @@ impl Pallet { Error::::NonAssociatedColdKey ); + ensure!(Self::if_subnet_exist(netuid), Error::::SubnetNotExists); + // Ensure the take value is valid ensure!( take >= Self::get_effective_min_childkey_take(netuid) diff --git a/pallets/subtensor/src/subnets/dissolution.rs b/pallets/subtensor/src/subnets/dissolution.rs new file mode 100644 index 0000000000..c5885c167d --- /dev/null +++ b/pallets/subtensor/src/subnets/dissolution.rs @@ -0,0 +1,997 @@ +use super::*; +use frame_support::weights::WeightMeter; +use subtensor_runtime_common::{NetUid, NetUidStorageIndex, clear_prefix_with_meter}; +use subtensor_swap_interface::SwapHandler; +/// Enum for the dissolve cleanup phase. +#[derive(Encode, Decode, TypeInfo, Clone, PartialEq, Eq, Debug, DecodeWithMemTracking)] +pub enum DissolveCleanupPhase { + /// Phase 1.1: Remove root dividend claimable entries for the subnet. + SubnetRootDividendsRootClaimable, + /// Phase 1.2: Remove root dividend claimed entries for the subnet. + SubnetRootDividendsRootClaimed, + /// Phase 2.1: Get the total alpha value for the subnet. + AlphaInOutStakesGetTotalAlphaValue, + /// Phase 2.2: Destroy alpha in and out stakes for the subnet. + AlphaInOutStakesSettleStakes, + /// Phase 2.3: Clean alpha entries for the subnet. + AlphaInOutStakesAlpha, + /// Phase 2.4: Clear hotkey totals for the subnet. + AlphaInOutStakesHotkeyTotals, + /// Phase 2.5: Clear locks for the subnet. + AlphaInOutStakesLocks, + /// Phase 2.6: Clear locks for the subnet. + AlphaInOutStakesDecayingLocks, + /// Phase 2.7: Destroy alpha in and out stakes for the subnet. + AlphaInOutStakes, + /// Phase 3: Clear protocol liquidity for the subnet on the swap layer. + ProtocolLiquidity, + /// Phase 4: Remove scalar `Network*` parameters, then continue with map and index cleanup phases. + PurgeNetuid, + /// Phase 5.1: Remove is network member entries for the subnet. + NetworkIsNetworkMember, + /// Phase 5.2: Recovery / legacy: scalar `Network*` removal; the hook advances to map cleanup like `PurgeNetuid` after `remove_network_parameters` completes. + NetworkParameters, + /// Phase 5.3: Remove map-backed subnet storage (keys, axons, per-mechanism weights, etc.). + NetworkMapParameters, + /// Phase 5.4: Clear root-network weight entries referencing this netuid. + NetworkUpdateWeightsOnRoot, + /// Phase 5.5: Remove childkey take entries for this netuid. + NetworkChildkeyTake, + /// Phase 5.6: Remove child key bindings for this netuid. + NetworkChildkeys, + /// Phase 5.7: Remove parent key bindings for this netuid. + NetworkParentkeys, + /// Phase 5.8: Remove last hotkey emission records for this netuid. + NetworkLastHotkeyEmissionOnNetuid, + /// Phase 5.9: Remove total hotkey alpha last epoch entries for this netuid. + NetworkTotalHotkeyAlphaLastEpoch, + /// Phase 5.10: Remove transaction key last-block rate limit entries for this netuid. + NetworkTransactionKeyLastBlock, + /// Phase 5.11: Remove lock entries for this netuid. + NetworkLock, + /// Phase 5.12: Remove decaying lock entries for this netuid. + NetworkDecayingLock, +} + +impl Default for DissolveCleanupPhase { + fn default() -> Self { + Self::SubnetRootDividendsRootClaimable + } +} + +#[crate::freeze_struct("c524ea54893ae91a")] +#[derive(Encode, Decode, TypeInfo, Clone, PartialEq, Eq, Debug, DecodeWithMemTracking)] +pub struct DissolveCleanupStatus { + pub netuid: NetUid, + pub phase: DissolveCleanupPhase, + pub last_key: Option>, + pub subnet_total_alpha_value: Option, + pub subnet_distributed_tao: Option, +} + +impl DissolveCleanupStatus { + pub fn new(netuid: NetUid) -> Self { + Self { + netuid, + phase: DissolveCleanupPhase::default(), + last_key: None, + subnet_total_alpha_value: None, + subnet_distributed_tao: None, + } + } + + pub fn set_phase(&mut self, phase: DissolveCleanupPhase) { + self.phase = phase; + } +} + +impl Pallet { + /// Facilitates the removal of a user's subnetwork. + /// + /// # Args: + /// * 'origin': ('T::RuntimeOrigin'): The calling origin. Must be signed. + /// * 'netuid': ('u16'): The unique identifier of the network to be removed. + /// + /// # Event: + /// * 'NetworkRemoved': Emitted when a network is successfully removed. + /// + /// # Raises: + /// * 'MechanismDoesNotExist': If the specified network does not exist. + /// * 'NotSubnetOwner': If the caller does not own the specified subnet. + /// + pub fn do_dissolve_network(netuid: NetUid) -> dispatch::DispatchResult { + // --- The network exists? + ensure!( + Self::if_subnet_exist(netuid) && netuid != NetUid::ROOT, + Error::::SubnetNotExists + ); + + let mut dissolved_networks = DissolveCleanupQueue::::get(); + ensure!( + !dissolved_networks.contains(&netuid), + Error::::NetworkDissolveAlreadyQueued + ); + + // Just remove the network from the added networks, it is used to check if the network is existed. + NetworksAdded::::remove(netuid); + // Reduce the total networks count. + TotalNetworks::::mutate(|n: &mut u16| *n = n.saturating_sub(1)); + TotalStake::::mutate(|total| *total = total.saturating_sub(SubnetTAO::::get(netuid))); + + dissolved_networks.push(netuid); + DissolveCleanupQueue::::set(dissolved_networks); + + log::debug!("NetworkRemoved( netuid:{netuid:?} )"); + + // --- Emit the NetworkRemoved event + Self::deposit_event(Event::NetworkRemoved(netuid)); + + Ok(()) + } + + pub fn remove_network_map_parameters(netuid: NetUid, weight_meter: &mut WeightMeter) -> bool { + let write_weight = T::DbWeight::get().writes(1); + + let result = clear_prefix_with_meter(weight_meter, write_weight, |limit| { + Keys::::clear_prefix(netuid, limit, None) + }) && clear_prefix_with_meter(weight_meter, write_weight, |limit| { + Uids::::clear_prefix(netuid, limit, None) + }) && clear_prefix_with_meter(weight_meter, write_weight, |limit| { + BlockAtRegistration::::clear_prefix(netuid, limit, None) + }) && clear_prefix_with_meter(weight_meter, write_weight, |limit| { + Axons::::clear_prefix(netuid, limit, None) + }) && clear_prefix_with_meter(weight_meter, write_weight, |limit| { + NeuronCertificates::::clear_prefix(netuid, limit, None) + }) && clear_prefix_with_meter(weight_meter, write_weight, |limit| { + Prometheus::::clear_prefix(netuid, limit, None) + }) && clear_prefix_with_meter(weight_meter, write_weight, |limit| { + AlphaDividendsPerSubnet::::clear_prefix(netuid, limit, None) + }) && clear_prefix_with_meter(weight_meter, write_weight, |limit| { + PendingChildKeys::::clear_prefix(netuid, limit, None) + }) && clear_prefix_with_meter(weight_meter, write_weight, |limit| { + AssociatedEvmAddress::::clear_prefix(netuid, limit, None) + }) && clear_prefix_with_meter(weight_meter, write_weight, |limit| { + AssociatedUidsByEvmAddress::::clear_prefix(netuid, limit, None) + }) && clear_prefix_with_meter(weight_meter, write_weight, |limit| { + HotkeyLock::::clear_prefix(netuid, limit, None) + }) && clear_prefix_with_meter(weight_meter, write_weight, |limit| { + DecayingHotkeyLock::::clear_prefix(netuid, limit, None) + }) && clear_prefix_with_meter(weight_meter, write_weight, |limit| { + LockingColdkeys::::clear_prefix((netuid,), limit, None) + }); + + if !result { + return false; + } + + let read_weight = T::DbWeight::get().reads(1); + if !weight_meter.can_consume(read_weight) { + return false; + } + weight_meter.consume(read_weight); + let mechanisms: u8 = MechanismCountCurrent::::get(netuid).into(); + + for subid in 0..mechanisms { + let mechanism_weight = T::DbWeight::get().reads_writes(1, 2); + if !weight_meter.can_consume(mechanism_weight) { + return false; + } + weight_meter.consume(mechanism_weight); + let netuid_index = Self::get_mechanism_storage_index(netuid, subid.into()); + + LastUpdate::::remove(netuid_index); + Incentive::::remove(netuid_index); + + let result = clear_prefix_with_meter(weight_meter, write_weight, |limit| { + WeightCommits::::clear_prefix(netuid_index, limit, None) + }) && clear_prefix_with_meter(weight_meter, write_weight, |limit| { + TimelockedWeightCommits::::clear_prefix(netuid_index, limit, None) + }) && clear_prefix_with_meter(weight_meter, write_weight, |limit| { + CRV3WeightCommits::::clear_prefix(netuid_index, limit, None) + }) && clear_prefix_with_meter(weight_meter, write_weight, |limit| { + CRV3WeightCommitsV2::::clear_prefix(netuid_index, limit, None) + }) && clear_prefix_with_meter(weight_meter, write_weight, |limit| { + Bonds::::clear_prefix(netuid_index, limit, None) + }) && clear_prefix_with_meter(weight_meter, write_weight, |limit| { + Weights::::clear_prefix(netuid_index, limit, None) + }); + + if !result { + return false; + } + } + + let removal_weight = T::DbWeight::get().writes(3); + if !weight_meter.can_consume(removal_weight) { + return false; + } + weight_meter.consume(removal_weight); + RevealPeriodEpochs::::remove(netuid); + MechanismCountCurrent::::remove(netuid); + MechanismEmissionSplit::::remove(netuid); + + if !clear_prefix_with_meter(weight_meter, write_weight, |limit| { + LastHotkeySwapOnNetuid::::clear_prefix(netuid, limit, None) + }) { + return false; + } + + if let Some(lease_id) = SubnetUidToLeaseId::::get(netuid) { + if !clear_prefix_with_meter(weight_meter, write_weight, |limit| { + SubnetLeaseShares::::clear_prefix(lease_id, limit, None) + }) { + return false; + } + let lease_weight = T::DbWeight::get().writes(3); + if !weight_meter.can_consume(lease_weight) { + return false; + } + weight_meter.consume(lease_weight); + SubnetLeases::::remove(lease_id); + AccumulatedLeaseDividends::::remove(lease_id); + SubnetUidToLeaseId::::remove(netuid); + } + + true + } + + pub fn remove_network_parameters(netuid: NetUid, weight_meter: &mut WeightMeter) -> bool { + let removal_weight = T::DbWeight::get().writes(80); + if !weight_meter.can_consume(removal_weight) { + return false; + } + weight_meter.consume(removal_weight); + SubnetOwner::::remove(netuid); + SubnetworkN::::remove(netuid); + NetworkRegisteredAt::::remove(netuid); + Active::::remove(netuid); + Emission::::remove(netuid); + Consensus::::remove(netuid); + Dividends::::remove(netuid); + ValidatorPermit::::remove(netuid); + ValidatorTrust::::remove(netuid); + Tempo::::remove(netuid); + Kappa::::remove(netuid); + Difficulty::::remove(netuid); + MaxAllowedUids::::remove(netuid); + ImmunityPeriod::::remove(netuid); + ActivityCutoff::::remove(netuid); + MinAllowedWeights::::remove(netuid); + RegistrationsThisInterval::::remove(netuid); + POWRegistrationsThisInterval::::remove(netuid); + BurnRegistrationsThisInterval::::remove(netuid); + SubnetAlphaInEmission::::remove(netuid); + SubnetAlphaOutEmission::::remove(netuid); + SubnetTaoInEmission::::remove(netuid); + SubnetVolume::::remove(netuid); + SubnetMovingPrice::::remove(netuid); + SubnetTaoFlow::::remove(netuid); + SubnetEmaTaoFlow::::remove(netuid); + SubnetProtocolFlow::::remove(netuid); + SubnetEmaProtocolFlow::::remove(netuid); + SubnetExcessTao::::remove(netuid); + SubnetRootSellTao::::remove(netuid); + TokenSymbol::::remove(netuid); + SubnetMechanism::::remove(netuid); + SubnetOwnerHotkey::::remove(netuid); + NetworkRegistrationAllowed::::remove(netuid); + NetworkPowRegistrationAllowed::::remove(netuid); + TransferToggle::::remove(netuid); + SubnetLocked::::remove(netuid); + LargestLocked::::remove(netuid); + FirstEmissionBlockNumber::::remove(netuid); + PendingValidatorEmission::::remove(netuid); + PendingServerEmission::::remove(netuid); + PendingRootAlphaDivs::::remove(netuid); + PendingOwnerCut::::remove(netuid); + MinerBurned::::remove(netuid); + BlocksSinceLastStep::::remove(netuid); + LastMechansimStepBlock::::remove(netuid); + LastAdjustmentBlock::::remove(netuid); + ServingRateLimit::::remove(netuid); + Rho::::remove(netuid); + AlphaSigmoidSteepness::::remove(netuid); + MaxAllowedValidators::::remove(netuid); + BondsMovingAverage::::remove(netuid); + BondsPenalty::::remove(netuid); + BondsResetOn::::remove(netuid); + WeightsSetRateLimit::::remove(netuid); + ValidatorPruneLen::::remove(netuid); + ScalingLawPower::::remove(netuid); + TargetRegistrationsPerInterval::::remove(netuid); + CommitRevealWeightsEnabled::::remove(netuid); + BurnHalfLife::::remove(netuid); + BurnIncreaseMult::::remove(netuid); + Burn::::remove(netuid); + MinBurn::::remove(netuid); + MaxBurn::::remove(netuid); + MinDifficulty::::remove(netuid); + MaxDifficulty::::remove(netuid); + RegistrationsThisBlock::::remove(netuid); + EMAPriceHalvingBlocks::::remove(netuid); + RAORecycledForRegistration::::remove(netuid); + MaxRegistrationsPerBlock::::remove(netuid); + WeightsVersionKey::::remove(netuid); + LiquidAlphaOn::::remove(netuid); + Yuma3On::::remove(netuid); + AlphaValues::::remove(netuid); + SubtokenEnabled::::remove(netuid); + OwnerCutAutoLockEnabled::::remove(netuid); + ImmuneOwnerUidsLimit::::remove(netuid); + StakeWeight::::remove(netuid); + LoadedEmission::::remove(netuid); + OwnerLock::::remove(netuid); + DecayingOwnerLock::::remove(netuid); + ActivityCutoffFactorMilli::::remove(netuid); + LastEpochBlock::::remove(netuid); + PendingEpochAt::::remove(netuid); + SubnetEpochIndex::::remove(netuid); + + if SubnetIdentitiesV3::::contains_key(netuid) { + SubnetIdentitiesV3::::remove(netuid); + Self::deposit_event(Event::SubnetIdentityRemoved(netuid)); + } + true + } + + pub fn remove_network_is_network_member( + netuid: NetUid, + weight_meter: &mut WeightMeter, + last_key: Option>, + ) -> (bool, Option>) { + let iter = match last_key { + Some(raw_key) => Keys::::iter_from(raw_key), + None => Keys::::iter(), + }; + + let (read_all, last_item) = Self::remove_storage_entries_for_netuid( + weight_meter, + iter, + |(nu, _, _)| *nu == netuid, + |(_, _, hotkey)| hotkey, + |hotkey| IsNetworkMember::::remove(hotkey, netuid), + 1, + ); + + ( + read_all, + last_item.map(|(nu, uid, _)| Keys::::hashed_key_for(nu, uid)), + ) + } + + pub fn remove_network_update_weights_on_root( + netuid: NetUid, + weight_meter: &mut WeightMeter, + last_key: Option>, + ) -> (bool, Option>) { + let netuid_u16 = u16::from(netuid); + + let root = NetUidStorageIndex::ROOT; + let iter = match last_key { + Some(raw_key) => Weights::::iter_prefix_from(root, raw_key), + None => Weights::::iter_prefix(root), + }; + + fn filter_weights(netuid_u16: u16, weights: &[(u16, u16)]) -> (bool, Vec<(u16, u16)>) { + let mut need_update = false; + let mut filtered_weights = weights.to_vec(); + for (subnet_id, weight) in filtered_weights.iter_mut() { + if *subnet_id == netuid_u16 && *weight != 0 { + need_update = true; + *weight = 0; + } + } + (need_update, filtered_weights) + } + + let (read_all, last_item) = Self::remove_storage_entries_for_netuid( + weight_meter, + iter, + |_| true, + |(uid, weights)| (uid, weights), + |(uid, weights)| { + let (update, filtered_weights) = filter_weights(netuid_u16, weights); + if update { + Weights::::insert(root, *uid, filtered_weights); + } + }, + 1, + ); + + ( + read_all, + last_item.map(|key| Weights::::hashed_key_for(root, key.0)), + ) + } + + pub fn remove_network_childkey_take( + netuid: NetUid, + weight_meter: &mut WeightMeter, + last_key: Option>, + ) -> (bool, Option>) { + let iter = match last_key { + Some(raw_key) => ChildkeyTake::::iter_from(raw_key), + None => ChildkeyTake::::iter(), + }; + + let (read_all, last_item) = Self::remove_storage_entries_for_netuid( + weight_meter, + iter, + |(_, nu, _)| *nu == netuid, + |(hot, _, _)| hot, + |hot| ChildkeyTake::::remove(hot, netuid), + 1, + ); + + ( + read_all, + last_item.map(|(hot, nu, _)| ChildkeyTake::::hashed_key_for(&hot, nu)), + ) + } + + pub fn remove_network_childkeys( + netuid: NetUid, + weight_meter: &mut WeightMeter, + last_key: Option>, + ) -> (bool, Option>) { + let iter = match last_key { + Some(raw_key) => ChildKeys::::iter_from(raw_key), + None => ChildKeys::::iter(), + }; + + let (read_all, last_item) = Self::remove_storage_entries_for_netuid( + weight_meter, + iter, + |(_, nu, _)| *nu == netuid, + |(hot, _, _)| hot, + |hot| ChildKeys::::remove(hot, netuid), + 1, + ); + + ( + read_all, + last_item.map(|key| ChildKeys::::hashed_key_for(&key.0, key.1)), + ) + } + + pub fn remove_network_parentkeys( + netuid: NetUid, + weight_meter: &mut WeightMeter, + last_key: Option>, + ) -> (bool, Option>) { + let iter = match last_key { + Some(raw_key) => ParentKeys::::iter_from(raw_key), + None => ParentKeys::::iter(), + }; + + let (read_all, last_item) = Self::remove_storage_entries_for_netuid( + weight_meter, + iter, + |(_, nu, _)| *nu == netuid, + |(hot, _, _)| hot, + |hot| ParentKeys::::remove(hot, netuid), + 1, + ); + + ( + read_all, + last_item.map(|key| ParentKeys::::hashed_key_for(&key.0, key.1)), + ) + } + + pub fn remove_network_last_hotkey_emission_on_netuid( + netuid: NetUid, + weight_meter: &mut WeightMeter, + last_key: Option>, + ) -> (bool, Option>) { + let iter = match last_key { + Some(raw_key) => LastHotkeyEmissionOnNetuid::::iter_from(raw_key), + None => LastHotkeyEmissionOnNetuid::::iter(), + }; + + let (read_all, last_item) = Self::remove_storage_entries_for_netuid( + weight_meter, + iter, + |(_, nu, _)| *nu == netuid, + |(hot, _, _)| hot, + |hot| LastHotkeyEmissionOnNetuid::::remove(hot, netuid), + 1, + ); + + ( + read_all, + last_item.map(|key| LastHotkeyEmissionOnNetuid::::hashed_key_for(&key.0, key.1)), + ) + } + + pub fn remove_network_total_hotkey_alpha_last_epoch( + netuid: NetUid, + weight_meter: &mut WeightMeter, + last_key: Option>, + ) -> (bool, Option>) { + let iter = match last_key { + Some(raw_key) => TotalHotkeyAlphaLastEpoch::::iter_from(raw_key), + None => TotalHotkeyAlphaLastEpoch::::iter(), + }; + + let (read_all, last_item) = Self::remove_storage_entries_for_netuid( + weight_meter, + iter, + |(_, nu, _)| *nu == netuid, + |(hot, _, _)| hot, + |hot| TotalHotkeyAlphaLastEpoch::::remove(hot, netuid), + 1, + ); + + ( + read_all, + last_item.map(|(hot, nu, _)| TotalHotkeyAlphaLastEpoch::::hashed_key_for(&hot, nu)), + ) + } + + pub fn remove_network_transaction_key_last_block( + netuid: NetUid, + weight_meter: &mut WeightMeter, + last_key: Option>, + ) -> (bool, Option>) { + let iter = match last_key { + Some(raw_key) => TransactionKeyLastBlock::::iter_from(raw_key), + None => TransactionKeyLastBlock::::iter(), + }; + + let (read_all, last_item) = Self::remove_storage_entries_for_netuid( + weight_meter, + iter, + |((_, nu, _), _)| *nu == netuid, + |((hot, _, name), _)| (hot, name), + |(hot, name)| TransactionKeyLastBlock::::remove((hot.clone(), netuid, *name)), + 1, + ); + + ( + read_all, + last_item.map(|((hot, _, name), _)| { + TransactionKeyLastBlock::::hashed_key_for((&hot, netuid, name)) + }), + ) + } + + pub fn remove_data_for_dissolved_networks(remaining_weight: Weight) -> Weight { + let w = T::DbWeight::get().writes(1); + let r = T::DbWeight::get().reads(1); + let mut weight_meter = frame_support::weights::WeightMeter::with_limit(remaining_weight); + + // complete unfinished network cleanup at first if any + if let Some(mut status) = CurrentDissolveCleanupStatus::::get() { + let (cleanup_completed, weight) = + Self::clean_up_data_for_one_dissolved_network(&mut weight_meter, &mut status); + if cleanup_completed { + DissolveCleanupQueue::::mutate(|queue| { + queue.retain(|queued_netuid| *queued_netuid != status.netuid); + }); + CurrentDissolveCleanupStatus::::kill(); + return weight.saturating_add(T::DbWeight::get().writes(2)); + } + return weight; + } + + if !weight_meter.can_consume(r) { + return weight_meter.consumed(); + } + weight_meter.consume(r); + + let dissolved_networks = DissolveCleanupQueue::::get(); + if let Some(netuid) = dissolved_networks.first() { + if !weight_meter.can_consume(w) { + return weight_meter.consumed(); + } + weight_meter.consume(w); + + let mut status = DissolveCleanupStatus::new(*netuid); + CurrentDissolveCleanupStatus::::set(Some(status.clone())); + + let (cleanup_completed, _weight) = + Self::clean_up_data_for_one_dissolved_network(&mut weight_meter, &mut status); + + if cleanup_completed { + DissolveCleanupQueue::::mutate(|queue| { + queue.retain(|queued_netuid| *queued_netuid != status.netuid); + }); + CurrentDissolveCleanupStatus::::kill(); + weight_meter.consume(T::DbWeight::get().writes(2)); + } + } + + weight_meter.consumed() + } + + // try use all weight available to clean up data for one dissolved network based on the status + pub fn clean_up_data_for_one_dissolved_network( + weight_meter: &mut WeightMeter, + status: &mut DissolveCleanupStatus, + ) -> (bool, Weight) { + let r = T::DbWeight::get().reads(1); + + let netuid = status.netuid; + + if !weight_meter.can_consume(r) { + return (false, weight_meter.consumed()); + } + + // if one phase is done or exit because of weight limit + let mut phase_done = true; + let mut cleanup_completed = false; + // only reason for phase_done to be false is if the weight limit is reached + while phase_done { + // let phase = status.phase.clone(); + log::debug!( + "dissolved_networks phase: {:?} for netuid: {:?}", + &status.phase, + netuid + ); + + let done = match &status.phase { + DissolveCleanupPhase::SubnetRootDividendsRootClaimable => { + let (done, new_key) = Self::clean_up_root_claimable_for_subnet( + netuid, + weight_meter, + status.last_key.clone(), + ); + + if done { + status.set_phase(DissolveCleanupPhase::SubnetRootDividendsRootClaimed); + status.last_key = None; + } else { + status.last_key = new_key; + } + done + } + + DissolveCleanupPhase::SubnetRootDividendsRootClaimed => { + let done = Self::clean_up_root_claimed_for_subnet(netuid, weight_meter); + + if done { + status.set_phase(DissolveCleanupPhase::AlphaInOutStakesGetTotalAlphaValue); + status.last_key = None; + } + done + } + + DissolveCleanupPhase::AlphaInOutStakesGetTotalAlphaValue => { + let (done, new_key) = Self::destroy_alpha_in_out_stakes_get_total_alpha_value( + netuid, + weight_meter, + status.last_key.clone(), + status, + ); + if done { + status.subnet_distributed_tao = Some(0); + status.set_phase(DissolveCleanupPhase::AlphaInOutStakesSettleStakes); + status.last_key = None; + weight_meter.consume(T::DbWeight::get().writes(2)); + } else { + status.last_key = new_key; + } + done + } + + DissolveCleanupPhase::AlphaInOutStakesSettleStakes => { + let (done, new_key) = Self::destroy_alpha_in_out_stakes_settle_stakes( + netuid, + weight_meter, + status.last_key.clone(), + status, + ); + if done { + status.set_phase(DissolveCleanupPhase::AlphaInOutStakesAlpha); + status.last_key = None; + } else { + status.last_key = new_key; + } + done + } + + DissolveCleanupPhase::AlphaInOutStakesAlpha => { + let (done, new_key) = Self::destroy_alpha_in_out_stakes_clean_alpha( + netuid, + weight_meter, + status.last_key.clone(), + ); + if done { + status.set_phase(DissolveCleanupPhase::AlphaInOutStakesHotkeyTotals); + status.last_key = None; + } else { + status.last_key = new_key; + } + done + } + + DissolveCleanupPhase::AlphaInOutStakesHotkeyTotals => { + let (done, new_key) = Self::destroy_alpha_in_out_stakes_clear_hotkey_totals( + netuid, + weight_meter, + status.last_key.clone(), + ); + + if done { + status.set_phase(DissolveCleanupPhase::AlphaInOutStakesLocks); + status.last_key = None; + } else { + status.last_key = new_key; + } + done + } + + DissolveCleanupPhase::AlphaInOutStakesLocks => { + let (done, new_key) = Self::destroy_alpha_in_out_stakes_clear_locks( + netuid, + weight_meter, + status.last_key.clone(), + ); + if done { + status.set_phase(DissolveCleanupPhase::AlphaInOutStakesDecayingLocks); + status.last_key = None; + } else { + status.last_key = new_key; + } + done + } + DissolveCleanupPhase::AlphaInOutStakesDecayingLocks => { + let (done, new_key) = Self::destroy_alpha_in_out_stakes_clear_decaying_locks( + netuid, + weight_meter, + status.last_key.clone(), + ); + if done { + status.set_phase(DissolveCleanupPhase::AlphaInOutStakes); + status.last_key = None; + } else { + status.last_key = new_key; + } + done + } + + DissolveCleanupPhase::AlphaInOutStakes => { + let done = Self::destroy_alpha_in_out_stakes(netuid, weight_meter, status); + if done { + status.set_phase(DissolveCleanupPhase::ProtocolLiquidity); + status.last_key = None; + } + done + } + + DissolveCleanupPhase::ProtocolLiquidity => { + let done = T::SwapInterface::clear_protocol_liquidity(netuid, weight_meter); + + if done { + status.set_phase(DissolveCleanupPhase::PurgeNetuid); + status.last_key = None; + } + done + } + + DissolveCleanupPhase::PurgeNetuid => { + let done = T::CommitmentsInterface::purge_netuid(netuid, weight_meter); + + if done { + status.set_phase(DissolveCleanupPhase::NetworkIsNetworkMember); + status.last_key = None; + } + done + } + DissolveCleanupPhase::NetworkIsNetworkMember => { + let (done, new_key) = Self::remove_network_is_network_member( + netuid, + weight_meter, + status.last_key.clone(), + ); + + if done { + status.set_phase(DissolveCleanupPhase::NetworkParameters); + status.last_key = None; + } else { + status.last_key = new_key; + } + done + } + DissolveCleanupPhase::NetworkParameters => { + let done = Self::remove_network_parameters(netuid, weight_meter); + + if done { + status.set_phase(DissolveCleanupPhase::NetworkMapParameters); + status.last_key = None; + } + done + } + DissolveCleanupPhase::NetworkMapParameters => { + let done = Self::remove_network_map_parameters(netuid, weight_meter); + + if done { + status.set_phase(DissolveCleanupPhase::NetworkUpdateWeightsOnRoot); + status.last_key = None; + } + done + } + DissolveCleanupPhase::NetworkUpdateWeightsOnRoot => { + let (done, new_key) = Self::remove_network_update_weights_on_root( + netuid, + weight_meter, + status.last_key.clone(), + ); + + if done { + status.set_phase(DissolveCleanupPhase::NetworkChildkeyTake); + status.last_key = None; + } else { + status.last_key = new_key; + } + done + } + DissolveCleanupPhase::NetworkChildkeyTake => { + let (done, new_key) = Self::remove_network_childkey_take( + netuid, + weight_meter, + status.last_key.clone(), + ); + + if done { + status.set_phase(DissolveCleanupPhase::NetworkChildkeys); + status.last_key = None; + } else { + status.last_key = new_key; + } + done + } + DissolveCleanupPhase::NetworkChildkeys => { + let (done, new_key) = Self::remove_network_childkeys( + netuid, + weight_meter, + status.last_key.clone(), + ); + + if done { + status.set_phase(DissolveCleanupPhase::NetworkParentkeys); + status.last_key = None; + } else { + status.last_key = new_key; + } + done + } + DissolveCleanupPhase::NetworkParentkeys => { + let (done, new_key) = Self::remove_network_parentkeys( + netuid, + weight_meter, + status.last_key.clone(), + ); + + if done { + status.set_phase(DissolveCleanupPhase::NetworkLastHotkeyEmissionOnNetuid); + status.last_key = None; + } else { + status.last_key = new_key; + } + done + } + DissolveCleanupPhase::NetworkLastHotkeyEmissionOnNetuid => { + let (done, new_key) = Self::remove_network_last_hotkey_emission_on_netuid( + netuid, + weight_meter, + status.last_key.clone(), + ); + + if done { + status.set_phase(DissolveCleanupPhase::NetworkTotalHotkeyAlphaLastEpoch); + status.last_key = None; + } else { + status.last_key = new_key; + } + done + } + DissolveCleanupPhase::NetworkTotalHotkeyAlphaLastEpoch => { + let (done, new_key) = Self::remove_network_total_hotkey_alpha_last_epoch( + netuid, + weight_meter, + status.last_key.clone(), + ); + + if done { + status.set_phase(DissolveCleanupPhase::NetworkTransactionKeyLastBlock); + status.last_key = None; + } else { + status.last_key = new_key; + } + done + } + DissolveCleanupPhase::NetworkTransactionKeyLastBlock => { + let (done, new_key) = Self::remove_network_transaction_key_last_block( + netuid, + weight_meter, + status.last_key.clone(), + ); + if done { + status.set_phase(DissolveCleanupPhase::NetworkLock); + status.last_key = None; + } else { + status.last_key = new_key; + } + done + } + DissolveCleanupPhase::NetworkLock => { + let (done, new_key) = + Self::remove_network_lock(netuid, weight_meter, status.last_key.clone()); + + if done { + status.set_phase(DissolveCleanupPhase::NetworkDecayingLock); + status.last_key = None; + } else { + status.last_key = new_key; + } + done + } + DissolveCleanupPhase::NetworkDecayingLock => { + let (done, new_key) = Self::remove_network_decaying_lock( + netuid, + weight_meter, + status.last_key.clone(), + ); + + // if all phases are done, remove the network from the dissolved networks list and emit the event + if done { + cleanup_completed = true; + } else { + status.last_key = new_key; + } + done + } + }; + + phase_done = done; + + if cleanup_completed { + Self::deposit_event(Event::NetworkDissolveCleanupCompleted { netuid }); + break; + } + + CurrentDissolveCleanupStatus::::set(Some(status.clone())); + } + + (cleanup_completed, weight_meter.consumed()) + } + + pub fn process_network_registration_queue() -> Weight { + let db_weight = T::DbWeight::get(); + let queue = NetworkRegistrationQueue::::get(); + let mut weight = db_weight.reads(1); + + for (index, info) in queue.iter().enumerate() { + // just complete one registration at a time since on_idle just complete one network dissolve cleanup + // if one registration fails, then try next one. it could be not align with the order of registration in the queue + match Self::set_new_network_state( + &info.coldkey, + &info.hotkey, + info.mechid, + info.identity.clone(), + info.lock_amount, + info.median_subnet_alpha_price, + Some(info.lock_id), + ) { + Ok(post_info) => { + NetworkRegistrationQueue::::mutate(|queue| queue.remove(index)); + weight.saturating_accrue(db_weight.reads_writes(1, 1)); + weight.saturating_accrue(post_info.actual_weight.unwrap_or_else(Weight::zero)); + return weight; + } + Err(_) => { + log::error!( + "Failed to set new network state for coldkey: {:?}, hotkey: {:?}", + info.coldkey, + info.hotkey + ); + continue; + } + } + } + + weight + } +} diff --git a/pallets/subtensor/src/subnets/mod.rs b/pallets/subtensor/src/subnets/mod.rs index e93628eef4..c04db2027b 100644 --- a/pallets/subtensor/src/subnets/mod.rs +++ b/pallets/subtensor/src/subnets/mod.rs @@ -1,4 +1,5 @@ use super::*; +pub mod dissolution; pub mod leasing; pub mod mechanism; pub mod registration; diff --git a/pallets/subtensor/src/subnets/serving.rs b/pallets/subtensor/src/subnets/serving.rs index 585fc897c0..e3f85ae888 100644 --- a/pallets/subtensor/src/subnets/serving.rs +++ b/pallets/subtensor/src/subnets/serving.rs @@ -69,6 +69,7 @@ impl Pallet { ) -> dispatch::DispatchResult { // We check the callers (hotkey) signature. let hotkey_id = ensure_signed(origin)?; + ensure!(Self::if_subnet_exist(netuid), Error::::SubnetNotExists); // Validate user input Self::validate_serve_axon( @@ -169,6 +170,7 @@ impl Pallet { ) -> dispatch::DispatchResult { // We check the callers (hotkey) signature. let hotkey_id = ensure_signed(origin)?; + ensure!(Self::if_subnet_exist(netuid), Error::::SubnetNotExists); let updated_prometheus = Self::validate_serve_prometheus(&hotkey_id, netuid, version, ip, port, ip_type)?; diff --git a/pallets/subtensor/src/subnets/subnet.rs b/pallets/subtensor/src/subnets/subnet.rs index f304c3d836..4dbc964aba 100644 --- a/pallets/subtensor/src/subnets/subnet.rs +++ b/pallets/subtensor/src/subnets/subnet.rs @@ -2,9 +2,32 @@ use super::*; use frame_support::PalletId; use safe_math::FixedExt; use sp_core::Get; -use sp_runtime::traits::AccountIdConversion; +use sp_runtime::{SaturatedConversion, traits::AccountIdConversion}; use substrate_fixed::types::U64F64; use subtensor_runtime_common::{NetUid, TaoBalance}; + +/// Data structure for a pending network registration in the execution queue. +#[crate::freeze_struct("c47fe93995c89025")] +#[derive(Encode, Decode, Default, TypeInfo, Clone, PartialEq, Eq, Debug)] +pub struct NetworkRegistrationInfo { + /// The account that registered the network. + pub coldkey: AccountId, + /// The account that registered the network. + pub hotkey: AccountId, + /// The mechanism that registered the network. + pub mechid: u16, + /// The identity that registered the network. + pub identity: Option, + /// The lock amount that registered the network. + pub lock_amount: TaoBalance, + /// The median subnet alpha price that registered the network. + pub median_subnet_alpha_price: U64F64, + /// The block at which the network was registered. + pub registration_block: u64, + /// The lock id that registered the network. + pub lock_id: u32, +} + impl Pallet { /// Returns true if the subnetwork exists. /// @@ -56,8 +79,12 @@ impl Pallet { pub fn get_next_netuid() -> NetUid { let mut next_netuid = NetUid::from(1); // do not allow creation of root let netuids = Self::get_all_subnet_netuids(); + let netuids_in_cleanup: Vec = DissolveCleanupQueue::::get() + .iter() + .map(|netuid| NetUid::from(netuid.inner())) + .collect(); loop { - if !netuids.contains(&next_netuid) { + if !netuids.contains(&next_netuid) && !netuids_in_cleanup.contains(&next_netuid) { break next_netuid; } next_netuid = next_netuid.next(); @@ -149,6 +176,13 @@ impl Pallet { Error::::NetworkTxRateLimitExceeded ); + if let Some(identity_value) = identity.clone() { + ensure!( + Self::is_valid_subnet_identity(&identity_value), + Error::::InvalidIdentity + ); + } + // --- 5. Check if we need to prune a subnet (if at SubnetLimit). // But do not prune yet; we only do it after all checks pass. let subnet_limit = Self::get_max_subnets(); @@ -156,10 +190,19 @@ impl Pallet { .filter(|(netuid, added)| *added && *netuid != NetUid::ROOT) .count() as u16; - let mut recycle_netuid: Option = None; - if current_count >= subnet_limit { - if let Some(netuid) = Self::get_network_to_prune() { - recycle_netuid = Some(netuid); + let cleanup_queue_len = DissolveCleanupQueue::::get().len(); + let registration_queue_len = NetworkRegistrationQueue::::get().len(); + + let mut prune_netuid: Option = None; + let mut wait_to_cleanup = false; + + if current_count.saturating_add(cleanup_queue_len.saturated_into::()) >= subnet_limit { + // no netuid available now, but enough netuids in the cleanup queue + // unnecessary to prune now, we need to wait to cleanup + if cleanup_queue_len > registration_queue_len { + wait_to_cleanup = true; + } else if let Some(netuid) = Self::get_network_to_prune() { + prune_netuid = Some(netuid); } else { return Err(Error::::SubnetLimitReached.into()); } @@ -173,54 +216,164 @@ impl Pallet { Error::::CannotAffordLockCost ); - // --- 7. If we identified a subnet to prune, do it now. - if let Some(prune_netuid) = recycle_netuid { + // --- 7. If we reach the limit and need prune a subnet, do it now. + if let Some(prune_netuid) = prune_netuid { Self::do_dissolve_network(prune_netuid)?; } - // --- 8. Determine netuid to register. If we pruned a subnet, reuse that netuid. - let netuid_to_register: NetUid = match recycle_netuid { - Some(prune_netuid) => prune_netuid, - None => Self::get_next_netuid(), + // can't get a netuid to register, so queue the registration + if wait_to_cleanup || prune_netuid.is_some() { + let lock_id = NetworkRegistrationLockId::::get(); + ensure!(lock_id != u32::MAX, Error::::LockIdOverFlow); + + Self::lock_network_registration_cost(&coldkey, lock_amount.into(), lock_id)?; + NetworkRegistrationLockId::::set(lock_id.saturating_add(1)); + + let median_subnet_alpha_price = Self::get_median_subnet_alpha_price(); + let info = NetworkRegistrationInfo:: { + coldkey: coldkey.clone(), + hotkey: hotkey.clone(), + mechid, + identity: identity.clone(), + lock_amount, + median_subnet_alpha_price, + registration_block: current_block, + lock_id, + }; + NetworkRegistrationQueue::::mutate(|queue| queue.push(info)); + Self::deposit_event(Event::NetworkRegistrationQueued { + coldkey: coldkey.clone(), + hotkey: hotkey.clone(), + mechid, + identity, + lock_amount, + median_subnet_alpha_price, + registration_block: current_block, + }); + return Ok(()); + } + + // --- 8. Set the new network state. + Self::set_new_network_state( + &coldkey, + hotkey, + mechid, + identity, + lock_amount, + Self::get_median_subnet_alpha_price(), + None, + ) + .map(|_| ()) + .map_err(|e| e.error) + } + + pub fn set_new_network_state( + coldkey: &T::AccountId, + hotkey: &T::AccountId, + mechid: u16, + identity: Option, + lock_amount: TaoBalance, + median_subnet_alpha_price: U64F64, + lock_id: Option, + ) -> DispatchResultWithPostInfo { + let db_weight = T::DbWeight::get(); + let mut weight = Weight::from_parts(0, 0); + + // --- 1. Determine netuid to register. + let current_block = Self::get_current_block_as_u64(); + weight.saturating_accrue(db_weight.reads(1)); + + let subnet_limit = Self::get_max_subnets(); + weight.saturating_accrue(db_weight.reads(1)); + + let mut networks_added_reads: u64 = 0; + let mut current_count: u16 = 0; + for (netuid, added) in NetworksAdded::::iter() { + networks_added_reads = networks_added_reads.saturating_add(1); + if added && netuid != NetUid::ROOT { + current_count = current_count.saturating_add(1); + } + } + weight.saturating_accrue(db_weight.reads(networks_added_reads)); + + let cleanup_queue_len: u16 = DissolveCleanupQueue::::get().len() as u16; + weight.saturating_accrue(db_weight.reads(1)); + + let netuid_to_register = if current_count.saturating_add(cleanup_queue_len) >= subnet_limit + { + return Err(Error::::SubnetLimitReached.into()); + } else { + // `get_next_netuid` scans `NetworksAdded` and `DissolveCleanupQueue` again. + weight.saturating_accrue(db_weight.reads(networks_added_reads.saturating_add(1))); + Self::get_next_netuid() }; - // --- 11. Snapshot the current median subnet alpha price before creating the new subnet. - let median_subnet_alpha_price = Self::get_median_subnet_alpha_price(); + // --- 2. Unlock the registration cost if the fund is locked. + if let Some(lock_id) = lock_id { + Self::unlock_network_registration_cost(coldkey, lock_id)?; + } - // --- 12. Set initial and custom parameters for the network. let default_tempo = DefaultTempo::::get(); + weight.saturating_accrue(db_weight.reads(1)); + Self::init_new_network(netuid_to_register, default_tempo); + // SubnetworkN, NetworksAdded, Tempo, TotalNetworks, default hyperparams, and + // explicit default-value inserts in `init_new_network`. + weight.saturating_accrue(db_weight.reads(5)); + weight.saturating_accrue(db_weight.writes(15)); log::debug!("init_new_network: {netuid_to_register:?}"); - // --- 10. Perform the lock operation (transfer TAO from owner's coldkey to subnet account). let actual_tao_lock_amount = - Self::transfer_tao_to_subnet(netuid_to_register, &coldkey, lock_amount.into())?; + Self::transfer_tao_to_subnet(netuid_to_register, coldkey, lock_amount.into())?; + // `get_subnet_account_id` + coldkey/subnet balance transfer. + weight.saturating_accrue(db_weight.reads(3)); + weight.saturating_accrue(db_weight.writes(2)); log::debug!("actual_tao_lock_amount: {actual_tao_lock_amount:?}"); - // --- 11. Set the lock amount for use to determine pricing. + // --- 3. Set the lock amount for use to determine pricing. Self::set_network_last_lock(actual_tao_lock_amount); Self::set_network_last_lock_block(current_block); + weight.saturating_accrue(db_weight.reads(1)); + weight.saturating_accrue(db_weight.writes(2)); + + // --- 4. Add the caller to the neuron set. + let hotkey_is_new = !Self::hotkey_account_exists(hotkey); + Self::create_account_if_non_existent(coldkey, hotkey)?; + if hotkey_is_new { + weight.saturating_accrue(db_weight.reads(4)); + weight.saturating_accrue(db_weight.writes(3)); + } else { + weight.saturating_accrue(db_weight.reads(2)); + } - // --- 12. Add the caller to the neuron set. - Self::create_account_if_non_existent(&coldkey, hotkey)?; Self::append_neuron(netuid_to_register, hotkey, current_block); + weight.saturating_accrue(db_weight.reads(10)); + weight.saturating_accrue(db_weight.writes(13)); log::debug!("Appended neuron for netuid {netuid_to_register:?}, hotkey: {hotkey:?}"); - // --- 14. Set the mechanism. + // --- 5. Set the mechanism. SubnetMechanism::::insert(netuid_to_register, mechid); + weight.saturating_accrue(db_weight.writes(1)); log::debug!("SubnetMechanism for netuid {netuid_to_register:?} set to: {mechid:?}"); - // --- 15. Set the creation terms. + // --- 6. Set the creation terms. NetworkRegisteredAt::::insert(netuid_to_register, current_block); RegisteredSubnetCounter::::mutate(netuid_to_register, |c| *c = c.saturating_add(1)); + weight.saturating_accrue(db_weight.reads(1)); + weight.saturating_accrue(db_weight.writes(2)); - // --- 16. Set the symbol. + // --- 7. Set the symbol. let symbol = Self::get_next_available_symbol(netuid_to_register); TokenSymbol::::insert(netuid_to_register, symbol); + // `get_next_available_symbol` scans all assigned symbols once. + weight.saturating_accrue(db_weight.reads(networks_added_reads.max(1))); + weight.saturating_accrue(db_weight.writes(1)); // Keep the locked TAO in the pool instead of recycling the excess. // Size the pool alpha reserve from the total TAO reserve at that same price. let pool_initial_tao: TaoBalance = Self::get_network_min_lock(); + weight.saturating_accrue(db_weight.reads(1)); + let total_pool_tao: TaoBalance = if actual_tao_lock_amount >= pool_initial_tao { actual_tao_lock_amount } else { @@ -233,7 +386,7 @@ impl Pallet { .saturating_to_num::() .into(); - // With the full lock retained in the reserve, this will normally be zero. + // // With the full lock retained in the reserve, this will normally be zero. let tao_recycled_for_registration = actual_tao_lock_amount.saturating_sub(total_pool_tao); // Core pool + ownership @@ -245,6 +398,8 @@ impl Pallet { SubnetAlphaOut::::insert(netuid_to_register, AlphaBalance::ZERO); SubnetVolume::::insert(netuid_to_register, 0u128); RAORecycledForRegistration::::insert(netuid_to_register, tao_recycled_for_registration); + weight.saturating_accrue(db_weight.reads(2)); + weight.saturating_accrue(db_weight.writes(8)); if tao_recycled_for_registration > TaoBalance::ZERO && let Some(subnet_account_id) = Self::get_subnet_account_id(netuid_to_register) @@ -252,25 +407,26 @@ impl Pallet { // The subnet account ID is guaranteed to have adequate balance for this // recycle because of transfer operation earlier. No need to check this result. let _ = Self::recycle_tao(&subnet_account_id, tao_recycled_for_registration); + weight.saturating_accrue(db_weight.reads(2)); + weight.saturating_accrue(db_weight.writes(1)); } if total_pool_tao > TaoBalance::ZERO { // Record in TotalStake the initial TAO in the pool. Self::increase_total_stake(total_pool_tao); + weight.saturating_accrue(db_weight.reads(1)); + weight.saturating_accrue(db_weight.writes(1)); } - // --- 17. Add the identity if it exists + // --- 8. Add the identity if it exists if let Some(identity_value) = identity { - ensure!( - Self::is_valid_subnet_identity(&identity_value), - Error::::InvalidIdentity - ); - SubnetIdentitiesV3::::insert(netuid_to_register, identity_value); Self::deposit_event(Event::SubnetIdentitySet(netuid_to_register)); + weight.saturating_accrue(db_weight.writes(1)); } - // --- 18. Schedule root validators as parents of the subnet owner hotkey. + // --- 9. Schedule root validators as parents of the subnet owner hotkey. + weight.saturating_accrue(db_weight.reads(2)); if let Err(e) = Self::do_set_root_validators_for_subnet(netuid_to_register) { log::warn!( "Failed to set root validators for netuid {:?}: {:?}", @@ -279,15 +435,16 @@ impl Pallet { ); } - // --- 19. Emit the NetworkAdded event. + // --- 10. Default emission off + SubnetEmissionEnabled::::insert(netuid_to_register, false); + weight.saturating_accrue(db_weight.writes(1)); + + // --- 11. Emit the NetworkAdded event. log::info!("NetworkAdded( netuid:{netuid_to_register:?}, mechanism:{mechid:?} )"); Self::deposit_event(Event::NetworkAdded(netuid_to_register, mechid)); - // --- 20. Default emission off - SubnetEmissionEnabled::::insert(netuid_to_register, false); - - // --- 20. Return success. - Ok(()) + // --- 12. Return success. + Ok(Some(weight).into()) } /// Sets initial and custom parameters for a new network. @@ -385,7 +542,7 @@ impl Pallet { ensure!( current_block_number >= registration_block_number.saturating_add(StartCallDelay::::get()), - Error::::NeedWaitingMoreBlocksToStarCall + Error::::StartCallNotReady ); let next_block_number = current_block_number.saturating_add(1); @@ -468,7 +625,10 @@ impl Pallet { } pub fn get_subnet_account_id(netuid: NetUid) -> Option { - if NetworksAdded::::contains_key(netuid) || netuid == NetUid::ROOT { + if NetworksAdded::::contains_key(netuid) + || netuid == NetUid::ROOT + || DissolveCleanupQueue::::get().contains(&netuid) + { Some(T::SubtensorPalletId::get().into_sub_account_truncating(u16::from(netuid))) } else { None diff --git a/pallets/subtensor/src/subnets/uids.rs b/pallets/subtensor/src/subnets/uids.rs index 3665b139ff..5ea0fea6d8 100644 --- a/pallets/subtensor/src/subnets/uids.rs +++ b/pallets/subtensor/src/subnets/uids.rs @@ -77,7 +77,7 @@ impl Pallet { // 2. Remove previous set memberships. Uids::::remove(netuid, old_hotkey.clone()); - AssociatedEvmAddress::::remove(netuid, uid_to_replace); + Self::remove_associated_evm_address(netuid, uid_to_replace); IsNetworkMember::::remove(old_hotkey.clone(), netuid); #[allow(unknown_lints)] Keys::::remove(netuid, uid_to_replace); @@ -213,7 +213,7 @@ impl Pallet { #[allow(unknown_lints)] Keys::::remove(netuid, neuron_uid); BlockAtRegistration::::remove(netuid, neuron_uid); - AssociatedEvmAddress::::remove(netuid, neuron_uid); + Self::remove_associated_evm_address(netuid, neuron_uid); for mecid in 0..mechanisms_count { let netuid_index = Self::get_mechanism_storage_index(netuid, mecid.into()); Weights::::remove(netuid_index, neuron_uid); @@ -346,6 +346,8 @@ impl Pallet { } } + Self::remap_associated_evm_address_index(netuid, &old_to_new_uid); + // Clear the UID map for the subnet let clear_result = Uids::::clear_prefix(netuid, u32::MAX, None); // Shouldn't happen, but possible. diff --git a/pallets/subtensor/src/subnets/weights.rs b/pallets/subtensor/src/subnets/weights.rs index 7a8dc50a60..89dc3cd119 100644 --- a/pallets/subtensor/src/subnets/weights.rs +++ b/pallets/subtensor/src/subnets/weights.rs @@ -218,43 +218,43 @@ impl Pallet { /// ---- Commits a timelocked, encrypted weight payload (Commit-Reveal v3). /// /// # Args - /// * `origin` (`::RuntimeOrigin`): + /// * `origin` (`::RuntimeOrigin`): /// The signed origin of the committing hotkey. - /// * `netuid` (`NetUid` = `u16`): + /// * `netuid` (`NetUid` = `u16`): /// Unique identifier for the subnet on which the commit is made. - /// * `commit` (`BoundedVec>`): - /// The encrypted weight payload, produced as follows: - /// 1. Build a [`WeightsPayload`] structure. - /// 2. SCALE-encode it (`parity_scale_codec::Encode`). - /// 3. Encrypt it following the steps - /// [here](https://github.com/ideal-lab5/tle/blob/f8e6019f0fb02c380ebfa6b30efb61786dede07b/timelock/src/tlock.rs#L283-L336) to - /// produce a [`TLECiphertext`]. + /// * `commit` (`BoundedVec>`): + /// The encrypted weight payload, produced as follows: + /// 1. Build a [`WeightsPayload`] structure. + /// 2. SCALE-encode it (`parity_scale_codec::Encode`). + /// 3. Encrypt it following the steps + /// [here](https://github.com/ideal-lab5/tle/blob/f8e6019f0fb02c380ebfa6b30efb61786dede07b/timelock/src/tlock.rs#L283-L336) to + /// produce a [`TLECiphertext`]. /// 4. Compress & serialise. - /// * `reveal_round` (`u64`): - /// DRAND round whose output becomes known during epoch `n + 1`; the payload + /// * `reveal_round` (`u64`): + /// DRAND round whose output becomes known during epoch `n + 1`; the payload /// must be revealed in that epoch. - /// * `commit_reveal_version` (`u16`): - /// Version tag that **must** match [`get_commit_reveal_weights_version`] for + /// * `commit_reveal_version` (`u16`): + /// Version tag that **must** match [`get_commit_reveal_weights_version`] for /// the call to succeed. Used to gate runtime upgrades. /// /// # Behaviour - /// 1. Verifies the caller’s signature and registration on `netuid`. + /// 1. Verifies the caller’s signature and registration on `netuid`. /// 2. Ensures commit-reveal is enabled **and** the supplied - /// `commit_reveal_version` is current. - /// 3. Enforces per-neuron rate-limiting via [`Pallet::check_rate_limit`]. + /// `commit_reveal_version` is current. + /// 3. Enforces per-neuron rate-limiting via [`Pallet::check_rate_limit`]. /// 4. Rejects the call when the hotkey already has ≥ 10 unrevealed commits in - /// the current epoch. - /// 5. Appends `(hotkey, commit_block, commit, reveal_round)` to - /// `TimelockedWeightCommits[netuid][epoch]`. - /// 6. Emits `TimelockedWeightsCommitted` with the Blake2 hash of `commit`. + /// the current epoch. + /// 5. Appends `(hotkey, commit_block, commit, reveal_round)` to + /// `TimelockedWeightCommits[netuid][epoch]`. + /// 6. Emits `TimelockedWeightsCommitted` with the Blake2 hash of `commit`. /// 7. Updates `LastUpdateForUid` so subsequent rate-limit checks include this /// commit. /// /// # Raises - /// * `CommitRevealDisabled` – Commit-reveal is disabled on `netuid`. - /// * `IncorrectCommitRevealVersion` – Provided version ≠ runtime version. - /// * `HotKeyNotRegisteredInSubNet` – Caller’s hotkey is not registered. - /// * `CommittingWeightsTooFast` – Caller exceeds commit-rate limit. + /// * `CommitRevealDisabled` – Commit-reveal is disabled on `netuid`. + /// * `IncorrectCommitRevealVersion` – Provided version ≠ runtime version. + /// * `HotKeyNotRegisteredInSubNet` – Caller’s hotkey is not registered. + /// * `CommittingWeightsTooFast` – Caller exceeds commit-rate limit. /// * `TooManyUnrevealedCommits` – Caller already has 10 unrevealed commits. /// /// # Events @@ -457,6 +457,7 @@ impl Pallet { salt: Vec, version_key: u64, ) -> DispatchResult { + ensure!(Self::if_subnet_exist(netuid), Error::::SubnetNotExists); // Calculate netuid storage index let netuid_index = Self::get_mechanism_storage_index(netuid, mecid); @@ -610,6 +611,7 @@ impl Pallet { salts_list: Vec>, version_keys: Vec, ) -> DispatchResult { + ensure!(Self::if_subnet_exist(netuid), Error::::SubnetNotExists); // Calculate netuid storage index let netuid_index = Self::get_mechanism_storage_index(netuid, MechId::MAIN); diff --git a/pallets/subtensor/src/swap/swap_coldkey.rs b/pallets/subtensor/src/swap/swap_coldkey.rs index 608c61fd57..d8de37f029 100644 --- a/pallets/subtensor/src/swap/swap_coldkey.rs +++ b/pallets/subtensor/src/swap/swap_coldkey.rs @@ -23,6 +23,10 @@ impl Pallet { IdentitiesV2::::insert(new_coldkey.clone(), identity); } + // Temporarily allow the destination coldkey to receive this stake even if some of it is + // locked; swap_coldkey_locks will copy the source AccountFlags over afterward. + Self::set_accept_locked_alpha(new_coldkey, true); + for netuid in Self::get_all_subnet_netuids() { Self::transfer_subnet_ownership(netuid, old_coldkey, new_coldkey); Self::transfer_auto_stake_destination(netuid, old_coldkey, new_coldkey); @@ -37,8 +41,6 @@ impl Pallet { // Transfer any remaining balance from old_coldkey to new_coldkey Self::transfer_all_tao_and_kill(old_coldkey, new_coldkey)?; - Self::set_last_tx_block(new_coldkey, Self::get_current_block_as_u64()); - Self::deposit_event(Event::ColdkeySwapped { old_coldkey: old_coldkey.clone(), new_coldkey: new_coldkey.clone(), diff --git a/pallets/subtensor/src/swap/swap_hotkey.rs b/pallets/subtensor/src/swap/swap_hotkey.rs index a266798aeb..ca55340554 100644 --- a/pallets/subtensor/src/swap/swap_hotkey.rs +++ b/pallets/subtensor/src/swap/swap_hotkey.rs @@ -24,7 +24,6 @@ impl Pallet { /// # Errors /// /// * `NonAssociatedColdKey` - If the coldkey does not own the old hotkey. - /// * `HotKeySetTxRateLimitExceeded` - If the transaction rate limit is exceeded. /// * `NewHotKeyIsSameWithOld` - If the new hotkey is the same as the old hotkey. /// * `HotKeyAlreadyRegisteredInSubNet` - If the new hotkey is already registered in the subnet. /// * `NewHotKeyNotCleanForRootSwap` - If the swap touches root and the new hotkey @@ -40,6 +39,10 @@ impl Pallet { // // 1. Ensure the origin is signed and get the coldkey let coldkey = ensure_signed(origin)?; + if let Some(netuid) = netuid { + ensure!(Self::if_subnet_exist(netuid), Error::::SubnetNotExists); + } + // 2. Ensure the coldkey owns the old hotkey ensure!( Self::coldkey_owns_hotkey(&coldkey, old_hotkey), @@ -63,14 +66,6 @@ impl Pallet { // 6. Get the current block number let block: u64 = Self::get_current_block_as_u64(); - // 7. Ensure the transaction rate limit is not exceeded - ensure!( - !Self::exceeds_tx_rate_limit(Self::get_last_tx_block(&coldkey), block), - Error::::HotKeySetTxRateLimitExceeded - ); - - weight.saturating_accrue(T::DbWeight::get().reads(2)); - match netuid { // 8. Ensure the hotkey is not registered on the network before, if netuid is provided Some(netuid) => { @@ -107,12 +102,7 @@ impl Pallet { ); } - // 9. Swap LastTxBlock - let last_tx_block: u64 = Self::get_last_tx_block(old_hotkey); - Self::set_last_tx_block(new_hotkey, last_tx_block); - weight.saturating_accrue(T::DbWeight::get().reads_writes(1, 1)); - - // 10. Swap LastTxBlockDelegateTake + // 8. Swap LastTxBlockDelegateTake let last_tx_block_delegate_take: u64 = Self::get_last_tx_block_delegate_take(old_hotkey); Self::set_last_tx_block_delegate_take(new_hotkey, last_tx_block_delegate_take); weight.saturating_accrue(T::DbWeight::get().reads_writes(1, 1)); @@ -212,11 +202,7 @@ impl Pallet { weight.saturating_accrue(T::DbWeight::get().writes(1)); } - // 17. Update the last transaction block for the coldkey - Self::set_last_tx_block(&coldkey, block); - weight.saturating_accrue(T::DbWeight::get().writes(1)); - - // 18. Emit an event for the hotkey swap + // 17. Emit an event for the hotkey swap Self::deposit_event(Event::HotkeySwapped { coldkey, old_hotkey: old_hotkey.clone(), @@ -431,10 +417,10 @@ impl Pallet { keep_stake, )?; - // 11. Update the last transaction block for the coldkey - Self::set_last_tx_block(coldkey, block); + // 10. Record the per-subnet swap block for the HotkeySwapOnSubnetInterval gate. + // The generic LastTxBlock setter is dropped together with its removed check. LastHotkeySwapOnNetuid::::insert(netuid, coldkey, block); - weight.saturating_accrue(T::DbWeight::get().writes(2)); + weight.saturating_accrue(T::DbWeight::get().writes(1)); // 12. Emit an event for the hotkey swap Self::deposit_event(Event::HotkeySwappedOnSubnet { diff --git a/pallets/subtensor/src/tests/claim_root.rs b/pallets/subtensor/src/tests/claim_root.rs index 12606c5266..71b97d9fcd 100644 --- a/pallets/subtensor/src/tests/claim_root.rs +++ b/pallets/subtensor/src/tests/claim_root.rs @@ -1,15 +1,17 @@ #![allow(clippy::expect_used, clippy::unwrap_used)] +use super::mock::run_block_idle; use crate::RootAlphaDividendsPerSubnet; use crate::tests::mock::*; use crate::{ - DefaultMinRootClaimAmount, Error, MAX_NUM_ROOT_CLAIMS, MAX_ROOT_CLAIM_THRESHOLD, NetworksAdded, - NumRootClaim, NumStakingColdkeys, PendingRootAlphaDivs, RootClaimable, RootClaimableThreshold, - StakingColdkeys, StakingColdkeysByIndex, SubnetAlphaIn, SubnetAlphaOut, SubnetMechanism, - SubnetMovingPrice, SubnetProtocolFlow, SubnetRootSellTao, SubnetTAO, SubnetTaoFlow, - SubnetVolume, SubtokenEnabled, Tempo, TotalStake, pallet, + DefaultMinRootClaimAmount, DissolveCleanupQueue, Error, MAX_NUM_ROOT_CLAIMS, + MAX_ROOT_CLAIM_THRESHOLD, NetworksAdded, NumRootClaim, NumStakingColdkeys, + PendingRootAlphaDivs, RootClaimable, RootClaimableThreshold, RootClaimed, StakingColdkeys, + StakingColdkeysByIndex, SubnetAlphaIn, SubnetAlphaOut, SubnetMechanism, SubnetMovingPrice, + SubnetProtocolFlow, SubnetRootSellTao, SubnetTAO, SubnetTaoFlow, SubnetVolume, SubtokenEnabled, + Tempo, TotalStake, pallet, }; -use crate::{RootClaimType, RootClaimTypeEnum, RootClaimed}; +use crate::{RootClaimType, RootClaimTypeEnum}; use approx::assert_abs_diff_eq; use frame_support::dispatch::RawOrigin; use frame_support::pallet_prelude::Weight; @@ -17,7 +19,7 @@ use frame_support::traits::{Currency, Get}; use frame_support::{assert_err, assert_noop, assert_ok}; use sp_core::{H256, U256}; use sp_runtime::DispatchError; -use std::collections::BTreeSet; +use std::collections::{BTreeMap, BTreeSet}; use substrate_fixed::types::I96F32; use subtensor_runtime_common::{AlphaBalance, NetUid, TaoBalance, Token}; use subtensor_swap_interface::SwapHandler; @@ -1496,6 +1498,10 @@ fn test_claim_root_on_network_deregistration() { assert_ok!(SubtensorModule::do_dissolve_network(netuid)); + DissolveCleanupQueue::::set(vec![netuid]); + + run_block_idle(); + assert!(!RootClaimed::::contains_key(( netuid, &hotkey, &coldkey, ))); @@ -1503,6 +1509,91 @@ fn test_claim_root_on_network_deregistration() { }); } +#[test] +fn root_claim_on_subnet_is_noop_when_subnet_is_dissolved_queue() { + new_test_ext(1).execute_with(|| { + let owner_coldkey = U256::from(41_001); + let owner_hotkey = U256::from(41_002); + let netuid = add_dynamic_network(&owner_hotkey, &owner_coldkey); + let coldkey = U256::from(41_003); + let hotkey = U256::from(41_004); + register_ok_neuron(netuid, hotkey, coldkey, 111); + + let mut claimable = BTreeMap::new(); + claimable.insert(netuid, I96F32::from(9_000_000i32)); + RootClaimable::::insert(hotkey, claimable); + + DissolveCleanupQueue::::put(vec![netuid]); + + let before = RootClaimable::::get(hotkey).clone(); + let _ = SubtensorModule::root_claim_on_subnet( + &hotkey, + &coldkey, + netuid, + RootClaimTypeEnum::Swap, + true, + ); + assert_eq!( + RootClaimable::::get(hotkey), + before, + "dissolved subnets must not process root claims during async cleanup" + ); + }); +} + +#[test] +fn clean_up_root_claimable_for_subnet_removes_only_that_netuid_per_hotkey() { + new_test_ext(1).execute_with(|| { + let owner_coldkey = U256::from(42_001); + let owner_hotkey = U256::from(42_002); + let net = add_dynamic_network(&owner_hotkey, &owner_coldkey); + let hk1 = U256::from(42_010); + let hk2 = U256::from(42_011); + + let mut m1 = BTreeMap::new(); + m1.insert(net, I96F32::from(100i32)); + m1.insert(NetUid::ROOT, I96F32::from(50i32)); + let mut m2 = BTreeMap::new(); + m2.insert(net, I96F32::from(200i32)); + + RootClaimable::::insert(hk1, m1); + RootClaimable::::insert(hk2, m2); + + let mut weight_meter = + frame_support::weights::WeightMeter::with_limit(Weight::from_parts(u64::MAX, u64::MAX)); + let (done, _) = + SubtensorModule::clean_up_root_claimable_for_subnet(net, &mut weight_meter, None); + assert!( + done, + "full weight should scan and update all claimable maps" + ); + + assert!(!RootClaimable::::get(hk1).contains_key(&net)); + assert!(RootClaimable::::get(hk1).contains_key(&NetUid::ROOT)); + assert!(!RootClaimable::::get(hk2).contains_key(&net)); + }); +} + +#[test] +fn clean_up_root_claimed_for_subnet_clears_claimed_nmap_prefix() { + new_test_ext(1).execute_with(|| { + let owner_coldkey = U256::from(43_001); + let owner_hotkey = U256::from(43_002); + let net = add_dynamic_network(&owner_hotkey, &owner_coldkey); + let hk = U256::from(43_010); + let ck = U256::from(43_011); + + RootClaimed::::insert((net, hk, ck), 123u128); + assert!(RootClaimed::::contains_key((net, hk, ck))); + + let mut weight_meter = + frame_support::weights::WeightMeter::with_limit(Weight::from_parts(u64::MAX, u64::MAX)); + let done = SubtensorModule::clean_up_root_claimed_for_subnet(net, &mut weight_meter); + assert!(done); + assert!(!RootClaimed::::contains_key((net, hk, ck))); + }); +} + #[test] fn test_claim_root_threshold() { new_test_ext(1).execute_with(|| { @@ -2179,6 +2270,53 @@ fn test_claim_root_with_moved_stake() { }); } +#[test] +fn test_clean_up_root_claimed_for_subnet_clears_target_preserves_other_netuid() { + new_test_ext(1).execute_with(|| { + let hotkey = U256::from(5001u64); + let c_a = U256::from(5002u64); + let c_b = U256::from(5003u64); + let c_other = U256::from(5004u64); + let netuid_target = NetUid::from(11u16); + let netuid_other = NetUid::from(12u16); + + RootClaimed::::insert((netuid_target, &hotkey, &c_a), 10u128); + RootClaimed::::insert((netuid_target, &hotkey, &c_b), 20u128); + RootClaimed::::insert((netuid_other, &hotkey, &c_other), 99u128); + + let mut weight_meter = + frame_support::weights::WeightMeter::with_limit(Weight::from_parts(u64::MAX, u64::MAX)); + let done = + SubtensorModule::clean_up_root_claimed_for_subnet(netuid_target, &mut weight_meter); + assert!(done, "enough weight should complete cleanup"); + + assert_eq!( + RootClaimed::::get((netuid_target, &hotkey, &c_a)), + 0u128 + ); + assert_eq!( + RootClaimed::::get((netuid_target, &hotkey, &c_b)), + 0u128 + ); + assert!(!RootClaimed::::contains_key(( + netuid_target, + &hotkey, + &c_a + ))); + assert!(!RootClaimed::::contains_key(( + netuid_target, + &hotkey, + &c_b + ))); + + assert_eq!( + RootClaimed::::get((netuid_other, &hotkey, &c_other)), + 99u128, + "other netuid must be untouched" + ); + }); +} + // ============================================================ // GHSA-2026-010 regression test — security audit (June 2026) // Fails on the vulnerable code; passes with the fix in this PR. diff --git a/pallets/subtensor/src/tests/cleanup_tests.rs b/pallets/subtensor/src/tests/cleanup_tests.rs new file mode 100644 index 0000000000..ed7a61aade --- /dev/null +++ b/pallets/subtensor/src/tests/cleanup_tests.rs @@ -0,0 +1,214 @@ +#![allow(clippy::unwrap_used)] + +use super::mock::*; +use crate::*; +use frame_support::weights::{Weight, WeightMeter}; +use subtensor_runtime_common::NetUid; + +type TestEntry = (NetUid, u64); + +fn db_read() -> Weight { + ::DbWeight::get().reads(1) +} + +fn db_writes(n: u64) -> Weight { + ::DbWeight::get().writes(n) +} + +fn run_cleanup( + weight_meter: &mut WeightMeter, + entries: Vec, + target: NetUid, + writes_per_match: u64, +) -> (bool, Option, sp_std::vec::Vec) { + let removed = sp_std::cell::RefCell::new(sp_std::vec::Vec::new()); + let (read_all, last_item) = SubtensorModule::remove_storage_entries_for_netuid( + weight_meter, + entries.into_iter(), + |&(netuid, _)| netuid == target, + |(_, id)| id, + |id| removed.borrow_mut().push(*id), + writes_per_match, + ); + (read_all, last_item, removed.into_inner()) +} + +#[test] +fn remove_storage_entries_for_netuid_empty_iterator() { + new_test_ext(0).execute_with(|| { + let limit = Weight::from_parts(u64::MAX, u64::MAX); + let mut weight_meter = WeightMeter::with_limit(limit); + + let (read_all, last_item, removed) = + run_cleanup(&mut weight_meter, vec![], NetUid::from(1), 1); + + assert!(read_all); + assert!(last_item.is_none()); + assert!(removed.is_empty()); + assert_eq!(weight_meter.consumed(), Weight::zero()); + }); +} + +#[test] +fn remove_storage_entries_for_netuid_removes_matching_entries() { + new_test_ext(0).execute_with(|| { + let target = NetUid::from(1); + let entries = vec![ + (NetUid::from(1), 10), + (NetUid::from(2), 20), + (NetUid::from(1), 30), + ]; + let mut weight_meter = WeightMeter::with_limit(Weight::from_parts(u64::MAX, u64::MAX)); + + let (read_all, last_item, removed) = run_cleanup(&mut weight_meter, entries, target, 1); + + assert!(read_all); + assert_eq!(last_item, Some((NetUid::from(1), 30))); + assert_eq!(removed, vec![10, 30]); + + let expected = db_read() + .saturating_mul(3) + .saturating_add(db_writes(1).saturating_mul(2)); + assert_eq!(weight_meter.consumed(), expected); + }); +} + +#[test] +fn remove_storage_entries_for_netuid_skips_non_matching_entries() { + new_test_ext(0).execute_with(|| { + let target = NetUid::from(99); + let entries = vec![ + (NetUid::from(1), 10), + (NetUid::from(2), 20), + (NetUid::from(3), 30), + ]; + let mut weight_meter = WeightMeter::with_limit(Weight::from_parts(u64::MAX, u64::MAX)); + + let (read_all, last_item, removed) = run_cleanup(&mut weight_meter, entries, target, 1); + + assert!(read_all); + assert_eq!(last_item, Some((NetUid::from(3), 30))); + assert!(removed.is_empty()); + assert_eq!(weight_meter.consumed(), db_read().saturating_mul(3)); + }); +} + +#[test] +fn remove_storage_entries_for_netuid_stops_when_read_budget_exhausted() { + new_test_ext(0).execute_with(|| { + let target = NetUid::from(1); + let entries = vec![ + (NetUid::from(2), 10), + (NetUid::from(2), 20), + (NetUid::from(1), 30), + ]; + // Budget for two reads only; the third entry is never scanned. + let limit = db_read().saturating_mul(2); + let mut weight_meter = WeightMeter::with_limit(limit); + + let (read_all, last_item, removed) = run_cleanup(&mut weight_meter, entries, target, 1); + + assert!(!read_all); + assert_eq!(last_item, Some((NetUid::from(2), 20))); + assert!(removed.is_empty()); + assert_eq!(weight_meter.consumed(), limit); + }); +} + +#[test] +fn remove_storage_entries_for_netuid_stops_when_write_budget_exhausted() { + new_test_ext(0).execute_with(|| { + let target = NetUid::from(1); + let entries = vec![(NetUid::from(1), 10), (NetUid::from(1), 20)]; + // Two reads and one write: first match is removed, second match reads but cannot write. + let limit = db_read().saturating_mul(2).saturating_add(db_writes(1)); + let mut weight_meter = WeightMeter::with_limit(limit); + + let (read_all, last_item, removed) = run_cleanup(&mut weight_meter, entries, target, 1); + + assert!(!read_all); + assert_eq!(last_item, Some((NetUid::from(1), 10))); + assert_eq!(removed, vec![10]); + assert_eq!(weight_meter.consumed(), limit); + }); +} + +#[test] +fn remove_storage_entries_for_netuid_respects_writes_per_match() { + new_test_ext(0).execute_with(|| { + let target = NetUid::from(1); + let entries = vec![(NetUid::from(1), 10), (NetUid::from(1), 20)]; + let writes_per_match = 2_u64; + // Two reads and two writes: first match is removed, second match reads but cannot write. + let limit = db_read() + .saturating_mul(2) + .saturating_add(db_writes(writes_per_match)); + let mut weight_meter = WeightMeter::with_limit(limit); + + let (read_all, last_item, removed) = + run_cleanup(&mut weight_meter, entries, target, writes_per_match); + + assert!(!read_all); + assert_eq!(last_item, Some((NetUid::from(1), 10))); + assert_eq!(removed, vec![10]); + assert_eq!(weight_meter.consumed(), limit); + }); +} + +#[test] +fn remove_storage_entries_for_netuid_defers_removals_until_after_scan() { + new_test_ext(0).execute_with(|| { + use sp_std::{cell::Cell, rc::Rc}; + + let target = NetUid::from(1); + let entries = vec![ + (NetUid::from(1), 10), + (NetUid::from(1), 20), + (NetUid::from(1), 30), + ]; + let scan_finished = Rc::new(Cell::new(false)); + let mut weight_meter = WeightMeter::with_limit(Weight::from_parts(u64::MAX, u64::MAX)); + + struct ScanTrackingIter { + items: sp_std::vec::Vec, + index: usize, + scan_finished: Rc>, + } + + impl Iterator for ScanTrackingIter { + type Item = TestEntry; + + fn next(&mut self) -> Option { + if self.index >= self.items.len() { + self.scan_finished.set(true); + return None; + } + let item = self.items.get(self.index).copied()?; + self.index += 1; + Some(item) + } + } + + let scan_finished_for_ops = Rc::clone(&scan_finished); + let (read_all, last_item) = SubtensorModule::remove_storage_entries_for_netuid( + &mut weight_meter, + ScanTrackingIter { + items: entries, + index: 0, + scan_finished, + }, + |&(netuid, _)| netuid == target, + |(_, id)| id, + |_| { + assert!( + scan_finished_for_ops.get(), + "removal ops must run only after the iterator is exhausted" + ) + }, + 1, + ); + + assert!(read_all); + assert_eq!(last_item, Some((NetUid::from(1), 30))); + }); +} diff --git a/pallets/subtensor/src/tests/destroy_alpha_tests.rs b/pallets/subtensor/src/tests/destroy_alpha_tests.rs new file mode 100644 index 0000000000..d7186f1f16 --- /dev/null +++ b/pallets/subtensor/src/tests/destroy_alpha_tests.rs @@ -0,0 +1,446 @@ +#![allow(clippy::expect_used, clippy::indexing_slicing, clippy::unwrap_used)] + +use super::mock::*; +use crate::*; +use frame_support::{assert_ok, weights::Weight}; +use sp_core::U256; +use subtensor_runtime_common::TaoBalance; +use subtensor_swap_interface::SwapHandler; + +fn setup_staked_subnet() -> (U256, U256, NetUid) { + let owner_cold = U256::from(1001); + let owner_hot = U256::from(1002); + let netuid = add_dynamic_network(&owner_hot, &owner_cold); + + let stake_tao: u64 = 1000; + setup_reserves( + netuid, + (stake_tao * 1_000_000).into(), + (stake_tao * 10_000_000).into(), + ); + let amount: TaoBalance = stake_tao.into(); + assert_ok!(SubtensorModule::create_account_if_non_existent( + &owner_cold, + &owner_hot + )); + add_balance_to_coldkey_account(&owner_cold, amount); + assert_ok!(SubtensorModule::stake_into_subnet( + &owner_hot, + &owner_cold, + netuid, + amount, + ::SwapInterface::max_price(), + false, + )); + + (owner_cold, owner_hot, netuid) +} + +#[test] +fn test_destroy_alpha_in_out_stakes_get_total_alpha_value() { + new_test_ext(0).execute_with(|| { + let (_, _, netuid) = setup_staked_subnet(); + let w = Weight::from_parts(u64::MAX, u64::MAX); + let mut weight_meter = WeightMeter::with_limit(w); + assert!( + run_resumable_netuid_cleanup_with_status( + netuid, + &mut weight_meter, + &mut dissolve_cleanup_status(netuid), + |netuid, weight_meter, last_key, status| { + SubtensorModule::destroy_alpha_in_out_stakes_get_total_alpha_value( + netuid, + weight_meter, + last_key, + status, + ) + }, + ), + "destroy_alpha_in_out_stakes_get_total_alpha_value should complete" + ); + let mut status = dissolve_cleanup_status(netuid); + run_resumable_netuid_cleanup_with_status( + netuid, + &mut weight_meter, + &mut status, + |netuid, weight_meter, last_key, status| { + SubtensorModule::destroy_alpha_in_out_stakes_get_total_alpha_value( + netuid, + weight_meter, + last_key, + status, + ) + }, + ); + assert!(status.subnet_total_alpha_value.is_some()); + }); +} + +#[test] +fn test_destroy_alpha_in_out_stakes_settle_stakes() { + new_test_ext(0).execute_with(|| { + let (_, _, netuid) = setup_staked_subnet(); + run_destroy_alpha_get_total_and_settle(netuid); + }); +} + +#[test] +fn test_destroy_alpha_in_out_stakes_clean_alpha() { + new_test_ext(0).execute_with(|| { + let (_, owner_hot, netuid) = setup_staked_subnet(); + let w = Weight::from_parts(u64::MAX, u64::MAX); + let mut weight_meter = WeightMeter::with_limit(w); + let mut status = dissolve_cleanup_status(netuid); + assert!(run_resumable_netuid_cleanup_with_status( + netuid, + &mut weight_meter, + &mut status, + |netuid, weight_meter, last_key, status| { + SubtensorModule::destroy_alpha_in_out_stakes_get_total_alpha_value( + netuid, + weight_meter, + last_key, + status, + ) + }, + )); + status.subnet_distributed_tao = Some(0); + let mut weight_meter2 = WeightMeter::with_limit(w); + assert!(run_resumable_netuid_cleanup_with_status( + netuid, + &mut weight_meter2, + &mut status, + |netuid, weight_meter, last_key, status| { + SubtensorModule::destroy_alpha_in_out_stakes_settle_stakes( + netuid, + weight_meter, + last_key, + status, + ) + }, + )); + let mut weight_meter3 = WeightMeter::with_limit(w); + assert!( + run_resumable_netuid_cleanup( + netuid, + &mut weight_meter3, + SubtensorModule::destroy_alpha_in_out_stakes_clean_alpha, + ), + "destroy_alpha_in_out_stakes_clean_alpha should complete" + ); + assert_eq!( + Alpha::::iter() + .filter(|((_, _, nu), _)| *nu == netuid) + .count(), + 0 + ); + assert!(TotalHotkeyAlpha::::contains_key(owner_hot, netuid)); + }); +} + +#[test] +fn test_destroy_alpha_in_out_stakes_clear_hotkey_totals() { + new_test_ext(0).execute_with(|| { + let (_, owner_hot, netuid) = setup_staked_subnet(); + let w = Weight::from_parts(u64::MAX, u64::MAX); + let mut weight_meter = WeightMeter::with_limit(w); + let mut status = dissolve_cleanup_status(netuid); + assert!(run_resumable_netuid_cleanup_with_status( + netuid, + &mut weight_meter, + &mut status, + |netuid, weight_meter, last_key, status| { + SubtensorModule::destroy_alpha_in_out_stakes_get_total_alpha_value( + netuid, + weight_meter, + last_key, + status, + ) + }, + )); + status.subnet_distributed_tao = Some(0); + let mut weight_meter2 = WeightMeter::with_limit(w); + assert!(run_resumable_netuid_cleanup_with_status( + netuid, + &mut weight_meter2, + &mut status, + |netuid, weight_meter, last_key, status| { + SubtensorModule::destroy_alpha_in_out_stakes_settle_stakes( + netuid, + weight_meter, + last_key, + status, + ) + }, + )); + let mut weight_meter3 = WeightMeter::with_limit(w); + assert!(run_resumable_netuid_cleanup( + netuid, + &mut weight_meter3, + SubtensorModule::destroy_alpha_in_out_stakes_clean_alpha, + )); + let mut weight_meter4 = WeightMeter::with_limit(w); + assert!( + run_resumable_netuid_cleanup( + netuid, + &mut weight_meter4, + SubtensorModule::destroy_alpha_in_out_stakes_clear_hotkey_totals, + ), + "destroy_alpha_in_out_stakes_clear_hotkey_totals should complete" + ); + assert!(!TotalHotkeyAlpha::::contains_key(owner_hot, netuid)); + }); +} + +#[test] +fn test_destroy_alpha_in_out_stakes_clear_locks() { + new_test_ext(0).execute_with(|| { + let (owner_cold, owner_hot, netuid) = setup_staked_subnet(); + let w = Weight::from_parts(u64::MAX, u64::MAX); + let mut weight_meter = WeightMeter::with_limit(w); + let mut status = dissolve_cleanup_status(netuid); + assert!(run_resumable_netuid_cleanup_with_status( + netuid, + &mut weight_meter, + &mut status, + |netuid, weight_meter, last_key, status| { + SubtensorModule::destroy_alpha_in_out_stakes_get_total_alpha_value( + netuid, + weight_meter, + last_key, + status, + ) + }, + )); + status.subnet_distributed_tao = Some(0); + let mut weight_meter2 = WeightMeter::with_limit(w); + assert!(run_resumable_netuid_cleanup_with_status( + netuid, + &mut weight_meter2, + &mut status, + |netuid, weight_meter, last_key, status| { + SubtensorModule::destroy_alpha_in_out_stakes_settle_stakes( + netuid, + weight_meter, + last_key, + status, + ) + }, + )); + let mut weight_meter3 = WeightMeter::with_limit(w); + assert!(run_resumable_netuid_cleanup( + netuid, + &mut weight_meter3, + SubtensorModule::destroy_alpha_in_out_stakes_clean_alpha, + )); + let mut weight_meter4 = WeightMeter::with_limit(w); + assert!(run_resumable_netuid_cleanup( + netuid, + &mut weight_meter4, + SubtensorModule::destroy_alpha_in_out_stakes_clear_hotkey_totals, + )); + + Lock::::insert( + (owner_cold, netuid, owner_hot), + crate::staking::lock::LockState { + locked_mass: 10u64.into(), + conviction: substrate_fixed::types::U64F64::from_num(1.5), + last_update: 1, + }, + ); + + let mut weight_meter5 = WeightMeter::with_limit(w); + assert!( + run_resumable_netuid_cleanup( + netuid, + &mut weight_meter5, + SubtensorModule::destroy_alpha_in_out_stakes_clear_locks, + ), + "destroy_alpha_in_out_stakes_clear_locks should complete" + ); + assert!(!Lock::::contains_key((owner_cold, netuid, owner_hot))); + }); +} + +#[test] +fn test_destroy_alpha_in_out_stakes() { + new_test_ext(0).execute_with(|| { + let (_, _, netuid) = setup_staked_subnet(); + let mut status = run_destroy_alpha_get_total_and_settle(netuid); + let w = Weight::from_parts(u64::MAX, u64::MAX); + let mut weight_meter = WeightMeter::with_limit(w); + assert!( + SubtensorModule::destroy_alpha_in_out_stakes(netuid, &mut weight_meter, &mut status), + "destroy_alpha_in_out_stakes should complete" + ); + }); +} + +#[test] +fn test_destroy_alpha_clean_alpha_resumes_with_limited_weight() { + new_test_ext(0).execute_with(|| { + let (_, _, netuid) = setup_staked_subnet(); + let w = Weight::from_parts(u64::MAX, u64::MAX); + let mut weight_meter = WeightMeter::with_limit(w); + let mut status = dissolve_cleanup_status(netuid); + assert!(run_resumable_netuid_cleanup_with_status( + netuid, + &mut weight_meter, + &mut status, + |netuid, weight_meter, last_key, status| { + SubtensorModule::destroy_alpha_in_out_stakes_get_total_alpha_value( + netuid, + weight_meter, + last_key, + status, + ) + }, + )); + status.subnet_distributed_tao = Some(0); + let mut weight_meter2 = WeightMeter::with_limit(w); + assert!(run_resumable_netuid_cleanup_with_status( + netuid, + &mut weight_meter2, + &mut status, + |netuid, weight_meter, last_key, status| { + SubtensorModule::destroy_alpha_in_out_stakes_settle_stakes( + netuid, + weight_meter, + last_key, + status, + ) + }, + )); + + let read_weight = ::DbWeight::get().reads(1); + let mut weight_meter3 = WeightMeter::with_limit(read_weight); + let (done, mut last_key) = SubtensorModule::destroy_alpha_in_out_stakes_clean_alpha( + netuid, + &mut weight_meter3, + None, + ); + assert!(!done); + + let mut iterations = 0; + while Alpha::::iter().any(|((_, _, nu), _)| nu == netuid) { + let mut weight_meter = WeightMeter::with_limit(Weight::from_parts(u64::MAX, u64::MAX)); + let (done, new_key) = SubtensorModule::destroy_alpha_in_out_stakes_clean_alpha( + netuid, + &mut weight_meter, + last_key, + ); + last_key = new_key; + assert!( + done, + "clean_alpha should finish once all alpha entries are removed" + ); + iterations += 1; + assert!( + iterations < 10, + "clean_alpha should complete within a few passes" + ); + } + }); +} + +#[test] +fn test_destroy_alpha_in_out_stakes_settle_stakes_multi_block_total_issuance() { + new_test_ext(0).execute_with(|| { + // Create 3 independent stakers to force multi-block settle. + let cold_base = U256::from(2000); + let hot_base = U256::from(3000); + let netuid = add_dynamic_network(&hot_base, &cold_base); + + let stake_tao: u64 = 1000; + setup_reserves( + netuid, + (stake_tao * 1_000_000).into(), + (stake_tao * 10_000_000).into(), + ); + let amount: TaoBalance = stake_tao.into(); + + for index in 1..=10 { + let cold = U256::from(cold_base + index); + let hot = U256::from(hot_base + index); + assert_ok!(SubtensorModule::create_account_if_non_existent(&cold, &hot)); + add_balance_to_coldkey_account(&cold, amount); + assert_ok!(SubtensorModule::stake_into_subnet( + &hot, + &cold, + netuid, + amount, + ::SwapInterface::max_price(), + false, + )); + } + let w = Weight::from_parts(u64::MAX, u64::MAX); + let mut weight_meter = WeightMeter::with_limit(w); + let mut status = dissolve_cleanup_status(netuid); + + // Phase 1: Get total alpha (prerequisite for settle_stakes) + assert!(run_resumable_netuid_cleanup_with_status( + netuid, + &mut weight_meter, + &mut status, + |netuid, weight_meter, last_key, status| { + SubtensorModule::destroy_alpha_in_out_stakes_get_total_alpha_value( + netuid, + weight_meter, + last_key, + status, + ) + }, + )); + + status.subnet_distributed_tao = Some(0); + status.last_key = None; + + let total_issuance_before = TotalIssuance::::get(); + + // Phase 2: settle_stakes with per-call weight enough for 2 out of 10 stakers. + // + // Each hotkey+coldkey consumes: + // reads(1) outer + reads(2) inner + writes(1) value + + // reads_writes(11, 3) transfer = reads(14) + writes(4) + // Weight for two hotkeys = reads(28) + writes(8) + // Plus reads(1) to attempt the third outer iteration → reads(29) + writes(8) + let per_call = ::DbWeight::get() + .reads(29) + .saturating_add(::DbWeight::get().writes(8)); + + let mut last_key = status.last_key.clone(); + let mut completed = false; + let mut iterations = 0u32; + + for block in 1..=10 { + let mut meter = WeightMeter::with_limit(per_call); + let (done, new_key) = SubtensorModule::destroy_alpha_in_out_stakes_settle_stakes( + netuid, + &mut meter, + last_key.clone(), + &mut status, + ); + last_key = new_key; + + next_block(); + + assert_eq!( + TotalIssuance::::get(), + total_issuance_before, + "TotalIssuance unchanged after block {block}" + ); + + if done { + completed = true; + iterations = block; + break; + } + } + + assert!(completed, "settle_stakes should finish"); + assert!( + iterations >= 5, + "should need multiple blocks, completed in {iterations}" + ); + }); +} diff --git a/pallets/subtensor/src/tests/dissolution.rs b/pallets/subtensor/src/tests/dissolution.rs new file mode 100644 index 0000000000..ec336db93d --- /dev/null +++ b/pallets/subtensor/src/tests/dissolution.rs @@ -0,0 +1,200 @@ +#![allow( + clippy::unwrap_used, + clippy::indexing_slicing, + clippy::arithmetic_side_effects +)] + +use super::mock::*; +use crate::*; +use frame_support::{assert_ok, weights::WeightMeter}; +use frame_system::RawOrigin; +use sp_core::U256; +use subtensor_runtime_common::TaoBalance; +use subtensor_swap_interface::SwapHandler; + +/// Stake `n` distinct hotkeys (each with its own coldkey) onto `netuid`. +fn stake_n_hotkeys(netuid: NetUid, n: u64, amount_tao: u64) { + for i in 0..n { + let hot = U256::from(10_000 + i); + let cold = U256::from(20_000 + i); + let amount: TaoBalance = amount_tao.into(); + assert_ok!(SubtensorModule::create_account_if_non_existent(&cold, &hot)); + add_balance_to_coldkey_account(&cold, amount); + assert_ok!(SubtensorModule::stake_into_subnet( + &hot, + &cold, + netuid, + amount, + ::SwapInterface::max_price(), + false, + )); + } +} + +/// Off-by-one checkpoint in the hand-rolled alpha cleanup loops. +/// +/// `destroy_alpha_in_out_stakes_get_total_alpha_value` (and its siblings `..._settle_stakes`, +/// `..._clean_alpha`) checkpoint the hotkey that *ran out of weight* via +/// `last_hot = Some(hot)` and resume with `TotalHotkeyAlpha::iter_from(hashed_key_for(hot))`. +/// Substrate's `iter_from` is EXCLUSIVE (yields keys strictly AFTER the given key), so the +/// boundary hotkey is silently SKIPPED on the next batch. +/// +/// Consequence in the real `on_idle` path (which runs over many blocks with a real weight +/// budget): the denominator `subnet_total_alpha_value` is undercounted, settle never pays the +/// skipped stakers, and clean_alpha leaves orphaned Alpha entries. +/// +/// This test computes the total once with an unlimited budget (the ground truth) and again +/// with a tiny per-block budget resumed across batches exactly like `on_idle`. They must be +/// equal; on the buggy code the resumed value is strictly smaller. +#[test] +fn get_total_alpha_value_undercounts_when_weight_limited() { + new_test_ext(0).execute_with(|| { + let owner_cold = U256::from(1001); + let owner_hot = U256::from(1002); + let netuid = add_dynamic_network(&owner_hot, &owner_cold); + setup_reserves( + netuid, + (1_000u64 * 1_000_000).into(), + (1_000u64 * 10_000_000).into(), + ); + + // Enough stakers that the resumable scan must span several weight-limited batches. + stake_n_hotkeys(netuid, 8, 1_000); + + // --- Ground truth: one pass, unlimited budget. + let mut ref_status = dissolve_cleanup_status(netuid); + let mut ref_meter = WeightMeter::with_limit(Weight::from_parts(u64::MAX, u64::MAX)); + let (done, _) = SubtensorModule::destroy_alpha_in_out_stakes_get_total_alpha_value( + netuid, + &mut ref_meter, + None, + &mut ref_status, + ); + assert!(done, "reference pass should finish in a single batch"); + let true_total = ref_status.subnet_total_alpha_value.unwrap(); + assert!(true_total > 0, "test setup produced no alpha value"); + + // --- Buggy path: tiny budget (2 reads/block), resumed via `last_key` across batches, + // exactly as `on_idle` does block-by-block. + let per_block_budget = ::DbWeight::get().reads(2); + let mut status = dissolve_cleanup_status(netuid); + let mut last_key: Option> = None; + let mut batches = 0u32; + let resumed_total = loop { + let mut meter = WeightMeter::with_limit(per_block_budget); + let (done, new_key) = + SubtensorModule::destroy_alpha_in_out_stakes_get_total_alpha_value( + netuid, + &mut meter, + last_key.clone(), + &mut status, + ); + batches += 1; + assert!(batches < 10_000, "resumable scan did not terminate"); + if done { + break status.subnet_total_alpha_value.unwrap(); + } + last_key = new_key; + }; + + assert_eq!( + resumed_total, true_total, + "weight-limited resume undercounted total alpha ({resumed_total} vs true \ + {true_total}); boundary hotkeys were skipped by exclusive iter_from" + ); + }); +} + +/// A netuid whose data is still being torn down can be handed to a new subnet. +/// +/// `do_dissolve_network` removes the netuid from `NetworksAdded` and pushes it to +/// `DissolveCleanupQueue`. The orchestrator later pops it into `CurrentDissolveCleanupStatus` +/// and deletes its storage over many `on_idle` blocks. But `get_next_netuid` only excludes +/// `DissolveCleanupQueue` — NOT `CurrentDissolveCleanupStatus`. During the (multi-block) +/// cleanup window the netuid is in neither `NetworksAdded` nor the queue, so it looks free and +/// `get_next_netuid` re-hands it to a brand-new subnet, whose storage the ongoing cleanup then +/// destroys. +#[test] +fn in_progress_cleanup_netuid_must_not_be_reused() { + new_test_ext(0).execute_with(|| { + // Three live subnets (root is netuid 0). + let _n1 = add_dynamic_network(&U256::from(101), &U256::from(1)); + let n2 = add_dynamic_network(&U256::from(102), &U256::from(2)); + let _n3 = add_dynamic_network(&U256::from(103), &U256::from(3)); + assert!(SubtensorModule::if_subnet_exist(n2)); + + // Governance dissolves the middle subnet -> queued for cleanup. + assert_ok!(SubtensorModule::do_dissolve_network(n2)); + assert!(DissolveCleanupQueue::::get().contains(&n2)); + + // Reproduce the exact intermediate state of an in-progress, multi-block cleanup: + // the orchestrator (`remove_data_for_dissolved_networks`) pops n2 from the queue into + // `CurrentDissolveCleanupStatus` and tears its storage down across several on_idle + // calls. During that window n2 is in neither `NetworksAdded` nor `DissolveCleanupQueue`. + // DissolveCleanupQueue::::mutate(|q| q.retain(|n| *n != n2)); + CurrentDissolveCleanupStatus::::set(Some( + crate::subnets::dissolution::DissolveCleanupStatus::new(n2), + )); + + // While n2's storage is actively being deleted, the netuid allocator must not reuse it. + let next = SubtensorModule::get_next_netuid(); + assert_ne!( + next, n2, + "get_next_netuid handed out the in-progress cleanup netuid {n2:?}; a new subnet \ + registered here would be silently destroyed by the ongoing on_idle cleanup" + ); + }); +} + +/// there is NO lock protecting the in-progress netuid. +/// +/// This test documents the CURRENT (buggy) behavior: a fresh permissionless `register_network` +/// issued while n2's storage is being torn down is handed n2 and recreates the subnet on top of +/// storage that the ongoing `on_idle` cleanup will keep deleting. It passes on the buggy branch +/// and is expected to fail (no longer collide) once the in-progress netuid is excluded. +/// +/// Note: `WaitingForDissolvedSubnetCleanup` exists in `errors.rs` but is never used anywhere — +/// the intended guard was declared and never wired up. +#[test] +fn e2e_registration_reuses_in_progress_cleanup_netuid() { + new_test_ext(0).execute_with(|| { + let _n1 = add_dynamic_network(&U256::from(101), &U256::from(1)); + let n2 = add_dynamic_network(&U256::from(102), &U256::from(2)); + let _n3 = add_dynamic_network(&U256::from(103), &U256::from(3)); + + assert_ok!(SubtensorModule::do_dissolve_network(n2)); + + // Move n2 into the in-progress cleanup state (popped from queue, cleanup not finished). + // DissolveCleanupQueue::::mutate(|q| q.retain(|n| *n != n2)); + CurrentDissolveCleanupStatus::::set(Some( + crate::subnets::dissolution::DissolveCleanupStatus::new(n2), + )); + assert!(!SubtensorModule::if_subnet_exist(n2)); + + // Fresh coldkey/hotkey -> passes the per-coldkey registration rate limit. + let new_cold = U256::from(909); + let new_hot = U256::from(910); + let lock = SubtensorModule::get_network_lock_cost(); + add_balance_to_coldkey_account(&new_cold, lock.into()); + TotalIssuance::::mutate(|ti| *ti = ti.saturating_add(lock)); + + // Below the subnet limit -> the immediate registration path runs with NO guard against + // the in-progress netuid. + assert_ok!(SubtensorModule::register_network( + RawOrigin::Signed(new_cold).into(), + new_hot + )); + + // The collision happened: n2 is live again... + assert!( + !SubtensorModule::if_subnet_exist(n2), + "registration did not reuse n2 (good - bug may be fixed)" + ); + // ...while its cleanup is still pending and will keep deleting the new subnet's storage. + assert_eq!( + CurrentDissolveCleanupStatus::::get().map(|s| s.netuid), + Some(n2), + "n2 cleanup still in progress; on_idle will wipe the freshly registered subnet" + ); + }); +} diff --git a/pallets/subtensor/src/tests/evm.rs b/pallets/subtensor/src/tests/evm.rs index d692a72f72..d2e98779d4 100644 --- a/pallets/subtensor/src/tests/evm.rs +++ b/pallets/subtensor/src/tests/evm.rs @@ -31,6 +31,15 @@ fn sign_evm_message>(pair: &ecdsa::Pair, message: M) -> ecdsa::Si sig } +#[test] +fn test_weight_usage() { + new_test_ext(1).execute_with(|| { + let write = ::DbWeight::get().writes(1); + assert_eq!(write.ref_time(), 100_000_000); + assert_eq!(write.proof_size(), 0); + }); +} + #[test] fn test_associate_evm_key_success() { new_test_ext(1).execute_with(|| { @@ -339,6 +348,130 @@ fn test_associate_evm_key_rate_limit_exceeded() { }); } +#[test] +fn test_associate_evm_key_cap_exceeded() { + new_test_ext(1).execute_with(|| { + let netuid = NetUid::from(1); + + let tempo: u16 = 2; + let modality: u16 = 2; + add_network(netuid, tempo, modality); + System::set_block_number(EvmKeyAssociateRateLimit::get()); + + let coldkey = U256::from(1); + let hotkey = U256::from(2); + let _ = SubtensorModule::create_account_if_non_existent(&coldkey, &hotkey); + register_ok_neuron(netuid, hotkey, coldkey, 0); + let uid = SubtensorModule::get_uid_for_net_and_hotkey(netuid, &hotkey).unwrap(); + + let pair = ecdsa::Pair::generate().0; + let evm_key = public_to_evm_key(&pair.public()); + + // Fill the reverse-index bucket for `evm_key` to capacity with other UIDs. + for i in 0..MAX_ASSOCIATED_UIDS_PER_EVM_ADDRESS as u16 { + SubtensorModule::set_associated_evm_address(netuid, uid + 1 + i, evm_key, 1); + } + assert_eq!( + AssociatedUidsByEvmAddress::::get(netuid, evm_key).len(), + MAX_ASSOCIATED_UIDS_PER_EVM_ADDRESS as usize + ); + + // A valid association for the real neuron's UID must be rejected: it would need a + // brand-new slot in an already-full bucket. + let block_number = frame_system::Pallet::::block_number(); + let hashed_block_number = keccak_256(block_number.encode().as_ref()); + let hotkey_bytes = hotkey.encode(); + let message = [ + hotkey_bytes.as_ref(), + <[u8; 32] as AsRef<[u8]>>::as_ref(&hashed_block_number), + ] + .concat(); + let signature = sign_evm_message(&pair, message); + + assert_noop!( + SubtensorModule::associate_evm_key( + RuntimeOrigin::signed(hotkey), + netuid, + evm_key, + block_number, + signature, + ), + Error::::EvmKeyAssociationLimitExceeded + ); + }); +} + +#[test] +fn test_evm_address_index_capacity_allows_refresh_when_full() { + new_test_ext(1).execute_with(|| { + let netuid = NetUid::from(1); + let evm_key = H160::from_slice(&[7u8; 20]); + + // Fill the bucket to capacity with distinct UIDs 0..MAX. + for uid in 0..MAX_ASSOCIATED_UIDS_PER_EVM_ADDRESS as u16 { + SubtensorModule::set_associated_evm_address(netuid, uid, evm_key, 1); + } + + // A UID already tracked by the full bucket may be re-associated (e.g. block refresh): + // it consumes no new slot. + let tracked_uid = 0u16; + assert_ok!(SubtensorModule::ensure_evm_address_index_capacity( + netuid, + tracked_uid, + evm_key + )); + + // The refresh updates the stored block in place without growing the bucket. + SubtensorModule::set_associated_evm_address(netuid, tracked_uid, evm_key, 42); + let bucket = AssociatedUidsByEvmAddress::::get(netuid, evm_key); + assert_eq!(bucket.len(), MAX_ASSOCIATED_UIDS_PER_EVM_ADDRESS as usize); + assert_eq!( + bucket.iter().find(|(u, _)| *u == tracked_uid).unwrap().1, + 42 + ); + + // A brand-new UID is rejected once the bucket is full. + assert_err!( + SubtensorModule::ensure_evm_address_index_capacity( + netuid, + MAX_ASSOCIATED_UIDS_PER_EVM_ADDRESS as u16, + evm_key + ), + Error::::EvmKeyAssociationLimitExceeded + ); + }); +} + +#[test] +fn test_evm_address_index_capacity_rejects_switch_onto_full_address() { + new_test_ext(1).execute_with(|| { + let netuid = NetUid::from(1); + let addr_a = H160::from_slice(&[0xaa; 20]); + let addr_b = H160::from_slice(&[0xbb; 20]); + + // UID 100 currently associated to addr_a. + let uid = 100u16; + SubtensorModule::set_associated_evm_address(netuid, uid, addr_a, 1); + + // addr_b is filled to capacity with other UIDs. + for u in 0..MAX_ASSOCIATED_UIDS_PER_EVM_ADDRESS as u16 { + SubtensorModule::set_associated_evm_address(netuid, u, addr_b, 1); + } + + // Moving UID 100 onto the full addr_b must be rejected... + assert_err!( + SubtensorModule::ensure_evm_address_index_capacity(netuid, uid, addr_b), + Error::::EvmKeyAssociationLimitExceeded + ); + + // ...leaving UID 100 still associated to addr_a. + assert_eq!( + AssociatedEvmAddress::::get(netuid, uid).map(|(k, _)| k), + Some(addr_a) + ); + }); +} + #[test] fn test_associate_evm_key_uid_not_found() { new_test_ext(1).execute_with(|| { diff --git a/pallets/subtensor/src/tests/locks.rs b/pallets/subtensor/src/tests/locks.rs index 91b48b7881..ea5fe0e52e 100644 --- a/pallets/subtensor/src/tests/locks.rs +++ b/pallets/subtensor/src/tests/locks.rs @@ -2951,6 +2951,60 @@ fn test_change_subnet_owner_if_needed_reassigns_to_subnet_king() { }); } +#[test] +fn test_run_coinbase_reassigns_subnet_owner_by_conviction_on_epoch() { + new_test_ext(1).execute_with(|| { + let old_owner_coldkey = U256::from(1); + let old_owner_hotkey = U256::from(2); + let netuid = setup_subnet_with_stake(old_owner_coldkey, old_owner_hotkey, 100_000_000_000); + SubnetOwner::::insert(netuid, old_owner_coldkey); + SubnetOwnerHotkey::::insert(netuid, old_owner_hotkey); + + let new_owner_coldkey = U256::from(5); + let king_hotkey = U256::from(6); + assert_ok!(SubtensorModule::create_account_if_non_existent( + &new_owner_coldkey, + &king_hotkey + )); + + let now = crate::staking::lock::ONE_YEAR + 1; + System::set_block_number(now); + NetworkRegisteredAt::::insert(netuid, 1); + SubnetAlphaOut::::insert(netuid, AlphaBalance::from(10_000u64)); + SubtensorModule::set_tempo_unchecked(netuid, 1); + LastEpochBlock::::insert(netuid, now.saturating_sub(1)); + PendingEpochAt::::insert(netuid, 0); + + let locked_mass = AlphaBalance::from(1_000u64); + Lock::::insert( + (new_owner_coldkey, netuid, king_hotkey), + LockState { + locked_mass, + conviction: U64F64::from_num(1_000), + last_update: now, + }, + ); + HotkeyLock::::insert( + netuid, + king_hotkey, + LockState { + locked_mass, + conviction: U64F64::from_num(1_000), + last_update: now, + }, + ); + + assert_eq!(SubnetOwner::::get(netuid), old_owner_coldkey); + assert_eq!(SubnetOwnerHotkey::::get(netuid), old_owner_hotkey); + + SubtensorModule::run_coinbase(SubtensorModule::mint_tao(0.into())); + + assert_eq!(SubnetOwner::::get(netuid), new_owner_coldkey); + assert_eq!(SubnetOwnerHotkey::::get(netuid), king_hotkey); + assert_eq!(LastEpochBlock::::get(netuid), now); + }); +} + #[test] fn test_change_subnet_owner_rebuilds_old_owner_hotkey_by_lock_mode() { new_test_ext(1).execute_with(|| { @@ -3922,6 +3976,7 @@ fn test_subnet_dissolution_orphans_locks() { // Dissolve the subnet assert_ok!(SubtensorModule::do_dissolve_network(netuid)); + run_block_idle(); // All Alpha entries are gone assert_eq!( @@ -3956,6 +4011,7 @@ fn test_subnet_dissolution_and_netuid_reuse() { // Dissolve old subnet assert_ok!(SubtensorModule::do_dissolve_network(netuid)); + run_block_idle(); // No stale lock from old subnet remains let stale_lock = Lock::::get((coldkey, netuid, hotkey_old)); diff --git a/pallets/subtensor/src/tests/migration.rs b/pallets/subtensor/src/tests/migration.rs index ec24697522..d087df6058 100644 --- a/pallets/subtensor/src/tests/migration.rs +++ b/pallets/subtensor/src/tests/migration.rs @@ -28,7 +28,7 @@ use frame_system::Config; use pallet_drand::types::RoundNumber; use pallet_scheduler::ScheduledOf; use scale_info::prelude::collections::VecDeque; -use sp_core::{H256, U256, crypto::Ss58Codec}; +use sp_core::{H160, H256, U256, crypto::Ss58Codec}; use sp_io::hashing::twox_128; use sp_runtime::{ AccountId32, @@ -37,7 +37,7 @@ use sp_runtime::{ use sp_std::marker::PhantomData; use substrate_fixed::types::{I96F32, U64F64}; use substrate_fixed::{traits::ToFixed, types::extra::U2}; -use subtensor_runtime_common::{AlphaBalance, NetUidStorageIndex, TaoBalance}; +use subtensor_runtime_common::{AlphaBalance, NetUid, NetUidStorageIndex, TaoBalance}; #[allow(clippy::arithmetic_side_effects)] fn close(value: u64, target: u64, eps: u64) { @@ -47,6 +47,83 @@ fn close(value: u64, target: u64, eps: u64) { ) } +#[test] +fn test_migrate_associated_evm_address_index() { + new_test_ext(1).execute_with(|| { + let migration_name = b"migrate_associated_evm_address_index".to_vec(); + let netuid = NetUid::from(1); + let other_netuid = NetUid::from(2); + let evm_key = H160::repeat_byte(1); + let other_evm_key = H160::repeat_byte(2); + + HasMigrationRun::::remove(&migration_name); + AssociatedUidsByEvmAddress::::remove(netuid, evm_key); + AssociatedUidsByEvmAddress::::remove(other_netuid, other_evm_key); + + AssociatedEvmAddress::::insert(netuid, 0, (evm_key, 10)); + AssociatedEvmAddress::::insert(netuid, 1, (evm_key, 11)); + AssociatedEvmAddress::::insert(other_netuid, 0, (other_evm_key, 12)); + + crate::migrations::migrate_associated_evm_address_index::migrate_associated_evm_address_index::(); + + assert_eq!( + AssociatedUidsByEvmAddress::::get(netuid, evm_key).into_inner(), + vec![(0, 10), (1, 11)] + ); + assert_eq!( + AssociatedUidsByEvmAddress::::get(other_netuid, other_evm_key).into_inner(), + vec![(0, 12)] + ); + assert!(HasMigrationRun::::get(&migration_name)); + }); +} + +#[test] +fn test_migrate_associated_evm_address_index_reconciles_over_cap_buckets() { + new_test_ext(1).execute_with(|| { + let migration_name = b"migrate_associated_evm_address_index".to_vec(); + let netuid = NetUid::from(1); + let evm_key = H160::repeat_byte(1); + + HasMigrationRun::::remove(&migration_name); + AssociatedUidsByEvmAddress::::remove(netuid, evm_key); + + // Seed more forward-map associations for a single address than the reverse index can hold. + let cap = MAX_ASSOCIATED_UIDS_PER_EVM_ADDRESS; + let total = cap + 8; + for uid in 0..total { + AssociatedEvmAddress::::insert(netuid, uid as u16, (evm_key, 100 + uid as u64)); + } + + crate::migrations::migrate_associated_evm_address_index::migrate_associated_evm_address_index::(); + + // The reverse index is bounded by the cap. + let bucket = AssociatedUidsByEvmAddress::::get(netuid, evm_key); + assert_eq!(bucket.len() as u32, cap); + + // The forward map was pruned to match, so the two maps agree on the cap: every remaining + // forward entry is present in the reverse index, and there are no extras on either side. + let forward: Vec = AssociatedEvmAddress::::iter_prefix(netuid) + .map(|(uid, _)| uid) + .collect(); + assert_eq!(forward.len() as u32, cap); + for uid in &forward { + assert!( + bucket.iter().any(|(stored_uid, _)| stored_uid == uid), + "forward uid {uid} missing from reverse index" + ); + } + for (uid, _) in bucket.iter() { + assert!( + forward.contains(uid), + "reverse uid {uid} missing from forward map" + ); + } + + assert!(HasMigrationRun::::get(&migration_name)); + }); +} + #[test] fn test_migrate_tao_in_refund_deployment_block() { new_test_ext(1).execute_with(|| { diff --git a/pallets/subtensor/src/tests/mock.rs b/pallets/subtensor/src/tests/mock.rs index 0e3347ec89..4af03c19fe 100644 --- a/pallets/subtensor/src/tests/mock.rs +++ b/pallets/subtensor/src/tests/mock.rs @@ -6,6 +6,7 @@ use core::num::NonZeroU64; +use crate::subnets::dissolution::DissolveCleanupStatus; use crate::utils::rate_limiting::TransactionType; use crate::*; pub use frame_support::traits::Imbalance; @@ -19,8 +20,10 @@ use frame_support::{ }; use frame_system as system; use frame_system::{EnsureRoot, RawOrigin, limits}; +use pallet_commitments::pallet::Pallet as CommitmentsPallet; use pallet_subtensor_proxy as pallet_proxy; use pallet_subtensor_utility as pallet_utility; +use scale_info::TypeInfo; use share_pool::SafeFloat; use sp_core::{ConstU64, Get, H256, U256, offchain::KeyTypeId}; use sp_runtime::Perbill; @@ -31,9 +34,43 @@ use sp_runtime::{ use sp_std::{cell::RefCell, cmp::Ordering, sync::OnceLock}; use sp_tracing::tracing_subscriber; use substrate_fixed::types::U64F64; -use subtensor_runtime_common::{AuthorshipInfo, NetUid, TaoBalance}; +use subtensor_runtime_common::{AuthorshipInfo, ConstTao, NetUid, TaoBalance}; use subtensor_swap_interface::{Order, SwapHandler}; use tracing_subscriber::{EnvFilter, layer::SubscriberExt, util::SubscriberInitExt}; + +#[derive(TypeInfo)] +pub struct TestMaxFields; +impl Get for TestMaxFields { + fn get() -> u32 { + 16 + } +} + +pub struct TestCanCommit; +impl pallet_commitments::CanCommit for TestCanCommit { + fn can_commit(_netuid: NetUid, _who: &U256) -> bool { + true + } +} + +pub struct MockTempoInterface; +impl pallet_commitments::GetTempoInterface for MockTempoInterface { + fn get_epoch_index(netuid: NetUid, cur_block: u64) -> u64 { + let tempo: u64 = 360; // Default tempo + let tempo_plus_one: u64 = tempo.checked_add(1).unwrap(); + let netuid_plus_one: u64 = (u16::from(netuid) as u64).checked_add(1).unwrap(); + let block_with_offset: u64 = cur_block.checked_add(netuid_plus_one).unwrap(); + + block_with_offset.checked_div(tempo_plus_one).unwrap_or(0) + } +} + +// // Implement OnMetadataCommitment for U256 by creating a local wrapper type +// pub struct TestOnMetadataCommitment; +// impl pallet_commitments::OnMetadataCommitment for TestOnMetadataCommitment { +// fn on_metadata_commitment(_netuid: NetUid, _who: &U256) {} +// } + type Block = frame_system::mocking::MockBlock; // Configure a mock runtime to test the pallet. @@ -51,7 +88,8 @@ frame_support::construct_runtime!( Drand: pallet_drand = 9, Swap: pallet_subtensor_swap = 10, Crowdloan: pallet_crowdloan = 11, - Proxy: pallet_subtensor_proxy = 12, + Commitments: pallet_commitments = 12, + Proxy: pallet_subtensor_proxy = 13, } ); @@ -374,6 +412,18 @@ impl pallet_subtensor_swap::Config for Test { type BenchmarkHelper = (); } +// Implement pallet_commitments::Config for Test +impl pallet_commitments::Config for Test { + type Currency = Balances; + type WeightInfo = (); + type CanCommit = TestCanCommit; + type OnMetadataCommitment = (); + type MaxFields = TestMaxFields; + type InitialDeposit = ConstTao<0>; + type FieldDeposit = ConstTao<0>; + type TempoInterface = MockTempoInterface; +} + pub struct OriginPrivilegeCmp; impl PrivilegeCmp for OriginPrivilegeCmp { @@ -384,7 +434,12 @@ impl PrivilegeCmp for OriginPrivilegeCmp { pub struct CommitmentsI; impl CommitmentsInterface for CommitmentsI { - fn purge_netuid(_netuid: NetUid) {} + fn purge_netuid( + netuid: NetUid, + weight_meter: &mut frame_support::weights::WeightMeter, + ) -> bool { + CommitmentsPallet::::purge_netuid(netuid, weight_meter) + } } parameter_types! { @@ -689,6 +744,19 @@ pub(crate) fn run_to_block_ext(n: u64, enable_events: bool) { } } +#[allow(dead_code)] +pub(crate) fn run_block_idle() { + SubtensorModule::on_idle( + System::block_number(), + Weight::from_parts(u64::MAX, u64::MAX), + ); +} + +#[allow(dead_code)] +pub(crate) fn run_network_registration_queue() { + run_block_idle(); +} + #[allow(dead_code)] pub(crate) fn next_block_no_epoch(netuid: NetUid) -> u64 { // high tempo to skip automatic epochs in on_initialize @@ -1188,3 +1256,165 @@ pub fn remove_owner_registration_stake(netuid: NetUid) { AlphaBalance::ZERO ); } + +/// Runs a weight-metered cleanup step that may pause and resume via `last_key`. +pub fn run_resumable_cleanup_step(mut step: F) -> bool +where + F: FnMut(Option>) -> (bool, Option>), +{ + let mut last_key = None; + for _ in 0..100 { + let (done, new_key) = step(last_key); + if done { + return true; + } + last_key = new_key; + } + false +} + +/// Runs a resumable per-netuid cleanup helper to completion. +pub fn run_resumable_netuid_cleanup( + netuid: NetUid, + weight_meter: &mut WeightMeter, + mut step: F, +) -> bool +where + F: FnMut(NetUid, &mut WeightMeter, Option>) -> (bool, Option>), +{ + run_resumable_cleanup_step(|last_key| step(netuid, weight_meter, last_key)) +} + +/// Runs a resumable per-netuid cleanup helper that reads/writes dissolve cleanup status. +pub fn run_resumable_netuid_cleanup_with_status( + netuid: NetUid, + weight_meter: &mut WeightMeter, + status: &mut DissolveCleanupStatus, + mut step: F, +) -> bool +where + F: FnMut( + NetUid, + &mut WeightMeter, + Option>, + &mut DissolveCleanupStatus, + ) -> (bool, Option>), +{ + run_resumable_cleanup_step(|last_key| step(netuid, weight_meter, last_key, status)) +} + +pub fn dissolve_cleanup_status(netuid: NetUid) -> DissolveCleanupStatus { + let mut status = DissolveCleanupStatus::new(netuid); + status.subnet_distributed_tao = Some(0); + status +} + +/// Runs `get_total_alpha_value` then `settle_stakes` with one shared dissolve status. +pub fn run_destroy_alpha_get_total_and_settle(netuid: NetUid) -> DissolveCleanupStatus { + let w = Weight::from_parts(u64::MAX, u64::MAX); + let mut weight_meter = WeightMeter::with_limit(w); + let mut status = dissolve_cleanup_status(netuid); + assert!( + run_resumable_netuid_cleanup_with_status( + netuid, + &mut weight_meter, + &mut status, + |netuid, weight_meter, last_key, status| { + SubtensorModule::destroy_alpha_in_out_stakes_get_total_alpha_value( + netuid, + weight_meter, + last_key, + status, + ) + }, + ), + "destroy_alpha_in_out_stakes_get_total_alpha_value incomplete" + ); + status.subnet_distributed_tao = Some(0); + assert!( + run_resumable_netuid_cleanup_with_status( + netuid, + &mut weight_meter, + &mut status, + |netuid, weight_meter, last_key, status| { + SubtensorModule::destroy_alpha_in_out_stakes_settle_stakes( + netuid, + weight_meter, + last_key, + status, + ) + }, + ), + "destroy_alpha_in_out_stakes_settle_stakes incomplete" + ); + status +} + +/// Runs the α-out destroy pipeline used during dissolved-network cleanup (through final destroy). +pub fn run_destroy_alpha_in_out_stakes_full_pipeline(netuid: NetUid) { + let w = Weight::from_parts(u64::MAX, u64::MAX); + let mut weight_meter = WeightMeter::with_limit(w); + let mut status = dissolve_cleanup_status(netuid); + + assert!( + run_resumable_netuid_cleanup_with_status( + netuid, + &mut weight_meter, + &mut status, + |netuid, weight_meter, last_key, status| { + SubtensorModule::destroy_alpha_in_out_stakes_get_total_alpha_value( + netuid, + weight_meter, + last_key, + status, + ) + }, + ), + "destroy_alpha_in_out_stakes_get_total_alpha_value incomplete" + ); + status.subnet_distributed_tao = Some(0); + assert!( + run_resumable_netuid_cleanup_with_status( + netuid, + &mut weight_meter, + &mut status, + |netuid, weight_meter, last_key, status| { + SubtensorModule::destroy_alpha_in_out_stakes_settle_stakes( + netuid, + weight_meter, + last_key, + status, + ) + }, + ), + "destroy_alpha_in_out_stakes_settle_stakes incomplete" + ); + assert!( + run_resumable_netuid_cleanup( + netuid, + &mut weight_meter, + SubtensorModule::destroy_alpha_in_out_stakes_clean_alpha, + ), + "destroy_alpha_in_out_stakes_clean_alpha incomplete" + ); + assert!( + run_resumable_netuid_cleanup( + netuid, + &mut weight_meter, + SubtensorModule::destroy_alpha_in_out_stakes_clear_hotkey_totals, + ), + "destroy_alpha_in_out_stakes_clear_hotkey_totals incomplete" + ); + assert!( + run_resumable_netuid_cleanup( + netuid, + &mut weight_meter, + SubtensorModule::destroy_alpha_in_out_stakes_clear_locks, + ), + "destroy_alpha_in_out_stakes_clear_locks incomplete" + ); + assert!( + SubtensorModule::destroy_alpha_in_out_stakes(netuid, &mut weight_meter, &mut status), + "destroy_alpha_in_out_stakes incomplete" + ); +} diff --git a/pallets/subtensor/src/tests/mock_high_ed.rs b/pallets/subtensor/src/tests/mock_high_ed.rs index 6f5c69a89c..fbfe2667f4 100644 --- a/pallets/subtensor/src/tests/mock_high_ed.rs +++ b/pallets/subtensor/src/tests/mock_high_ed.rs @@ -344,7 +344,12 @@ impl PrivilegeCmp for OriginPrivilegeCmp { pub struct CommitmentsI; impl CommitmentsInterface for CommitmentsI { - fn purge_netuid(_netuid: NetUid) {} + fn purge_netuid( + _netuid: NetUid, + _weight_meter: &mut frame_support::weights::WeightMeter, + ) -> bool { + true + } } parameter_types! { diff --git a/pallets/subtensor/src/tests/mod.rs b/pallets/subtensor/src/tests/mod.rs index 2a9f71e021..fec3b580d0 100644 --- a/pallets/subtensor/src/tests/mod.rs +++ b/pallets/subtensor/src/tests/mod.rs @@ -2,9 +2,12 @@ mod auto_stake_hotkey; mod batch_tx; mod children; mod claim_root; +mod cleanup_tests; mod coinbase; mod consensus; mod delegate_info; +mod destroy_alpha_tests; +mod dissolution; mod emission; mod ensure; mod epoch; @@ -22,6 +25,7 @@ mod networks; mod neuron_info; mod recycle_alpha; mod registration; +mod remove_data_tests; mod serving; mod staking; mod staking2; diff --git a/pallets/subtensor/src/tests/networks.rs b/pallets/subtensor/src/tests/networks.rs index a967761ef5..afa7fc77a8 100644 --- a/pallets/subtensor/src/tests/networks.rs +++ b/pallets/subtensor/src/tests/networks.rs @@ -4,7 +4,7 @@ use super::mock::*; use crate::migrations::migrate_network_immunity_period; use crate::staking::lock::LockState; use crate::*; -use frame_support::{assert_err, assert_ok}; +use frame_support::{assert_err, assert_ok, weights::Weight}; use frame_system::Config; use sp_core::U256; use sp_std::collections::{btree_map::BTreeMap, vec_deque::VecDeque}; @@ -12,6 +12,11 @@ use substrate_fixed::types::{I96F32, U64F64, U96F32}; use subtensor_runtime_common::{MechId, NetUidStorageIndex, TaoBalance}; use subtensor_swap_interface::{Order, SwapHandler}; +/// Run the same α-out destroy steps as `remove_data_for_dissolved_networks` (post-root-cleanup). +fn destroy_alpha_in_out_stakes_full_pipeline_for_test(netuid: NetUid) { + run_destroy_alpha_in_out_stakes_full_pipeline(netuid); +} + #[test] fn test_registration_ok() { new_test_ext(1).execute_with(|| { @@ -79,6 +84,46 @@ fn dissolve_no_stakers_no_alpha_no_emission() { }); } +#[test] +fn dissolve_defers_cleanup_until_on_idle() { + new_test_ext(0).execute_with(|| { + let owner_cold = U256::from(11); + let owner_hot = U256::from(12); + let net = add_dynamic_network(&owner_hot, &owner_cold); + + // Set up EVM association data to verify it gets cleaned up too. + let evm_key = sp_core::H160::from_low_u64_be(42); + SubtensorModule::set_associated_evm_address(net, 0u16, evm_key, 1u64); + assert!(AssociatedEvmAddress::::contains_key(net, 0u16)); + assert!(!AssociatedUidsByEvmAddress::::get(net, evm_key).is_empty()); + + assert!(SubnetOwner::::contains_key(net)); + assert!(SubnetOwnerHotkey::::contains_key(net)); + assert!(NetworkRegisteredAt::::contains_key(net)); + assert!(!DissolveCleanupQueue::::get().contains(&net)); + + assert_ok!(SubtensorModule::do_dissolve_network(net)); + + // Network is no longer considered "existing" but data is not cleaned yet. + assert!(!SubtensorModule::if_subnet_exist(net)); + assert!(DissolveCleanupQueue::::get().contains(&net)); + assert!(SubnetOwner::::contains_key(net)); + assert!(NetworkRegisteredAt::::contains_key(net)); + // EVM data still present before on_idle cleanup. + assert!(AssociatedEvmAddress::::contains_key(net, 0u16)); + assert!(!AssociatedUidsByEvmAddress::::get(net, evm_key).is_empty()); + + // Cleanup happens in on_idle. + run_block_idle(); + assert!(!NetworkRegisteredAt::::contains_key(net)); + assert!(!SubnetOwner::::contains_key(net)); + assert!(!DissolveCleanupQueue::::get().contains(&net)); + // EVM data cleaned up as part of NetworkMapParameters phase. + assert!(!AssociatedEvmAddress::::contains_key(net, 0u16)); + assert!(AssociatedUidsByEvmAddress::::get(net, evm_key).is_empty()); + }); +} + #[test] fn dissolve_refunds_full_lock_cost_when_no_emission() { new_test_ext(0).execute_with(|| { @@ -97,6 +142,7 @@ fn dissolve_refunds_full_lock_cost_when_no_emission() { let before = SubtensorModule::get_coldkey_balance(&cold); assert_ok!(SubtensorModule::do_dissolve_network(net)); + run_block_idle(); let after = SubtensorModule::get_coldkey_balance(&cold); assert_eq!(TaoBalance::from(after), TaoBalance::from(before) + lock); @@ -129,6 +175,7 @@ fn dissolve_single_alpha_out_staker_gets_all_tao() { // Dissolve assert_ok!(SubtensorModule::do_dissolve_network(net)); + run_block_idle(); // Cold-key received full pot let after = SubtensorModule::get_coldkey_balance(&s_cold); @@ -203,6 +250,7 @@ fn dissolve_two_stakers_pro_rata_distribution() { // Dissolve assert_ok!(SubtensorModule::do_dissolve_network(net)); + run_block_idle(); // Cold-keys received their τ shares assert_eq!( @@ -283,12 +331,14 @@ fn dissolve_owner_cut_refund_logic() { let before = SubtensorModule::get_coldkey_balance(&oc); assert_ok!(SubtensorModule::do_dissolve_network(net)); + run_block_idle(); let after = SubtensorModule::get_coldkey_balance(&oc); assert!(after > before); // some refund is expected - assert_eq!( - TaoBalance::from(after), - TaoBalance::from(before) + expected_refund + let gain: TaoBalance = after.saturating_sub(before.into()); + assert!( + gain >= expected_refund, + "owner should receive at least the lock-based refund: gain {gain:?} expected_refund {expected_refund:?}" ); }); } @@ -468,7 +518,7 @@ fn dissolve_clears_all_per_subnet_storages() { TransactionKeyLastBlock::::insert((owner_hot, net, 1u16), 1u64); // EVM association indexed by (netuid, uid) - AssociatedEvmAddress::::insert(net, 0u16, (sp_core::H160::zero(), 1u64)); + SubtensorModule::set_associated_evm_address(net, 0u16, sp_core::H160::zero(), 1u64); // (Optional) subnet -> lease link SubnetUidToLeaseId::::insert(net, 42u32); @@ -477,6 +527,7 @@ fn dissolve_clears_all_per_subnet_storages() { // Dissolve // ------------------------------------------------------------------ assert_ok!(SubtensorModule::do_dissolve_network(net)); + run_block_idle(); // ------------------------------------------------------------------ // Items that must be COMPLETELY REMOVED @@ -632,6 +683,7 @@ fn dissolve_clears_all_per_subnet_storages() { // EVM association assert!(AssociatedEvmAddress::::get(net, 0u16).is_none()); + assert!(AssociatedUidsByEvmAddress::::get(net, sp_core::H160::zero()).is_empty()); // Subnet -> lease link assert!(!SubnetUidToLeaseId::::contains_key(net)); @@ -661,6 +713,7 @@ fn dissolve_alpha_out_but_zero_tao_no_rewards() { let before = SubtensorModule::get_coldkey_balance(&sc); assert_ok!(SubtensorModule::do_dissolve_network(net)); + run_block_idle(); let after = SubtensorModule::get_coldkey_balance(&sc); // No reward distributed, α-out cleared. @@ -719,6 +772,7 @@ fn dissolve_rounding_remainder_distribution() { // 3. Run full dissolve flow assert_ok!(SubtensorModule::do_dissolve_network(net)); + run_block_idle(); // 4. s1 (larger remainder) should get +1 τ on cold-key let c1_after = SubtensorModule::get_coldkey_balance(&s1c); @@ -766,6 +820,10 @@ fn dissolve_protocol_alpha_share_is_not_paid_to_users() { assert_ok!(SubtensorModule::do_dissolve_network(net)); + run_block_idle(); + + // User gets 50 / (100 alpha-in + 50 cached protocol alpha + 50 user alpha) + // of the TAO pot. The protocol share is withheld from user/owner payout. // Settlement denominator = 50 cached protocol alpha + 50 user alpha = 100 // (alpha-in is excluded). The user therefore gets 50/100 of the 200 TAO pot, // i.e. 100 TAO. The chain-bought alpha's 100 TAO share is withheld from the @@ -816,6 +874,7 @@ fn dissolve_protocol_alpha_post_deploy_includes_alpha_in() { let owner_before = SubtensorModule::get_coldkey_balance(&owner_cold); assert_ok!(SubtensorModule::do_dissolve_network(net)); + run_block_idle(); // Post-deploy denominator = 100 alpha-in + 50 cached protocol alpha // + 50 user alpha = 200. The user gets 50/200 of the 200 TAO pot. @@ -858,6 +917,7 @@ fn dissolve_chain_bought_alpha_is_converted_to_tao_and_recycled() { let owner_before = SubtensorModule::get_coldkey_balance(&owner_cold); assert_ok!(SubtensorModule::do_dissolve_network(net)); + run_block_idle(); // There are no stakers, so the entire pot is the chain-bought alpha's TAO // share. It is not paid to the owner; instead it is recycled back to the @@ -934,7 +994,7 @@ fn destroy_alpha_out_multiple_stakers_pro_rata() { let owner_before = SubtensorModule::get_coldkey_balance(&owner_cold); // 7. Run the (now credit-to-coldkey) logic - assert_ok!(SubtensorModule::destroy_alpha_in_out_stakes(netuid)); + destroy_alpha_in_out_stakes_full_pipeline_for_test(netuid); // 8. Expected τ shares via largest remainder let prod1 = (tao_pot as u128) * a1; @@ -997,7 +1057,8 @@ fn destroy_alpha_in_out_stakes_cleans_locking_coldkeys() { Lock::::insert((coldkey, other_netuid, hotkey), lock); LockingColdkeys::::insert((other_netuid, hotkey, coldkey), ()); - assert_ok!(SubtensorModule::destroy_alpha_in_out_stakes(netuid)); + DissolveCleanupQueue::::set(vec![netuid]); + run_block_idle(); assert!(!Lock::::contains_key((coldkey, netuid, hotkey))); assert!(!LockingColdkeys::::contains_key(( @@ -1041,7 +1102,8 @@ fn destroy_alpha_in_out_stakes_cleans_all_lock_aggregates() { DecayingOwnerLock::::insert(other_netuid, lock); DecayingLock::::insert(coldkey, other_netuid, false); - assert_ok!(SubtensorModule::destroy_alpha_in_out_stakes(netuid)); + DissolveCleanupQueue::::set(vec![netuid]); + run_block_idle(); assert!(!HotkeyLock::::contains_key(netuid, hotkey)); assert!(!DecayingHotkeyLock::::contains_key(netuid, hotkey)); @@ -1179,7 +1241,7 @@ fn destroy_alpha_out_many_stakers_complex_distribution() { let expected_refund = lock.saturating_sub(owner_emission_tao); // ── 6) run distribution (credits τ to coldkeys, wipes α state) ───── - assert_ok!(SubtensorModule::destroy_alpha_in_out_stakes(netuid)); + destroy_alpha_in_out_stakes_full_pipeline_for_test(netuid); // ── 7) post checks ────────────────────────────────────────────────── for i in 0..N { @@ -1264,7 +1326,15 @@ fn destroy_alpha_out_refund_gating_by_registration_block() { let owner_before = SubtensorModule::get_coldkey_balance(&owner_cold); // Run the path under test - assert_ok!(SubtensorModule::destroy_alpha_in_out_stakes(netuid)); + let mut weight_meter = + frame_support::weights::WeightMeter::with_limit(Weight::from_parts(u64::MAX, u64::MAX)); + // total alpha tracked in CurrentDissolveCleanupStatus; + // distributed tao tracked in CurrentDissolveCleanupStatus; + { + let mut status = dissolve_cleanup_status(netuid); + status.subnet_total_alpha_value = Some(0); + SubtensorModule::destroy_alpha_in_out_stakes(netuid, &mut weight_meter, &mut status); + } // Owner received their refund… let owner_after = SubtensorModule::get_coldkey_balance(&owner_cold); @@ -1311,7 +1381,15 @@ fn destroy_alpha_out_refund_gating_by_registration_block() { let owner_before = SubtensorModule::get_coldkey_balance(&owner_cold); // Run the path under test - assert_ok!(SubtensorModule::destroy_alpha_in_out_stakes(netuid)); + let mut weight_meter = + frame_support::weights::WeightMeter::with_limit(Weight::from_parts(u64::MAX, u64::MAX)); + // total alpha tracked in CurrentDissolveCleanupStatus; + // distributed tao tracked in CurrentDissolveCleanupStatus; + { + let mut status = dissolve_cleanup_status(netuid); + status.subnet_total_alpha_value = Some(0); + SubtensorModule::destroy_alpha_in_out_stakes(netuid, &mut weight_meter, &mut status); + } // No refund for non‑legacy let owner_after = SubtensorModule::get_coldkey_balance(&owner_cold); @@ -1346,7 +1424,13 @@ fn destroy_alpha_out_refund_gating_by_registration_block() { SubnetOwnerCut::::put(32_768u16); // ~50% let owner_before = SubtensorModule::get_coldkey_balance(&owner_cold); - assert_ok!(SubtensorModule::destroy_alpha_in_out_stakes(netuid)); + let mut weight_meter = + frame_support::weights::WeightMeter::with_limit(Weight::from_parts(u64::MAX, u64::MAX)); + { + let mut status = dissolve_cleanup_status(netuid); + status.subnet_total_alpha_value = Some(0); + SubtensorModule::destroy_alpha_in_out_stakes(netuid, &mut weight_meter, &mut status); + } let owner_after = SubtensorModule::get_coldkey_balance(&owner_cold); // No refund possible when lock = 0 @@ -1643,46 +1727,43 @@ fn prune_selection_complex_state_exhaustive() { } #[test] -fn register_network_prunes_and_recycles_netuid() { - new_test_ext(0).execute_with(|| { - SubnetLimit::::put(2u16); - - let n1_cold = U256::from(21); - let n1_hot = U256::from(22); - let n1 = add_dynamic_network(&n1_hot, &n1_cold); - - let n2_cold = U256::from(23); - let n2_hot = U256::from(24); - let n2 = add_dynamic_network(&n2_hot, &n2_cold); - - // Add 100 TAO to subnet accounts (lock) - let subnet_account1 = SubtensorModule::get_subnet_account_id(n1).unwrap(); - add_balance_to_coldkey_account(&subnet_account1, 100_000_000_000_u64.into()); - let subnet_account2 = SubtensorModule::get_subnet_account_id(n2).unwrap(); - add_balance_to_coldkey_account(&subnet_account2, 100_000_000_000_u64.into()); - - let imm = SubtensorModule::get_network_immunity_period(); - System::set_block_number(imm + 100); +fn get_subnet_account_id_some_while_dissolved_cleanup_pending() { + new_test_ext(1).execute_with(|| { + let cold = U256::from(44_001); + let hot = U256::from(44_002); + let net = add_dynamic_network(&hot, &cold); + assert_ok!(SubtensorModule::do_dissolve_network(net)); + assert!(!SubtensorModule::if_subnet_exist(net)); + assert!(DissolveCleanupQueue::::get().contains(&net)); + assert!( + SubtensorModule::get_subnet_account_id(net).is_some(), + "subnet TAO account must stay derivable during async dissolve cleanup" + ); + }); +} - Emission::::insert(n1, vec![AlphaBalance::from(1)]); - Emission::::insert(n2, vec![AlphaBalance::from(1_000)]); +#[test] +fn register_network_skips_dissolved_netuid() { + new_test_ext(0).execute_with(|| { + let dissolved = NetUid::from(1); + DissolveCleanupQueue::::put(vec![dissolved]); - let new_cold = U256::from(30); - let new_hot = U256::from(31); + let cold = U256::from(60); + let hot = U256::from(61); let needed: u64 = SubtensorModule::get_network_lock_cost().into(); - add_balance_to_coldkey_account(&new_cold, needed.saturating_mul(10).into()); + add_balance_to_coldkey_account(&cold, needed.saturating_mul(10).into()); assert_ok!(SubtensorModule::do_register_network( - RuntimeOrigin::signed(new_cold), - &new_hot, + RuntimeOrigin::signed(cold), + &hot, 1, None, )); - assert_eq!(TotalNetworks::::get(), 2); - assert_eq!(SubnetOwner::::get(n1), new_cold); - assert_eq!(SubnetOwnerHotkey::::get(n1), new_hot); - assert_eq!(SubnetOwner::::get(n2), n2_cold); + assert!(!NetworksAdded::::get(dissolved)); + let expected = NetUid::from(2); + assert!(NetworksAdded::::get(expected)); + assert_eq!(SubnetOwner::::get(expected), cold); }); } @@ -2258,6 +2339,7 @@ fn massive_dissolve_refund_and_reregistration_flow_is_lossless_and_cleans_state( } for &net in nets.iter() { assert_ok!(SubtensorModule::do_dissolve_network(net)); + run_block_idle(); } // ──────────────────────────────────────────────────────────────────── @@ -2472,6 +2554,7 @@ fn dissolve_clears_all_mechanism_scoped_maps_for_all_mechanisms() { // --- Dissolve the subnet --- assert_ok!(SubtensorModule::do_dissolve_network(net)); + run_block_idle(); // After dissolve, ALL mechanism-scoped items must be cleared for ALL mechanisms. @@ -2604,6 +2687,7 @@ fn dissolve_clears_all_lock_maps_for_removed_network() { // --- Dissolve --- assert_ok!(SubtensorModule::do_dissolve_network(net)); + run_block_idle(); // Ensure removed assert!(!Lock::::contains_key((cold_1, net, hot_1))); @@ -3109,6 +3193,7 @@ fn registered_subnet_counter_survives_dissolve_and_bumps_on_reregistration() { // can still detect the pre-dereg lifetime if they stored the counter // value they observed at approval time. assert_ok!(SubtensorModule::do_dissolve_network(netuid)); + run_block_idle(); assert!(!SubtensorModule::if_subnet_exist(netuid)); assert_eq!( SubtensorModule::get_registered_subnet_counter(netuid), @@ -3131,3 +3216,579 @@ fn registered_subnet_counter_survives_dissolve_and_bumps_on_reregistration() { ); }); } + +#[test] +fn dissolve_async_cleanup_leaves_phase_unset_until_idle_finishes() { + new_test_ext(0).execute_with(|| { + let owner_cold = U256::from(910); + let owner_hot = U256::from(911); + let net = add_dynamic_network(&owner_hot, &owner_cold); + + assert_ok!(SubtensorModule::do_dissolve_network(net)); + assert!( + DissolveCleanupQueue::::get().contains(&net), + "dissolved netuid should be queued for on_idle cleanup" + ); + assert!( + CurrentDissolveCleanupStatus::::get().is_none(), + "global cleanup phase is only driven from on_idle (not from do_dissolve_network)" + ); + + run_block_idle(); + + assert!( + !DissolveCleanupQueue::::get().contains(&net), + "idle cleanup should drain the dissolved net from the queue" + ); + assert!( + CurrentDissolveCleanupStatus::::get().is_none(), + "when the queue is empty, global cleanup phase storage must be cleared" + ); + }); +} + +#[test] +fn dissolve_full_on_idle_emits_dissolved_network_data_cleaned_and_clears_phase() { + // `frame_system::Pallet::events()` stays empty at block #0 in the test externalities; + // use a non-zero block like other event-asserting tests (`recycle_alpha`, etc.). + new_test_ext(1).execute_with(|| { + let owner_cold = U256::from(930); + let owner_hot = U256::from(931); + let net = add_dynamic_network(&owner_hot, &owner_cold); + + assert_ok!(SubtensorModule::do_dissolve_network(net)); + System::reset_events(); + run_block_idle(); + + assert!( + System::events().iter().any(|e| { + matches!( + &e.event, + RuntimeEvent::SubtensorModule(Event::NetworkDissolveCleanupCompleted { netuid: n }) + if *n == net + ) + }), + "expected NetworkDissolveCleanupCompleted after async dissolve pipeline" + ); + assert!( + CurrentDissolveCleanupStatus::::get().is_none(), + "global cleanup phase storage must be cleared when the queue is empty" + ); + }); +} + +#[test] +fn dissolve_two_networks_fifo_cleanup_drains_queue() { + new_test_ext(0).execute_with(|| { + let n1 = add_dynamic_network(&U256::from(940), &U256::from(941)); + let n2 = add_dynamic_network(&U256::from(942), &U256::from(943)); + + assert_ok!(SubtensorModule::do_dissolve_network(n1)); + assert_ok!(SubtensorModule::do_dissolve_network(n2)); + assert_eq!(DissolveCleanupQueue::::get(), vec![n1, n2]); + + let mut guard = 0u32; + while !DissolveCleanupQueue::::get().is_empty() { + guard = guard.saturating_add(1); + assert!( + guard < 256, + "dissolve cleanup should drain in finite idle passes (guard={guard})" + ); + run_block_idle(); + } + + assert!(!SubtensorModule::if_subnet_exist(n1)); + assert!(!SubtensorModule::if_subnet_exist(n2)); + assert!( + CurrentDissolveCleanupStatus::::get().is_none(), + "no stale phase after queue drain" + ); + }); +} + +#[test] +fn set_new_network_state_registers_subnet_with_expected_state() { + new_test_ext(1).execute_with(|| { + let cold = U256::from(9001); + let hot = U256::from(9002); + let lock_amount = SubtensorModule::get_network_lock_cost(); + add_balance_to_coldkey_account(&cold, lock_amount.saturating_mul(2.into()).into()); + TotalIssuance::::mutate(|total| *total = total.saturating_add(lock_amount)); + + let median_price = SubtensorModule::get_median_subnet_alpha_price(); + let netuid = SubtensorModule::get_next_netuid(); + + assert_ok!(SubtensorModule::set_new_network_state( + &cold, + &hot, + 1, + None, + lock_amount, + median_price, + None, + )); + + assert!(SubtensorModule::if_subnet_exist(netuid)); + assert_eq!(SubnetOwner::::get(netuid), cold); + assert_eq!(SubnetMechanism::::get(netuid), 1); + assert_eq!(SubnetLocked::::get(netuid), lock_amount); + assert_eq!( + SubtensorModule::get_uid_for_net_and_hotkey(netuid, &hot), + Ok(0) + ); + }); +} + +#[test] +fn register_network_queues_when_waiting_for_dissolve_cleanup() { + new_test_ext(0).execute_with(|| { + SubnetLimit::::put(2u16); + + let n1 = add_dynamic_network(&U256::from(9102), &U256::from(9101)); + let _n2 = add_dynamic_network(&U256::from(9202), &U256::from(9201)); + + assert_ok!(SubtensorModule::do_dissolve_network(n1)); + assert!(DissolveCleanupQueue::::get().contains(&n1)); + + let cold = U256::from(9301); + let hot = U256::from(9302); + let lock_amount = SubtensorModule::get_network_lock_cost(); + add_balance_to_coldkey_account(&cold, lock_amount.saturating_mul(2.into()).into()); + TotalIssuance::::mutate(|total| *total = total.saturating_add(lock_amount)); + + assert_ok!(SubtensorModule::do_register_network( + RuntimeOrigin::signed(cold), + &hot, + 1, + None, + )); + + assert_eq!(NetworkRegistrationQueue::::get().len(), 1); + assert_eq!(NetworkRegistrationQueue::::get()[0].coldkey, cold); + assert_eq!(TotalNetworks::::get(), 1); + assert!(!SubtensorModule::hotkey_account_exists(&hot)); + }); +} + +#[test] +fn process_network_registration_queue_registers_after_cleanup_slot_available() { + new_test_ext(0).execute_with(|| { + SubnetLimit::::put(2u16); + + let n1 = add_dynamic_network(&U256::from(9402), &U256::from(9401)); + let n2 = add_dynamic_network(&U256::from(9502), &U256::from(9501)); + + assert_ok!(SubtensorModule::do_dissolve_network(n1)); + assert!(DissolveCleanupQueue::::get().contains(&n1)); + + let cold = U256::from(9601); + let hot = U256::from(9602); + let lock_amount = SubtensorModule::get_network_lock_cost(); + add_balance_to_coldkey_account(&cold, lock_amount.saturating_mul(3.into()).into()); + TotalIssuance::::mutate(|total| *total = total.saturating_add(lock_amount)); + + assert_ok!(SubtensorModule::do_register_network( + RuntimeOrigin::signed(cold), + &hot, + 1, + None, + )); + assert_eq!(NetworkRegistrationQueue::::get().len(), 1); + + // Simulate dissolve cleanup completing and freeing a subnet slot. + DissolveCleanupQueue::::kill(); + + run_network_registration_queue(); + + assert!(NetworkRegistrationQueue::::get().is_empty()); + assert!(SubtensorModule::hotkey_account_exists(&hot)); + assert_eq!(TotalNetworks::::get(), 2); + + let registered_netuid = NetworksAdded::::iter() + .find(|(netuid, added)| *added && *netuid != n2) + .map(|(netuid, _)| netuid) + .expect("queued registration should create a new subnet"); + assert_eq!(SubnetOwner::::get(registered_netuid), cold); + }); +} + +#[test] +fn register_network_prune_registers_registration_queued() { + new_test_ext(0).execute_with(|| { + SubnetLimit::::put(2u16); + + let n1 = add_dynamic_network(&U256::from(9702), &U256::from(9701)); + let n2 = add_dynamic_network(&U256::from(9802), &U256::from(9801)); + + let imm = SubtensorModule::get_network_immunity_period(); + System::set_block_number(imm + 100); + Emission::::insert(n1, vec![AlphaBalance::from(1)]); + Emission::::insert(n2, vec![AlphaBalance::from(1_000)]); + + let cold = U256::from(9901); + let hot = U256::from(9902); + let lock_amount = SubtensorModule::get_network_lock_cost(); + add_balance_to_coldkey_account(&cold, lock_amount.saturating_mul(10.into()).into()); + TotalIssuance::::mutate(|total| *total = total.saturating_add(lock_amount)); + + assert_ok!(SubtensorModule::do_register_network( + RuntimeOrigin::signed(cold), + &hot, + 1, + None, + )); + + assert!(NetworkRegistrationQueue::::get().len() == 1); + assert!(DissolveCleanupQueue::::get().contains(&n1)); + assert!(!NetworksAdded::::get(n1)); + }); +} + +#[test] +fn set_new_network_state_fails_when_subnet_limit_reached() { + new_test_ext(1).execute_with(|| { + SubnetLimit::::put(1u16); + let _n1 = add_dynamic_network(&U256::from(10_002), &U256::from(10_001)); + + let cold = U256::from(10_011); + let hot = U256::from(10_012); + let lock_amount = SubtensorModule::get_network_lock_cost(); + add_balance_to_coldkey_account(&cold, lock_amount.saturating_mul(2.into()).into()); + + assert_err!( + SubtensorModule::set_new_network_state( + &cold, + &hot, + 1, + None, + lock_amount, + SubtensorModule::get_median_subnet_alpha_price(), + None, + ), + Error::::SubnetLimitReached + ); + + // No partial state was written. + assert_eq!(TotalNetworks::::get(), 1); + assert!(!SubtensorModule::hotkey_account_exists(&hot)); + }); +} + +#[test] +fn set_new_network_state_stores_identity_and_emits_events() { + new_test_ext(1).execute_with(|| { + let cold = U256::from(10_101); + let hot = U256::from(10_102); + let lock_amount = SubtensorModule::get_network_lock_cost(); + add_balance_to_coldkey_account(&cold, lock_amount.saturating_mul(2.into()).into()); + + let identity = SubnetIdentityOfV3 { + subnet_name: b"my subnet".to_vec(), + github_repo: b"https://github.com/example/repo".to_vec(), + subnet_contact: b"contact@example.com".to_vec(), + subnet_url: b"https://example.com".to_vec(), + discord: b"discord".to_vec(), + description: b"description".to_vec(), + logo_url: b"https://example.com/logo.png".to_vec(), + additional: b"".to_vec(), + }; + + let netuid = SubtensorModule::get_next_netuid(); + System::reset_events(); + + assert_ok!(SubtensorModule::set_new_network_state( + &cold, + &hot, + 1, + Some(identity.clone()), + lock_amount, + SubtensorModule::get_median_subnet_alpha_price(), + None, + )); + + assert_eq!(SubnetIdentitiesV3::::get(netuid), Some(identity)); + let events = System::events(); + assert!(events.iter().any(|e| matches!( + &e.event, + RuntimeEvent::SubtensorModule(Event::SubnetIdentitySet(n)) if *n == netuid + ))); + assert!(events.iter().any(|e| matches!( + &e.event, + RuntimeEvent::SubtensorModule(Event::NetworkAdded(n, m)) if *n == netuid && *m == 1 + ))); + }); +} + +#[test] +fn set_new_network_state_uses_provided_median_price_for_pool_alpha() { + new_test_ext(1).execute_with(|| { + let cold = U256::from(10_201); + let hot = U256::from(10_202); + + // Lock twice the min lock so the pool is seeded from the actual lock amount. + let min_lock = SubtensorModule::get_network_min_lock(); + let lock_amount = min_lock.saturating_mul(2.into()); + add_balance_to_coldkey_account(&cold, lock_amount.saturating_mul(2.into()).into()); + + let netuid = SubtensorModule::get_next_netuid(); + let price = U64F64::from_num(2); + + assert_ok!(SubtensorModule::set_new_network_state( + &cold, + &hot, + 1, + None, + lock_amount, + price, + None, + )); + + // Pool TAO equals the actual lock; alpha reserve is tao / price. + assert_eq!(SubnetTAO::::get(netuid), lock_amount); + let expected_alpha: u64 = u64::from(lock_amount) / 2; + assert_eq!( + SubnetAlphaIn::::get(netuid), + AlphaBalance::from(expected_alpha) + ); + }); +} + +#[test] +fn set_new_network_state_seeds_pool_with_min_lock_floor() { + new_test_ext(1).execute_with(|| { + let cold = U256::from(10_301); + let hot = U256::from(10_302); + add_balance_to_coldkey_account(&cold, 1_000_000_000.into()); + + let netuid = SubtensorModule::get_next_netuid(); + let min_lock = SubtensorModule::get_network_min_lock(); + + // Zero lock: the pool must still be seeded with the min lock floor. + assert_ok!(SubtensorModule::set_new_network_state( + &cold, + &hot, + 1, + None, + TaoBalance::ZERO, + U64F64::from_num(1), + None, + )); + + assert_eq!(SubnetTAO::::get(netuid), min_lock); + assert_eq!( + SubnetAlphaIn::::get(netuid), + AlphaBalance::from(u64::from(min_lock)) + ); + assert_eq!(SubnetLocked::::get(netuid), TaoBalance::ZERO); + }); +} + +#[test] +fn set_new_network_state_fund_locked_releases_balance_lock() { + new_test_ext(1).execute_with(|| { + let cold = U256::from(10_401); + let hot = U256::from(10_402); + let lock_amount = SubtensorModule::get_network_lock_cost(); + add_balance_to_coldkey_account(&cold, lock_amount.saturating_mul(2.into()).into()); + + let lock_id = NetworkRegistrationLockId::::get(); + let mut identifier = [0u8; 8]; + identifier[..4].copy_from_slice(b"rglk"); + identifier[4..8].copy_from_slice(&lock_id.to_le_bytes()); + + assert_ok!(SubtensorModule::lock_network_registration_cost( + &cold, + lock_amount.into(), + 0 + )); + assert!( + pallet_balances::Locks::::get(cold) + .iter() + .any(|l| l.id == identifier), + "registration lock must exist before processing" + ); + + let netuid = SubtensorModule::get_next_netuid(); + + assert_ok!(SubtensorModule::set_new_network_state( + &cold, + &hot, + 1, + None, + lock_amount, + SubtensorModule::get_median_subnet_alpha_price(), + Some(lock_id), + )); + + assert!( + pallet_balances::Locks::::get(cold) + .iter() + .all(|l| l.id != identifier), + "registration lock must be released after processing" + ); + assert!(SubtensorModule::if_subnet_exist(netuid)); + assert_eq!(SubnetLocked::::get(netuid), lock_amount); + }); +} + +#[test] +fn process_network_registration_queue_noop_when_empty() { + new_test_ext(1).execute_with(|| { + let networks_before = TotalNetworks::::get(); + + SubtensorModule::process_network_registration_queue(); + + assert!(NetworkRegistrationQueue::::get().is_empty()); + assert_eq!(TotalNetworks::::get(), networks_before); + }); +} + +#[test] +fn process_network_registration_queue_waits_for_cleanup_completion() { + new_test_ext(0).execute_with(|| { + SubnetLimit::::put(2u16); + + let n1 = add_dynamic_network(&U256::from(10_502), &U256::from(10_501)); + let _n2 = add_dynamic_network(&U256::from(10_602), &U256::from(10_601)); + + assert_ok!(SubtensorModule::do_dissolve_network(n1)); + + let cold = U256::from(10_701); + let hot = U256::from(10_702); + let lock_amount = SubtensorModule::get_network_lock_cost(); + add_balance_to_coldkey_account(&cold, lock_amount.saturating_mul(2.into()).into()); + + assert_ok!(SubtensorModule::do_register_network( + RuntimeOrigin::signed(cold), + &hot, + 1, + None, + )); + assert_eq!(NetworkRegistrationQueue::::get().len(), 1); + + // Cleanup is still pending: the queued registration must not be released. + SubtensorModule::process_network_registration_queue(); + + assert_eq!(NetworkRegistrationQueue::::get().len(), 1); + assert!(!SubtensorModule::hotkey_account_exists(&hot)); + assert_eq!(TotalNetworks::::get(), 1); + + // Once cleanup completes, the same call releases the registration. + DissolveCleanupQueue::::kill(); + SubtensorModule::process_network_registration_queue(); + + assert!(NetworkRegistrationQueue::::get().is_empty()); + assert!(SubtensorModule::hotkey_account_exists(&hot)); + assert_eq!(TotalNetworks::::get(), 2); + }); +} + +#[test] +fn process_network_registration_queue_processes_one_entry_per_call() { + new_test_ext(0).execute_with(|| { + SubnetLimit::::put(3u16); + + let n1 = add_dynamic_network(&U256::from(10_802), &U256::from(10_801)); + let n2 = add_dynamic_network(&U256::from(10_902), &U256::from(10_901)); + let _n3 = add_dynamic_network(&U256::from(11_002), &U256::from(11_001)); + + assert_ok!(SubtensorModule::do_dissolve_network(n1)); + assert_ok!(SubtensorModule::do_dissolve_network(n2)); + assert_eq!(DissolveCleanupQueue::::get().len(), 2); + + let cold_a = U256::from(11_101); + let hot_a = U256::from(11_102); + let cold_b = U256::from(11_201); + let hot_b = U256::from(11_202); + for cold in [&cold_a, &cold_b] { + let lock_amount = SubtensorModule::get_network_lock_cost(); + add_balance_to_coldkey_account(cold, lock_amount.saturating_mul(2.into()).into()); + } + + assert_ok!(SubtensorModule::do_register_network( + RuntimeOrigin::signed(cold_a), + &hot_a, + 1, + None, + )); + assert_ok!(SubtensorModule::do_register_network( + RuntimeOrigin::signed(cold_b), + &hot_b, + 1, + None, + )); + assert_eq!(NetworkRegistrationQueue::::get().len(), 2); + + DissolveCleanupQueue::::kill(); + + // First call processes only the first (FIFO) entry. + SubtensorModule::process_network_registration_queue(); + assert_eq!(NetworkRegistrationQueue::::get().len(), 1); + assert!(SubtensorModule::hotkey_account_exists(&hot_a)); + assert!(!SubtensorModule::hotkey_account_exists(&hot_b)); + assert_eq!(NetworkRegistrationQueue::::get()[0].coldkey, cold_b); + + // Second call processes the remaining entry. + SubtensorModule::process_network_registration_queue(); + assert!(NetworkRegistrationQueue::::get().is_empty()); + assert!(SubtensorModule::hotkey_account_exists(&hot_b)); + assert_eq!(TotalNetworks::::get(), 3); + }); +} + +#[test] +fn process_network_registration_queue_unlocks_funds_and_charges_coldkey() { + new_test_ext(0).execute_with(|| { + SubnetLimit::::put(2u16); + + let n1 = add_dynamic_network(&U256::from(11_302), &U256::from(11_301)); + let n2 = add_dynamic_network(&U256::from(11_402), &U256::from(11_401)); + + assert_ok!(SubtensorModule::do_dissolve_network(n1)); + + let cold = U256::from(11_501); + let hot = U256::from(11_502); + let lock_amount = SubtensorModule::get_network_lock_cost(); + let lock_id = NetworkRegistrationLockId::::get(); + let mut identifier = [0u8; 8]; + identifier[..4].copy_from_slice(b"rglk"); + identifier[4..8].copy_from_slice(&lock_id.to_le_bytes()); + add_balance_to_coldkey_account(&cold, lock_amount.saturating_mul(3.into()).into()); + + assert_ok!(SubtensorModule::do_register_network( + RuntimeOrigin::signed(cold), + &hot, + 1, + None, + )); + + // Funds are locked while queued. + assert!( + pallet_balances::Locks::::get(cold) + .iter() + .any(|l| l.id == identifier) + ); + let queued_lock = NetworkRegistrationQueue::::get()[0].lock_amount; + // Use free balance: the reducible balance is already reduced by the lock. + let balance_before = pallet_balances::Pallet::::free_balance(cold); + + DissolveCleanupQueue::::kill(); + SubtensorModule::process_network_registration_queue(); + + // Lock released and the lock cost transferred to the new subnet. + assert!( + pallet_balances::Locks::::get(cold) + .iter() + .all(|l| l.id != identifier) + ); + let balance_after = pallet_balances::Pallet::::free_balance(cold); + assert_eq!(balance_before.saturating_sub(balance_after), queued_lock); + + let new_netuid = NetworksAdded::::iter() + .find(|(netuid, added)| *added && *netuid != n2) + .map(|(netuid, _)| netuid) + .expect("queued registration should create a new subnet"); + assert_eq!(SubnetOwner::::get(new_netuid), cold); + assert_eq!(SubnetLocked::::get(new_netuid), queued_lock); + }); +} diff --git a/pallets/subtensor/src/tests/remove_data_tests.rs b/pallets/subtensor/src/tests/remove_data_tests.rs new file mode 100644 index 0000000000..134be2e89f --- /dev/null +++ b/pallets/subtensor/src/tests/remove_data_tests.rs @@ -0,0 +1,1043 @@ +#![allow(clippy::expect_used, clippy::indexing_slicing, clippy::unwrap_used)] + +use super::mock::*; +use crate::subnets::dissolution::DissolveCleanupStatus; +use crate::*; +use frame_support::{assert_ok, weights::Weight}; +use sp_core::U256; +use subtensor_runtime_common::{AlphaBalance, TaoBalance}; +use subtensor_swap_interface::SwapHandler; + +// Import required types from the correct locations +use pallet_commitments::pallet::Pallet as CommitmentsPallet; +use pallet_commitments::{CommitmentInfo, Data}; +use sp_runtime::BoundedVec; + +fn call_remove_single_value(weight_meter: &mut WeightMeter, weight: Weight) -> bool { + if !weight_meter.can_consume(weight) { + return false; + } + weight_meter.consume(weight); + CurrentDissolveCleanupStatus::::kill(); + true +} + +#[test] +fn test_remove_single_value() { + new_test_ext(0).execute_with(|| { + CurrentDissolveCleanupStatus::::set(Some(DissolveCleanupStatus::new(NetUid::from( + 1, + )))); + let w = Weight::from_parts(100_u64, 100_u64); + + let mut weight_meter = frame_support::weights::WeightMeter::with_limit(w); + assert!(call_remove_single_value(&mut weight_meter, w)); + assert!(CurrentDissolveCleanupStatus::::get().is_none()); + }); +} + +#[test] +fn test_remove_single_value_failed() { + new_test_ext(0).execute_with(|| { + CurrentDissolveCleanupStatus::::set(Some(DissolveCleanupStatus::new(NetUid::from( + 1, + )))); + let w = Weight::from_parts(100_u64, 100_u64); + + let mut weight_meter = + frame_support::weights::WeightMeter::with_limit(Weight::from_parts(50_u64, 50_u64)); + assert!(!call_remove_single_value(&mut weight_meter, w)); + assert!(CurrentDissolveCleanupStatus::::get().is_some()); + }); +} + +/// Test the remove_data_for_dissolved_networks macro function by testing each phase individually +#[test] +fn test_remove_data_for_dissolved_networks_all_phases() { + new_test_ext(0).execute_with(|| { + let owner_cold = U256::from(1001); + let owner_hot = U256::from(1002); + let netuid = add_dynamic_network(&owner_hot, &owner_cold); + + let stake_tao: u64 = 1000000; + let lock_tao: u64 = 500; + let amount: TaoBalance = (stake_tao).into(); + setup_reserves( + netuid, + (stake_tao * 1_000_000).into(), + (stake_tao * 10_000_000).into(), + ); + + assert_ok!(SubtensorModule::create_account_if_non_existent( + &owner_cold, + &owner_hot + )); + add_balance_to_coldkey_account(&owner_cold, amount); + // Stake into subnet to create some alpha + assert_ok!(SubtensorModule::stake_into_subnet( + &owner_hot, + &owner_cold, + netuid, + amount, + ::SwapInterface::max_price(), + false, + )); + + // Now add additional balance for locking + let lock_amount: TaoBalance = (lock_tao).into(); + add_balance_to_coldkey_account(&owner_cold, lock_amount); + + // Add some commitment data + assert_ok!(CommitmentsPallet::::set_commitment( + ::RuntimeOrigin::signed(owner_hot), + netuid, + Box::new( + CommitmentInfo::<::MaxFields> { + fields: BoundedVec::try_from(vec![Data::Raw( + BoundedVec::try_from(vec![1, 2, 3]).unwrap() + )]) + .unwrap(), + } + ) + )); + + // Add some lock data - balance already added above + assert_ok!(SubtensorModule::lock_stake( + ::RuntimeOrigin::signed(owner_cold), + owner_hot, + netuid, + AlphaBalance::from(lock_tao), + )); + + // Now test the full dissolution cleanup process by running on_idle multiple times + // until all phases complete + let total_weight = Weight::from_parts(u64::MAX, u64::MAX); + let mut remaining_weight = total_weight; + + // First dissolve the network + assert_ok!(SubtensorModule::do_dissolve_network(netuid)); + + // Verify it's in the dissolved networks queue + assert!(DissolveCleanupQueue::::get().contains(&netuid)); + + // Run cleanup phases until completion + let mut iterations = 0; + let max_iterations = 30; // Should be enough to go through all phases + + while !DissolveCleanupQueue::::get().is_empty() && iterations < max_iterations { + let used_weight = SubtensorModule::on_idle(0, remaining_weight); + remaining_weight = remaining_weight.saturating_sub(used_weight); + iterations += 1; + + // If we've used all weight, reset for next iteration + if remaining_weight.is_zero() { + remaining_weight = total_weight; + } + } + + // Verify the network has been fully removed + assert!(!DissolveCleanupQueue::::get().contains(&netuid)); + assert_eq!( + CurrentDissolveCleanupStatus::::get(), + None, + "Cleanup phase should be None after completion" + ); + + // Verify the subnet no longer exists + assert!(!SubtensorModule::if_subnet_exist(netuid)); + }); +} + +#[test] +fn test_clean_up_root_claimable_for_subnet() { + new_test_ext(0).execute_with(|| { + let owner_cold = U256::from(1001); + let owner_hot = U256::from(1002); + let netuid = add_dynamic_network(&owner_hot, &owner_cold); + + // Add some stake + let stake_tao: u64 = 1000; + setup_reserves( + netuid, + (stake_tao * 1_000_000).into(), + (stake_tao * 10_000_000).into(), + ); + let amount: TaoBalance = (stake_tao).into(); + assert_ok!(SubtensorModule::create_account_if_non_existent( + &owner_cold, + &owner_hot + )); + add_balance_to_coldkey_account(&owner_cold, amount); + assert_ok!(SubtensorModule::stake_into_subnet( + &owner_hot, + &owner_cold, + netuid, + amount, + ::SwapInterface::max_price(), + false, + )); + + // Verify root dividend exists before cleanup - we'll check this by running the function + + // Test the cleanup function + let w = Weight::from_parts(u64::MAX, u64::MAX); + let mut weight_meter = frame_support::weights::WeightMeter::with_limit(w); + let (result, _) = + SubtensorModule::clean_up_root_claimable_for_subnet(netuid, &mut weight_meter, None); + // This function should return true when it completes its work (or false if weight limited) + // In our test case with generous weight limit, it should complete + assert!( + result, + "clean_up_root_claimable_for_subnet should complete successfully" + ); + }); +} + +#[test] +fn test_clean_up_root_claimed_for_subnet() { + new_test_ext(0).execute_with(|| { + let owner_cold = U256::from(1001); + let owner_hot = U256::from(1002); + let netuid = add_dynamic_network(&owner_hot, &owner_cold); + + // Add some stake to have alpha value + let stake_tao: u64 = 1000; + setup_reserves( + netuid, + (stake_tao * 1_000_000).into(), + (stake_tao * 10_000_000).into(), + ); + let amount: TaoBalance = (stake_tao).into(); + assert_ok!(SubtensorModule::create_account_if_non_existent( + &owner_cold, + &owner_hot + )); + add_balance_to_coldkey_account(&owner_cold, amount); + // Stake into subnet to create some alpha + assert_ok!(SubtensorModule::stake_into_subnet( + &owner_hot, + &owner_cold, + netuid, + amount, + ::SwapInterface::max_price(), + false, + )); + + // Note: We don't need to actually create root dividend data for this test + // The cleanup function should handle the case where there's nothing to clean up + + // Test the cleanup function + let w = Weight::from_parts(u64::MAX, u64::MAX); + let mut weight_meter = frame_support::weights::WeightMeter::with_limit(w); + let result = SubtensorModule::clean_up_root_claimed_for_subnet(netuid, &mut weight_meter); + // This function should return true when it completes its work + assert!( + result, + "clean_up_root_claimed_for_subnet should complete successfully" + ); + }); +} + +#[test] +fn test_purge_netuid() { + new_test_ext(0).execute_with(|| { + let owner_cold = U256::from(1001); + let owner_hot = U256::from(1002); + let netuid = add_dynamic_network(&owner_hot, &owner_cold); + + // Add some commitment data + assert_ok!(SubtensorModule::create_account_if_non_existent( + &owner_cold, + &owner_hot + )); + assert_ok!(CommitmentsPallet::::set_commitment( + ::RuntimeOrigin::signed(owner_hot), + netuid, + Box::new( + CommitmentInfo::<::MaxFields> { + fields: BoundedVec::try_from(vec![Data::Raw( + BoundedVec::try_from(vec![1, 2, 3]).unwrap() + )]) + .unwrap(), + } + ) + )); + + // Verify commitment exists + assert!(CommitmentsPallet::::commitment_of(netuid, owner_hot).is_some()); + + // Test the purge function + let w = Weight::from_parts(u64::MAX, u64::MAX); + let mut weight_meter = frame_support::weights::WeightMeter::with_limit(w); + let result = + ::CommitmentsInterface::purge_netuid(netuid, &mut weight_meter); + assert!( + result, + "purge_netuid should return true when it successfully purges data" + ); + + // Verify commitment was purged + assert!(CommitmentsPallet::::commitment_of(netuid, owner_hot).is_none()); + }); +} + +#[test] +fn test_clear_protocol_liquidity() { + new_test_ext(0).execute_with(|| { + let owner_cold = U256::from(1001); + let owner_hot = U256::from(1002); + let netuid = add_dynamic_network(&owner_hot, &owner_cold); + + // Add some stake to have alpha value + let stake_tao: u64 = 1000; + setup_reserves( + netuid, + (stake_tao * 1_000_000).into(), + (stake_tao * 10_000_000).into(), + ); + let amount: TaoBalance = (stake_tao).into(); + assert_ok!(SubtensorModule::create_account_if_non_existent( + &owner_cold, + &owner_hot + )); + add_balance_to_coldkey_account(&owner_cold, amount); + // Stake into subnet to create some alpha and potentially protocol liquidity + assert_ok!(SubtensorModule::stake_into_subnet( + &owner_hot, + &owner_cold, + netuid, + amount, + ::SwapInterface::max_price(), + false, + )); + + // Test the clear protocol liquidity function (through swap interface) + let w = Weight::from_parts(u64::MAX, u64::MAX); + let mut weight_meter = frame_support::weights::WeightMeter::with_limit(w); + let result = + ::SwapInterface::clear_protocol_liquidity(netuid, &mut weight_meter); + // This function should return true when it completes its work + assert!( + result, + "clear_protocol_liquidity should complete successfully" + ); + }); +} + +#[test] +fn test_remove_data_for_dissolved_networks_via_on_idle() { + new_test_ext(0).execute_with(|| { + let owner_cold = U256::from(1001); + let owner_hot = U256::from(1002); + let netuid = add_dynamic_network(&owner_hot, &owner_cold); + + // Add some stake to have alpha value and create various data + let stake_tao: u64 = 1000; + setup_reserves( + netuid, + (stake_tao * 1_000_000).into(), + (stake_tao * 10_000_000).into(), + ); + let amount: TaoBalance = (stake_tao).into(); + assert_ok!(SubtensorModule::create_account_if_non_existent( + &owner_cold, + &owner_hot + )); + add_balance_to_coldkey_account(&owner_cold, amount); + // Stake into subnet to create some alpha, locks, etc. + assert_ok!(SubtensorModule::stake_into_subnet( + &owner_hot, + &owner_cold, + netuid, + amount, + ::SwapInterface::max_price(), + false, + )); + + // Add some commitment data + assert_ok!(CommitmentsPallet::::set_commitment( + ::RuntimeOrigin::signed(owner_hot), + netuid, + Box::new( + CommitmentInfo::<::MaxFields> { + fields: BoundedVec::try_from(vec![Data::Raw( + BoundedVec::try_from(vec![1, 2, 3]).unwrap() + )]) + .unwrap(), + } + ) + )); + + // Now test the full dissolution cleanup process by running on_idle multiple times + // until all phases complete (this indirectly tests remove_data_for_dissolved_networks) + let total_weight = Weight::from_parts(u64::MAX, u64::MAX); + let mut remaining_weight = total_weight; + + // First dissolve the network + assert_ok!(SubtensorModule::do_dissolve_network(netuid)); + + // Verify it's in the dissolved networks queue + assert!(DissolveCleanupQueue::::get().contains(&netuid)); + + // Run cleanup phases until completion + let mut iterations = 0; + let max_iterations = 30; // Should be enough to go through all phases + + while !DissolveCleanupQueue::::get().is_empty() && iterations < max_iterations { + let used_weight = SubtensorModule::on_idle(0, remaining_weight); + remaining_weight = remaining_weight.saturating_sub(used_weight); + iterations += 1; + + // If we've used all weight, reset for next iteration + if remaining_weight.is_zero() { + remaining_weight = total_weight; + } + } + + // Verify the network has been fully removed + assert!(!DissolveCleanupQueue::::get().contains(&netuid)); + assert_eq!( + CurrentDissolveCleanupStatus::::get(), + None, + "Cleanup phase should be None after completion" + ); + + // Verify the subnet no longer exists + assert!(!SubtensorModule::if_subnet_exist(netuid)); + + // Verify data has been cleaned up + assert_eq!(SubtensorModule::get_subnet_owner(netuid), U256::from(0)); + assert!(CommitmentsPallet::::commitment_of(netuid, owner_hot).is_none()); + }); +} + +#[test] +fn test_destroy_alpha_in_out_stakes_get_total_alpha_value() { + new_test_ext(0).execute_with(|| { + let owner_cold = U256::from(1001); + let owner_hot = U256::from(1002); + let netuid = add_dynamic_network(&owner_hot, &owner_cold); + + // Add some stake to have alpha value + let stake_tao: u64 = 1000; + setup_reserves(netuid, (stake_tao * 1_000_000).into(), (stake_tao * 10_000_000).into()); + let amount: TaoBalance = (stake_tao).into(); + assert_ok!(SubtensorModule::create_account_if_non_existent(&owner_cold, &owner_hot)); + add_balance_to_coldkey_account(&owner_cold, amount); + // Stake into subnet to create some alpha + assert_ok!(SubtensorModule::stake_into_subnet( + &owner_hot, + &owner_cold, + netuid, + amount, + ::SwapInterface::max_price(), + false, + )); + + // Now test the function + let w = Weight::from_parts(u64::MAX, u64::MAX); + let mut weight_meter = frame_support::weights::WeightMeter::with_limit(w); + assert!( + run_resumable_netuid_cleanup_with_status( + netuid, + &mut weight_meter, + &mut dissolve_cleanup_status(netuid), + |netuid, weight_meter, last_key, status| { + SubtensorModule::destroy_alpha_in_out_stakes_get_total_alpha_value( + netuid, + weight_meter, + last_key, + status, + ) + }, + ), + "destroy_alpha_in_out_stakes_get_total_alpha_value should return true when there is alpha to process" + ); + }); +} + +#[test] +fn test_destroy_alpha_in_out_stakes_settle_stakes() { + new_test_ext(0).execute_with(|| { + let owner_cold = U256::from(1001); + let owner_hot = U256::from(1002); + let netuid = add_dynamic_network(&owner_hot, &owner_cold); + + // Add some stake to have alpha value + let stake_tao: u64 = 1000; + setup_reserves( + netuid, + (stake_tao * 1_000_000).into(), + (stake_tao * 10_000_000).into(), + ); + let amount: TaoBalance = (stake_tao).into(); + assert_ok!(SubtensorModule::create_account_if_non_existent( + &owner_cold, + &owner_hot + )); + add_balance_to_coldkey_account(&owner_cold, amount); + // Stake into subnet to create some alpha + assert_ok!(SubtensorModule::stake_into_subnet( + &owner_hot, + &owner_cold, + netuid, + amount, + ::SwapInterface::max_price(), + false, + )); + + // First, we need to get the total alpha value (simulate the previous step) + run_destroy_alpha_get_total_and_settle(netuid); + }); +} + +#[test] +fn test_destroy_alpha_in_out_stakes_clean_alpha() { + new_test_ext(0).execute_with(|| { + let owner_cold = U256::from(1001); + let owner_hot = U256::from(1002); + let netuid = add_dynamic_network(&owner_hot, &owner_cold); + + // Add some stake to have alpha value + let stake_tao: u64 = 1000; + setup_reserves(netuid, (stake_tao * 1_000_000).into(), (stake_tao * 10_000_000).into()); + let amount: TaoBalance = (stake_tao).into(); + assert_ok!(SubtensorModule::create_account_if_non_existent(&owner_cold, &owner_hot)); + add_balance_to_coldkey_account(&owner_cold, amount); + // Stake into subnet to create some alpha + assert_ok!(SubtensorModule::stake_into_subnet( + &owner_hot, + &owner_cold, + netuid, + amount, + ::SwapInterface::max_price(), + false, + )); + + // Simulate the previous two steps: get total alpha and settle stakes + let w = Weight::from_parts(u64::MAX, u64::MAX); + let mut weight_meter = frame_support::weights::WeightMeter::with_limit(w); + let mut status = dissolve_cleanup_status(netuid); + assert!( + run_resumable_netuid_cleanup_with_status( + netuid, + &mut weight_meter, + &mut status, + |netuid, weight_meter, last_key, status| { + SubtensorModule::destroy_alpha_in_out_stakes_get_total_alpha_value( + netuid, + weight_meter, + last_key, + status, + ) + }, + ) + ); + status.subnet_distributed_tao = Some(0); + let mut weight_meter2 = frame_support::weights::WeightMeter::with_limit(w); + assert!( + run_resumable_netuid_cleanup_with_status( + netuid, + &mut weight_meter2, + &mut status, + |netuid, weight_meter, last_key, status| { + SubtensorModule::destroy_alpha_in_out_stakes_settle_stakes( + netuid, + weight_meter, + last_key, + status, + ) + }, + ) + ); + // Now test the clean_alpha function + let mut weight_meter3 = frame_support::weights::WeightMeter::with_limit(w); + assert!( + run_resumable_netuid_cleanup( + netuid, + &mut weight_meter3, + SubtensorModule::destroy_alpha_in_out_stakes_clean_alpha, + ), + "destroy_alpha_in_out_stakes_clean_alpha should return true when there is alpha to clean" + ); + }); +} + +#[test] +fn test_destroy_alpha_in_out_stakes_clear_hotkey_totals() { + new_test_ext(0).execute_with(|| { + let owner_cold = U256::from(1001); + let owner_hot = U256::from(1002); + let netuid = add_dynamic_network(&owner_hot, &owner_cold); + + // Add some stake to have alpha value and hotkey totals + let stake_tao: u64 = 1000; + setup_reserves(netuid, (stake_tao * 1_000_000).into(), (stake_tao * 10_000_000).into()); + let amount: TaoBalance = (stake_tao).into(); + assert_ok!(SubtensorModule::create_account_if_non_existent(&owner_cold, &owner_hot)); + add_balance_to_coldkey_account(&owner_cold, amount); + // Stake into subnet to create some alpha and hotkey totals + assert_ok!(SubtensorModule::stake_into_subnet( + &owner_hot, + &owner_cold, + netuid, + amount, + ::SwapInterface::max_price(), + false, + )); + + // Simulate the previous three steps + let w = Weight::from_parts(u64::MAX, u64::MAX); + let mut weight_meter = frame_support::weights::WeightMeter::with_limit(w); + let mut status = dissolve_cleanup_status(netuid); + assert!( + run_resumable_netuid_cleanup_with_status( + netuid, + &mut weight_meter, + &mut status, + |netuid, weight_meter, last_key, status| { + SubtensorModule::destroy_alpha_in_out_stakes_get_total_alpha_value( + netuid, + weight_meter, + last_key, + status, + ) + }, + ) + ); + status.subnet_distributed_tao = Some(0); + let mut weight_meter2 = frame_support::weights::WeightMeter::with_limit(w); + assert!( + run_resumable_netuid_cleanup_with_status( + netuid, + &mut weight_meter2, + &mut status, + |netuid, weight_meter, last_key, status| { + SubtensorModule::destroy_alpha_in_out_stakes_settle_stakes( + netuid, + weight_meter, + last_key, + status, + ) + }, + ) + ); + let mut weight_meter3 = frame_support::weights::WeightMeter::with_limit(w); + assert!(run_resumable_netuid_cleanup( + netuid, + &mut weight_meter3, + SubtensorModule::destroy_alpha_in_out_stakes_clean_alpha, + )); + // Now test the clear_hotkey_totals function + let mut weight_meter4 = frame_support::weights::WeightMeter::with_limit(w); + assert!( + run_resumable_netuid_cleanup( + netuid, + &mut weight_meter4, + SubtensorModule::destroy_alpha_in_out_stakes_clear_hotkey_totals, + ), + "destroy_alpha_in_out_stakes_clear_hotkey_totals should return true when there are hotkey totals to clear" + ); + }); +} + +#[test] +fn test_destroy_alpha_in_out_stakes_clear_locks() { + new_test_ext(0).execute_with(|| { + let owner_cold = U256::from(1001); + let owner_hot = U256::from(1002); + let netuid = add_dynamic_network(&owner_hot, &owner_cold); + + // Add some stake to have alpha value and create locks + let stake_tao: u64 = 1000; + setup_reserves(netuid, (stake_tao * 1_000_000).into(), (stake_tao * 10_000_000).into()); + let amount: TaoBalance = (stake_tao).into(); + assert_ok!(SubtensorModule::create_account_if_non_existent(&owner_cold, &owner_hot)); + add_balance_to_coldkey_account(&owner_cold, amount); + // Stake into subnet to create some alpha and locks + assert_ok!(SubtensorModule::stake_into_subnet( + &owner_hot, + &owner_cold, + netuid, + amount, + ::SwapInterface::max_price(), + false, + )); + + // Simulate the previous four steps + let w = Weight::from_parts(u64::MAX, u64::MAX); + let mut weight_meter = frame_support::weights::WeightMeter::with_limit(w); + let mut status = dissolve_cleanup_status(netuid); + assert!( + run_resumable_netuid_cleanup_with_status( + netuid, + &mut weight_meter, + &mut status, + |netuid, weight_meter, last_key, status| { + SubtensorModule::destroy_alpha_in_out_stakes_get_total_alpha_value( + netuid, + weight_meter, + last_key, + status, + ) + }, + ) + ); + status.subnet_distributed_tao = Some(0); + let mut weight_meter2 = frame_support::weights::WeightMeter::with_limit(w); + assert!( + run_resumable_netuid_cleanup_with_status( + netuid, + &mut weight_meter2, + &mut status, + |netuid, weight_meter, last_key, status| { + SubtensorModule::destroy_alpha_in_out_stakes_settle_stakes( + netuid, + weight_meter, + last_key, + status, + ) + }, + ) + ); + let mut weight_meter3 = frame_support::weights::WeightMeter::with_limit(w); + assert!(run_resumable_netuid_cleanup( + netuid, + &mut weight_meter3, + SubtensorModule::destroy_alpha_in_out_stakes_clean_alpha, + )); + let mut weight_meter4 = frame_support::weights::WeightMeter::with_limit(w); + assert!(run_resumable_netuid_cleanup( + netuid, + &mut weight_meter4, + SubtensorModule::destroy_alpha_in_out_stakes_clear_hotkey_totals, + )); + // Now test the clear_locks function + let mut weight_meter5 = frame_support::weights::WeightMeter::with_limit(w); + assert!( + run_resumable_netuid_cleanup( + netuid, + &mut weight_meter5, + SubtensorModule::destroy_alpha_in_out_stakes_clear_locks, + ), + "destroy_alpha_in_out_stakes_clear_locks should return true when there are locks to clear" + ); + }); +} + +#[test] +fn test_destroy_alpha_in_out_stakes() { + new_test_ext(0).execute_with(|| { + let owner_cold = U256::from(1001); + let owner_hot = U256::from(1002); + let netuid = add_dynamic_network(&owner_hot, &owner_cold); + + // Add some stake to have alpha value and create locks, etc. + let stake_tao: u64 = 1000; + setup_reserves(netuid, (stake_tao * 1_000_000).into(), (stake_tao * 10_000_000).into()); + let amount: TaoBalance = (stake_tao).into(); + assert_ok!(SubtensorModule::create_account_if_non_existent(&owner_cold, &owner_hot)); + add_balance_to_coldkey_account(&owner_cold, amount); + // Stake into subnet to create some alpha and locks + assert_ok!(SubtensorModule::stake_into_subnet( + &owner_hot, + &owner_cold, + netuid, + amount, + ::SwapInterface::max_price(), + false, + )); + + // Now test the main destroy function (which should call all the steps internally) + let mut status = run_destroy_alpha_get_total_and_settle(netuid); + let w = Weight::from_parts(u64::MAX, u64::MAX); + let mut weight_meter = frame_support::weights::WeightMeter::with_limit(w); + let result = + SubtensorModule::destroy_alpha_in_out_stakes(netuid, &mut weight_meter, &mut status); + assert!(result, "destroy_alpha_in_out_stakes should return true when it successfully processes the netuid"); + }); +} + +#[test] +fn test_clean_up_hotkey_swap_records() { + new_test_ext(0).execute_with(|| { + // Create two subnets: netuid 1 and netuid 2 + let owner_cold = U256::from(1001); + let owner_hot = U256::from(1002); + let netuid_1 = add_dynamic_network(&owner_hot, &owner_cold); + assert_eq!(netuid_1, 1.into()); + + let owner_cold_2 = U256::from(2001); + let owner_hot_2 = U256::from(2002); + let netuid_2 = add_dynamic_network(&owner_hot_2, &owner_cold_2); + assert_eq!(netuid_2, 2.into()); + + // We will choose a block number such that block_number % interval == 1 + // With the default interval of 15, we use block_number = 16 (16 % 15 = 1) + // So only netuid_1 (which is 1) will be processed because 1 % 15 == 1 + let block_number: u64 = 16; // 16 % 15 = 1 + + // Insert some hotkey swap records for netuid_1 + // We'll insert two records: one old (should be removed) and one new (should remain) + let coldkey_old = U256::from(3001); + let coldkey_new = U256::from(3002); + // Set an old swap block number: old enough to be removed + let swap_block_old: u64 = 0; // This is definitely < block_number - interval (101 - 100 = 1) + // Set a new swap block number: recent enough to remain + let swap_block_new: u64 = 101; // This is >= block_number - interval (1) so should remain + + // Insert the records + LastHotkeySwapOnNetuid::::insert(netuid_1, coldkey_old, swap_block_old); + LastHotkeySwapOnNetuid::::insert(netuid_1, coldkey_new, swap_block_new); + + // Insert some hotkey swap records for netuid_2 (should not be processed because 2 % 100 != 1) + let coldkey_other = U256::from(4001); + let swap_block_other: u64 = 0; // old, but netuid_2 won't be processed + LastHotkeySwapOnNetuid::::insert(netuid_2, coldkey_other, swap_block_other); + + // Also insert a record for netuid_2 with a new swap block number to show it remains untouched + let coldkey_other_new = U256::from(4002); + let swap_block_other_new: u64 = 101; + LastHotkeySwapOnNetuid::::insert(netuid_2, coldkey_other_new, swap_block_other_new); + + // Before calling the function, verify the records exist + assert!(LastHotkeySwapOnNetuid::::contains_key( + netuid_1, + coldkey_old + )); + assert!(LastHotkeySwapOnNetuid::::contains_key( + netuid_1, + coldkey_new + )); + assert!(LastHotkeySwapOnNetuid::::contains_key( + netuid_2, + coldkey_other + )); + assert!(LastHotkeySwapOnNetuid::::contains_key( + netuid_2, + coldkey_other_new + )); + + // Call the function and get the returned weight + let returned_weight = SubtensorModule::clean_up_hotkey_swap_records(block_number.into()); + + // After the function call, for netuid_1: + // - The old record (coldkey_old, swap_block_old) should be removed because swap_block_old + interval < block_number + // (0 + 100 < 101 -> 100 < 101 -> true) + // - The new record (coldkey_new, swap_block_new) should remain because swap_block_new + interval >= block_number + // (101 + 100 >= 101 -> 201 >= 101 -> true) + assert!( + !LastHotkeySwapOnNetuid::::contains_key(netuid_1, coldkey_old), + "Old hotkey swap record for netuid_1 should have been removed" + ); + assert!( + LastHotkeySwapOnNetuid::::contains_key(netuid_1, coldkey_new), + "New hotkey swap record for netuid_1 should still exist" + ); + // For netuid_2, since it was not processed (netuid_2 % interval != block_number % interval), both records should remain + assert!( + LastHotkeySwapOnNetuid::::contains_key(netuid_2, coldkey_other), + "Hotkey swap record for netuid_2 should remain untouched" + ); + assert!( + LastHotkeySwapOnNetuid::::contains_key(netuid_2, coldkey_other_new), + "Hotkey swap record for netuid_2 should remain untouched" + ); + + // We can also check that the weight returned is reasonable (non-zero and not max) + // Note: Weight comparison is tricky, but we can at least check it's not zero + assert!( + returned_weight.ref_time() > 0, + "Returned weight should have positive ref_time" + ); + }); +} + +#[test] +fn test_remove_network_lock() { + new_test_ext(0).execute_with(|| { + let netuid = NetUid::from(1); + let other_netuid = NetUid::from(2); + let cold_1 = U256::from(1001); + let cold_2 = U256::from(1002); + let hot_1 = U256::from(2001); + let hot_2 = U256::from(2002); + let lock_state = crate::staking::lock::LockState { + locked_mass: 10u64.into(), + conviction: substrate_fixed::types::U64F64::from_num(1.5), + last_update: 1, + }; + + Lock::::insert((cold_1, netuid, hot_1), lock_state.clone()); + Lock::::insert((cold_2, netuid, hot_2), lock_state.clone()); + Lock::::insert((cold_1, other_netuid, hot_1), lock_state); + + let w = Weight::from_parts(u64::MAX, u64::MAX); + let mut weight_meter = frame_support::weights::WeightMeter::with_limit(w); + assert!(run_resumable_netuid_cleanup( + netuid, + &mut weight_meter, + SubtensorModule::remove_network_lock, + )); + + assert!(!Lock::::contains_key((cold_1, netuid, hot_1))); + assert!(!Lock::::contains_key((cold_2, netuid, hot_2))); + assert!(Lock::::contains_key((cold_1, other_netuid, hot_1))); + }); +} + +#[test] +fn test_remove_network_decaying_lock() { + new_test_ext(0).execute_with(|| { + let netuid = NetUid::from(1); + let other_netuid = NetUid::from(2); + let cold_1 = U256::from(1001); + let cold_2 = U256::from(1002); + + DecayingLock::::insert(cold_1, netuid, true); + DecayingLock::::insert(cold_2, netuid, true); + DecayingLock::::insert(cold_1, other_netuid, true); + + let w = Weight::from_parts(u64::MAX, u64::MAX); + let mut weight_meter = frame_support::weights::WeightMeter::with_limit(w); + assert!(run_resumable_netuid_cleanup( + netuid, + &mut weight_meter, + SubtensorModule::remove_network_decaying_lock, + )); + + assert!(!DecayingLock::::contains_key(cold_1, netuid)); + assert!(!DecayingLock::::contains_key(cold_2, netuid)); + assert!(DecayingLock::::contains_key(cold_1, other_netuid)); + }); +} + +#[test] +fn test_remove_network_hotkey_and_owner_lock_maps() { + new_test_ext(0).execute_with(|| { + let netuid = NetUid::from(1); + let other_netuid = NetUid::from(2); + let hot_1 = U256::from(2001); + let hot_2 = U256::from(2002); + let lock_state = crate::staking::lock::LockState { + locked_mass: 10u64.into(), + conviction: substrate_fixed::types::U64F64::from_num(1.5), + last_update: 1, + }; + + DissolveCleanupQueue::::set(vec![netuid]); + + HotkeyLock::::insert(netuid, hot_1, lock_state.clone()); + HotkeyLock::::insert(netuid, hot_2, lock_state.clone()); + HotkeyLock::::insert(other_netuid, hot_1, lock_state.clone()); + + DecayingHotkeyLock::::insert(netuid, hot_1, lock_state.clone()); + DecayingHotkeyLock::::insert(netuid, hot_2, lock_state.clone()); + DecayingHotkeyLock::::insert(other_netuid, hot_1, lock_state.clone()); + + OwnerLock::::insert(netuid, lock_state.clone()); + OwnerLock::::insert(other_netuid, lock_state); + + run_block_idle(); + + assert!(!HotkeyLock::::contains_key(netuid, hot_1)); + assert!(!HotkeyLock::::contains_key(netuid, hot_2)); + assert!(HotkeyLock::::iter_prefix(netuid).next().is_none()); + + assert!(!DecayingHotkeyLock::::contains_key(netuid, hot_1)); + assert!(!DecayingHotkeyLock::::contains_key(netuid, hot_2)); + assert!( + DecayingHotkeyLock::::iter_prefix(netuid) + .next() + .is_none() + ); + + assert!(!OwnerLock::::contains_key(netuid)); + + assert!(HotkeyLock::::contains_key(other_netuid, hot_1)); + assert!(DecayingHotkeyLock::::contains_key( + other_netuid, + hot_1 + )); + assert!(OwnerLock::::contains_key(other_netuid)); + }); +} + +#[test] +fn test_remove_network_decaying_lock_resumes_with_limited_weight() { + new_test_ext(0).execute_with(|| { + let netuid = NetUid::from(1); + for i in 0..5 { + DecayingLock::::insert(U256::from(10_000 + i), netuid, true); + } + + let read_weight = ::DbWeight::get().reads(1); + let mut weight_meter = frame_support::weights::WeightMeter::with_limit(read_weight); + let (done, mut last_key) = + SubtensorModule::remove_network_decaying_lock(netuid, &mut weight_meter, None); + assert!(!done); + + let mut iterations = 0; + while DecayingLock::::iter().any(|(_, n, _)| n == netuid) { + let mut weight_meter = + frame_support::weights::WeightMeter::with_limit(Weight::from_parts(u64::MAX, 0)); + let (done, new_key) = + SubtensorModule::remove_network_decaying_lock(netuid, &mut weight_meter, last_key); + last_key = new_key; + assert!( + done, + "remove_network_decaying_lock should finish once all entries are removed" + ); + iterations += 1; + assert!( + iterations < 10, + "cleanup should complete within a few passes" + ); + } + assert_eq!( + DecayingLock::::iter() + .filter(|(_, n, _)| *n == netuid) + .count(), + 0 + ); + }); +} + +#[test] +fn test_remove_network_childkeys_resumes_with_limited_weight() { + new_test_ext(0).execute_with(|| { + let netuid = NetUid::from(1); + for i in 0..5 { + ChildKeys::::insert(U256::from(20_000 + i), netuid, vec![(1, U256::from(1))]); + } + + let read_weight = ::DbWeight::get().reads(1); + let mut weight_meter = WeightMeter::with_limit(read_weight); + let (done, mut last_key) = + SubtensorModule::remove_network_childkeys(netuid, &mut weight_meter, None); + assert!(!done); + + let mut iterations = 0; + while ChildKeys::::iter().any(|(_, n, _)| n == netuid) { + let mut weight_meter = WeightMeter::with_limit(Weight::from_parts(u64::MAX, u64::MAX)); + let (done, new_key) = + SubtensorModule::remove_network_childkeys(netuid, &mut weight_meter, last_key); + last_key = new_key; + assert!( + done, + "remove_network_childkeys should finish once all entries are removed" + ); + iterations += 1; + assert!( + iterations < 10, + "cleanup should complete within a few passes" + ); + } + assert_eq!( + ChildKeys::::iter() + .filter(|(_, n, _)| *n == netuid) + .count(), + 0 + ); + }); +} diff --git a/pallets/subtensor/src/tests/serving.rs b/pallets/subtensor/src/tests/serving.rs index f63dcd5f81..373ca98c99 100644 --- a/pallets/subtensor/src/tests/serving.rs +++ b/pallets/subtensor/src/tests/serving.rs @@ -1132,7 +1132,7 @@ fn test_set_identity_for_non_existent_subnet() { logo_url.clone(), additional.clone(), ), - Error::::NotSubnetOwner // Since there's no owner, it should fail + Error::::SubnetNotExists // Since there's no owner, it should fail ); }); } diff --git a/pallets/subtensor/src/tests/staking.rs b/pallets/subtensor/src/tests/staking.rs index 660b6957d7..a7e735e8ec 100644 --- a/pallets/subtensor/src/tests/staking.rs +++ b/pallets/subtensor/src/tests/staking.rs @@ -776,9 +776,9 @@ fn test_add_stake_insufficient_liquidity() { }); } -/// cargo test --package pallet-subtensor --lib -- tests::staking::test_add_stake_insufficient_liquidity_one_side_ok --exact --show-output +/// cargo test --package pallet-subtensor --lib -- tests::staking::test_add_stake_input_reserve_too_low_fails --exact --show-output #[test] -fn test_add_stake_insufficient_liquidity_one_side_ok() { +fn test_add_stake_input_reserve_too_low_fails() { new_test_ext(1).execute_with(|| { let subnet_owner_coldkey = U256::from(1001); let subnet_owner_hotkey = U256::from(1002); @@ -795,13 +795,17 @@ fn test_add_stake_insufficient_liquidity_one_side_ok() { let reserve_tao = u64::from(mock::SwapMinimumReserve::get()) - 1; mock::setup_reserves(netuid, reserve_tao.into(), reserve_alpha.into()); - // Check the error - assert_ok!(SubtensorModule::add_stake( - RuntimeOrigin::signed(coldkey), - hotkey, - netuid, - amount_staked.into() - )); + // The output-side reserve is sufficient, but the input-side reserve is too small for the + // requested swap under the 1000x input-reserve cap. + assert_noop!( + SubtensorModule::add_stake( + RuntimeOrigin::signed(coldkey), + hotkey, + netuid, + amount_staked.into() + ), + Error::::InsufficientLiquidity + ); }); } @@ -876,7 +880,7 @@ fn test_remove_stake_insufficient_liquidity() { // Mock more liquidity - remove becomes successful SubnetTAO::::insert(netuid, TaoBalance::from(amount_staked + 1)); - SubnetAlphaIn::::insert(netuid, AlphaBalance::from(1)); + SubnetAlphaIn::::insert(netuid, AlphaBalance::from(alpha.to_u64() / 1000 + 1)); assert_ok!(SubtensorModule::remove_stake( RuntimeOrigin::signed(coldkey), hotkey, @@ -3042,14 +3046,14 @@ fn test_max_amount_remove_dynamic() { pallet_subtensor_swap::Error::::PriceLimitExceeded, )), ), - (10_000_000_000, 10_000_000_000, 0, Ok(u64::MAX)), + (10_000_000_000, 10_000_000_000, 0, Ok(10_000_000_000_000)), // Low bounds (numbers are empirical, it is only important that result // is sharply decreasing when limit price increases) - (1_000, 1_000, 0, Ok(u64::MAX)), - (1_001, 1_001, 0, Ok(u64::MAX)), - (1_001, 1_001, 1, Ok(17_472)), - (1_001, 1_001, 2, Ok(17_472)), - (1_001, 1_001, 1_001, Ok(17_472)), + (1_000, 1_000, 0, Ok(1_000_000)), + (1_001, 1_001, 0, Ok(1_001_000)), + (1_001, 1_001, 1, Ok(1_001_000)), + (1_001, 1_001, 2, Ok(1_001_000)), + (1_001, 1_001, 1_001, Ok(1_001_000)), (1_001, 1_001, 10_000, Ok(17_472)), (1_001, 1_001, 100_000, Ok(17_472)), (1_001, 1_001, 1_000_000, Ok(17_472)), @@ -3065,7 +3069,7 @@ fn test_max_amount_remove_dynamic() { Ok(3_030_000_000_000), ), // Normal range values with edge cases and sanity checks - (200_000_000_000, 100_000_000_000, 0, Ok(u64::MAX)), + (200_000_000_000, 100_000_000_000, 0, Ok(100_000_000_000_000)), ( 200_000_000_000, 100_000_000_000, @@ -3153,10 +3157,13 @@ fn test_max_amount_remove_dynamic() { ), Ok(v) => { let v = AlphaBalance::from(v); - assert_abs_diff_eq!( - SubtensorModule::get_max_amount_remove(netuid, limit_price.into()).unwrap(), - v, - epsilon = v / 100.into() + let actual = + SubtensorModule::get_max_amount_remove(netuid, limit_price.into()).unwrap(); + let epsilon = v / 100.into(); + let diff = actual.max(v).saturating_sub(actual.min(v)); + assert!( + diff <= epsilon, + "max remove mismatch: tao_in={tao_in}, alpha_in={alpha_in:?}, limit_price={limit_price}, actual={actual:?}, expected={v:?}, epsilon={epsilon:?}", ); } } @@ -3413,10 +3420,10 @@ fn test_max_amount_move_dynamic_stable() { // The tests below just mimic the remove_stake_limit tests - // 0 price => max is u64::MAX + // 0 price => max is capped at 1000x input reserve assert_eq!( SubtensorModule::get_max_amount_move(dynamic_netuid, stable_netuid, TaoBalance::ZERO), - Ok(AlphaBalance::MAX) + Ok(alpha_in.saturating_mul(1_000.into())) ); // Low price values don't blow things up @@ -3872,6 +3879,33 @@ fn test_add_stake_limit_fill_or_kill() { }); } +#[test] +fn test_add_stake_limit_rejects_input_over_swap_reserve_cap() { + new_test_ext(1).execute_with(|| { + let hotkey_account_id = U256::from(533454); + let coldkey_account_id = U256::from(55454); + + let netuid = add_dynamic_network(&hotkey_account_id, &coldkey_account_id); + let tao_reserve = TaoBalance::from(1_000_u64); + mock::setup_reserves(netuid, tao_reserve, AlphaBalance::from(1_000_000_000_u64)); + + let amount = tao_reserve.saturating_mul(1_000.into()) + TaoBalance::from(1_u64); + add_balance_to_coldkey_account(&coldkey_account_id, amount); + + assert_noop!( + SubtensorModule::add_stake_limit( + RuntimeOrigin::signed(coldkey_account_id), + hotkey_account_id, + netuid, + amount, + ::SwapInterface::max_price(), + true + ), + Error::::InsufficientLiquidity + ); + }); +} + #[test] fn test_add_stake_limit_partial_zero_max_stake_amount_error() { new_test_ext(1).execute_with(|| { @@ -4279,11 +4313,21 @@ fn test_move_stake_limit_partial() { // Registration now goes through the burn/swap path, which initializes swap V3 state. // Clear that state first so the manual reserve fixture below actually controls price. - assert_ok!( - ::SwapInterface::clear_protocol_liquidity(origin_netuid) + let mut origin_weight_meter = + frame_support::weights::WeightMeter::with_limit(Weight::from_parts(u64::MAX, u64::MAX)); + assert!( + ::SwapInterface::clear_protocol_liquidity( + origin_netuid, + &mut origin_weight_meter + ) ); - assert_ok!( - ::SwapInterface::clear_protocol_liquidity(destination_netuid) + let mut destination_weight_meter = + frame_support::weights::WeightMeter::with_limit(Weight::from_parts(u64::MAX, u64::MAX)); + assert!( + ::SwapInterface::clear_protocol_liquidity( + destination_netuid, + &mut destination_weight_meter + ) ); // Force-set alpha in and tao reserve to make price equal 1.5 on both origin and destination, @@ -5248,7 +5292,8 @@ fn test_large_swap() { // add network let netuid = add_dynamic_network(&owner_hotkey, &owner_coldkey); add_balance_to_coldkey_account(&coldkey, 1_000_000_000_000_000_u64.into()); - let tao = TaoBalance::from(100_000_000u64); + let swap_amount = TaoBalance::from(100_000_000_000_000_u64); + let tao = TaoBalance::from(swap_amount.to_u64() / 1000); let alpha = AlphaBalance::from(1_000_000_000_000_000_u64); SubnetTAO::::insert(netuid, tao); SubnetAlphaIn::::insert(netuid, alpha); @@ -5256,7 +5301,6 @@ fn test_large_swap() { // Force the swap to initialize ::SwapInterface::init_swap(netuid, None); - let swap_amount = TaoBalance::from(100_000_000_000_000_u64); assert_ok!(SubtensorModule::add_stake( RuntimeOrigin::signed(coldkey), owner_hotkey, diff --git a/pallets/subtensor/src/tests/subnet_emissions.rs b/pallets/subtensor/src/tests/subnet_emissions.rs index 9218eabe76..ff56be851c 100644 --- a/pallets/subtensor/src/tests/subnet_emissions.rs +++ b/pallets/subtensor/src/tests/subnet_emissions.rs @@ -151,6 +151,47 @@ fn inplace_pow_normalize_fractional_exponent() { }) } +#[test] +fn get_shares_ignores_root_prop_storage_when_prices_and_burns_match() { + new_test_ext(1).execute_with(|| { + let owner_hotkey = U256::from(70); + let owner_coldkey = U256::from(71); + let n1 = add_dynamic_network(&owner_hotkey, &owner_coldkey); + let n2 = add_dynamic_network(&owner_hotkey, &owner_coldkey); + + // Equal prices and equal miner-burn values should produce equal shares, + // regardless of the stored root proportion. + SubnetMovingPrice::::insert(n1, i96f32(1.0)); + SubnetMovingPrice::::insert(n2, i96f32(1.0)); + MinerBurned::::insert(n1, U96F32::saturating_from_num(0.0)); + MinerBurned::::insert(n2, U96F32::saturating_from_num(0.0)); + + // Deliberately make root-prop unequal. The old formula would weight + // these equal-price subnets as 1.0 : 0.1 and fail the equality checks. + RootProp::::insert(n1, U96F32::saturating_from_num(1.0)); + RootProp::::insert(n2, U96F32::saturating_from_num(0.1)); + + assert_abs_diff_eq!( + RootProp::::get(n1).to_num::(), + 1.0_f64, + epsilon = 1e-9 + ); + assert_abs_diff_eq!( + RootProp::::get(n2).to_num::(), + 0.1_f64, + epsilon = 1e-9 + ); + + let shares = SubtensorModule::get_shares(&[n1, n2]); + let s1 = shares.get(&n1).copied().unwrap().to_num::(); + let s2 = shares.get(&n2).copied().unwrap().to_num::(); + + assert_abs_diff_eq!(s1, 0.5_f64, epsilon = 1e-9); + assert_abs_diff_eq!(s2, 0.5_f64, epsilon = 1e-9); + assert_abs_diff_eq!(s1 + s2, 1.0_f64, epsilon = 1e-9); + }); +} + // /// Normal (moderate, non-zero) EMA flows across 3 subnets. // /// Expect: shares sum to ~1 and are monotonic with flows. // #[test] diff --git a/pallets/subtensor/src/tests/swap_coldkey.rs b/pallets/subtensor/src/tests/swap_coldkey.rs index fd0281ad35..ca269fe3d1 100644 --- a/pallets/subtensor/src/tests/swap_coldkey.rs +++ b/pallets/subtensor/src/tests/swap_coldkey.rs @@ -499,6 +499,105 @@ fn test_swap_coldkey_works() { }); } +#[test] +fn test_swap_coldkey_announced_transfers_locked_alpha() { + new_test_ext(1000).execute_with(|| { + let old_coldkey = U256::from(1); + let new_coldkey = U256::from(2); + let new_coldkey_hash = ::Hashing::hash_of(&new_coldkey); + let hotkey1 = U256::from(1001); + let hotkey2 = U256::from(1002); + let hotkey3 = U256::from(1003); + let ed = ExistentialDeposit::get(); + let swap_cost = SubtensorModule::get_key_swap_cost(); + let min_stake = DefaultMinStake::::get(); + let stake1 = min_stake * 10.into(); + let stake2 = min_stake * 20.into(); + let stake3 = min_stake * 30.into(); + + add_balance_to_coldkey_account(&old_coldkey, swap_cost + stake1 + stake2 + stake3 + ed); + + let ( + netuid1, + _netuid2, + _hotkeys, + hk1_alpha, + _hk2_alpha, + _hk3_alpha, + _total_ck_stake, + _identity, + _balance_before, + _total_stake_before, + ) = comprehensive_setup!( + old_coldkey, + new_coldkey, + new_coldkey_hash, + stake1, + stake2, + stake3, + hotkey1, + hotkey2, + hotkey3, + swap_cost + ed + ); + + let lock_amount = hk1_alpha / 2.into(); + assert!(!lock_amount.is_zero()); + assert_ok!(SubtensorModule::do_lock_stake( + &old_coldkey, + netuid1, + &hotkey1, + lock_amount, + )); + + let old_stake_before = SubtensorModule::get_stake_for_hotkey_and_coldkey_on_subnet( + &hotkey1, + &old_coldkey, + netuid1, + ); + let new_stake_before = SubtensorModule::get_stake_for_hotkey_and_coldkey_on_subnet( + &hotkey1, + &new_coldkey, + netuid1, + ); + let old_lock_before = + Lock::::get((old_coldkey, netuid1, hotkey1)).expect("lock should exist"); + + ColdkeySwapAnnouncements::::insert( + old_coldkey, + (System::block_number(), new_coldkey_hash), + ); + + assert_ok!(SubtensorModule::swap_coldkey_announced( + ::RuntimeOrigin::signed(old_coldkey), + new_coldkey, + )); + + assert!(ColdkeySwapAnnouncements::::get(old_coldkey).is_none()); + assert_eq!( + SubtensorModule::get_stake_for_hotkey_and_coldkey_on_subnet( + &hotkey1, + &old_coldkey, + netuid1, + ), + AlphaBalance::ZERO + ); + assert_eq!( + SubtensorModule::get_stake_for_hotkey_and_coldkey_on_subnet( + &hotkey1, + &new_coldkey, + netuid1, + ), + old_stake_before + new_stake_before + ); + assert!(Lock::::get((old_coldkey, netuid1, hotkey1)).is_none()); + assert_eq!( + Lock::::get((new_coldkey, netuid1, hotkey1)), + Some(old_lock_before) + ); + }); +} + // cargo test --package pallet-subtensor --lib -- tests::swap_coldkey::test_swap_coldkey_works_with_zero_cost --exact --nocapture #[test] fn test_swap_coldkey_works_with_zero_cost() { diff --git a/pallets/subtensor/src/tests/swap_hotkey.rs b/pallets/subtensor/src/tests/swap_hotkey.rs index 29a3322638..5e73c89ef1 100644 --- a/pallets/subtensor/src/tests/swap_hotkey.rs +++ b/pallets/subtensor/src/tests/swap_hotkey.rs @@ -719,9 +719,9 @@ fn test_swap_hotkey_with_multiple_coldkeys_and_subnets() { }); } -// SKIP_WASM_BUILD=1 RUST_LOG=debug cargo test --test swap_hotkey -- test_swap_hotkey_tx_rate_limit_exceeded --exact --nocapture +// SKIP_WASM_BUILD=1 RUST_LOG=debug cargo test --test swap_hotkey -- test_swap_hotkey_no_tx_rate_limit --exact --nocapture #[test] -fn test_swap_hotkey_tx_rate_limit_exceeded() { +fn test_swap_hotkey_no_tx_rate_limit() { new_test_ext(1).execute_with(|| { let netuid = NetUid::from(1); let tempo: u16 = 13; @@ -731,15 +731,14 @@ fn test_swap_hotkey_tx_rate_limit_exceeded() { let coldkey = U256::from(3); let swap_cost = SubtensorModule::get_key_swap_cost() * 2.into(); - let tx_rate_limit = 1; - - // Get the current transaction rate limit - let current_tx_rate_limit = SubtensorModule::get_tx_rate_limit(); - log::info!("current_tx_rate_limit: {current_tx_rate_limit:?}"); + let interval: u64 = ::HotkeySwapOnSubnetInterval::get(); - // Set the transaction rate limit + // Set a tx rate limit far larger than the gap we leave between the two swaps below. + // The generic tx rate limit no longer gates hotkey swaps, so the second swap must + // succeed even though the old behaviour would have rejected it. Only the per-subnet + // `HotkeySwapOnSubnetInterval` cooldown still applies. + let tx_rate_limit = interval.saturating_mul(100); SubtensorModule::set_tx_rate_limit(tx_rate_limit); - // assert the rate limit is set to 1000 blocks assert_eq!(SubtensorModule::get_tx_rate_limit(), tx_rate_limit); // Setup initial state @@ -756,20 +755,10 @@ fn test_swap_hotkey_tx_rate_limit_exceeded() { false, )); - // Attempt to perform another swap immediately, which should fail due to rate limit - assert_err!( - SubtensorModule::do_swap_hotkey( - <::RuntimeOrigin>::signed(coldkey), - &new_hotkey_1, - &new_hotkey_2, - None, - false, - ), - Error::::HotKeySetTxRateLimitExceeded - ); - - // move in time past the rate limit - step_block(1001); + // Advance just past the per-subnet swap cooldown, but far less than the tx rate + // limit set above. Under the old rules the generic tx rate limit would reject this + // second swap; with that limit removed it succeeds. + step_block(interval as u16 + 1); assert_ok!(SubtensorModule::do_swap_hotkey( <::RuntimeOrigin>::signed(coldkey), &new_hotkey_1, @@ -1192,6 +1181,7 @@ fn test_do_swap_hotkey_err_new_hotkey_not_clean_for_root() { let other_coldkey = U256::from(4); Owner::::insert(old_hotkey, coldkey); + NetworksAdded::::insert(NetUid::ROOT, true); TotalNetworks::::put(1); SubtensorModule::set_last_tx_block(&coldkey, 0); @@ -1567,12 +1557,9 @@ fn test_swap_hotkey_swap_rate_limits() { let netuid = add_dynamic_network(&old_hotkey, &coldkey); add_balance_to_coldkey_account(&coldkey, 1_000_000_000_000_u64.into()); - let last_tx_block = 123; let delegate_take_block = 4567; let child_key_take_block = 8910; - // Set the last tx block for the old hotkey - SubtensorModule::set_last_tx_block(&old_hotkey, last_tx_block); // Set the last delegate take block for the old hotkey SubtensorModule::set_last_tx_block_delegate_take(&old_hotkey, delegate_take_block); // Set last childkey take block for the old hotkey @@ -1587,11 +1574,8 @@ fn test_swap_hotkey_swap_rate_limits() { false, )); - // Check for new hotkey - assert_eq!( - SubtensorModule::get_last_tx_block(&new_hotkey), - last_tx_block - ); + // Check for new hotkey (LastTxBlock is no longer transferred: the generic tx rate + // limit was removed, so it is dead state). The take rate limits are still active. assert_eq!( SubtensorModule::get_last_tx_block_delegate_take(&new_hotkey), delegate_take_block diff --git a/pallets/subtensor/src/tests/swap_hotkey_with_subnet.rs b/pallets/subtensor/src/tests/swap_hotkey_with_subnet.rs index 1e200aaedd..f299dbe3ae 100644 --- a/pallets/subtensor/src/tests/swap_hotkey_with_subnet.rs +++ b/pallets/subtensor/src/tests/swap_hotkey_with_subnet.rs @@ -774,69 +774,6 @@ fn test_swap_hotkey_with_multiple_coldkeys_and_subnets() { }); } -// SKIP_WASM_BUILD=1 RUST_LOG=debug cargo test --test swap_hotkey_with_subnet -- test_swap_hotkey_tx_rate_limit_exceeded --exact --nocapture -#[test] -fn test_swap_hotkey_tx_rate_limit_exceeded() { - new_test_ext(1).execute_with(|| { - let netuid = NetUid::from(1); - let tempo: u16 = 13; - let old_hotkey = U256::from(1); - let new_hotkey_1 = U256::from(2); - let new_hotkey_2 = U256::from(4); - let coldkey = U256::from(3); - let swap_cost = TaoBalance::from(1_000_000_000u64) * 2.into(); - - let tx_rate_limit = 1; - - // Get the current transaction rate limit - let current_tx_rate_limit = SubtensorModule::get_tx_rate_limit(); - log::info!("current_tx_rate_limit: {current_tx_rate_limit:?}"); - - // Set the transaction rate limit - SubtensorModule::set_tx_rate_limit(tx_rate_limit); - // assert the rate limit is set to 1000 blocks - assert_eq!(SubtensorModule::get_tx_rate_limit(), tx_rate_limit); - - // Setup initial state - add_network(netuid, tempo, 0); - register_ok_neuron(netuid, old_hotkey, coldkey, 0); - add_balance_to_coldkey_account(&coldkey, swap_cost); - - // Perform the first swap - System::set_block_number(System::block_number() + HotkeySwapOnSubnetInterval::get()); - assert_ok!(SubtensorModule::do_swap_hotkey( - RuntimeOrigin::signed(coldkey), - &old_hotkey, - &new_hotkey_1, - Some(netuid), - false - ),); - - // Attempt to perform another swap immediately, which should fail due to rate limit - assert_err!( - SubtensorModule::do_swap_hotkey( - RuntimeOrigin::signed(coldkey), - &old_hotkey, - &new_hotkey_1, - Some(netuid), - false - ), - Error::::HotKeySetTxRateLimitExceeded - ); - - // move in time past the rate limit - step_block(1001); - System::set_block_number(System::block_number() + HotkeySwapOnSubnetInterval::get()); - assert_ok!(SubtensorModule::do_swap_hotkey( - <::RuntimeOrigin>::signed(coldkey), - &new_hotkey_1, - &new_hotkey_2, - None, - false - )); - }); -} - // SKIP_WASM_BUILD=1 RUST_LOG=debug cargo test --test swap_hotkey_with_subnet -- test_do_swap_hotkey_err_not_owner --exact --nocapture #[test] fn test_do_swap_hotkey_err_not_owner() { @@ -1575,15 +1512,12 @@ fn test_swap_hotkey_swap_rate_limits() { let new_hotkey = U256::from(2); let coldkey = U256::from(3); - let last_tx_block = 123; let delegate_take_block = 4567; let child_key_take_block = 8910; let netuid = add_dynamic_network(&old_hotkey, &coldkey); add_balance_to_coldkey_account(&coldkey, 1_000_000_000_000_u64.into()); - // Set the last tx block for the old hotkey - SubtensorModule::set_last_tx_block(&old_hotkey, last_tx_block); // Set the last delegate take block for the old hotkey SubtensorModule::set_last_tx_block_delegate_take(&old_hotkey, delegate_take_block); // Set last childkey take block for the old hotkey @@ -1599,11 +1533,8 @@ fn test_swap_hotkey_swap_rate_limits() { false ),); - // Check for new hotkey - assert_eq!( - SubtensorModule::get_last_tx_block(&new_hotkey), - last_tx_block - ); + // Check for new hotkey (LastTxBlock is no longer transferred: the generic tx rate + // limit was removed. assert_eq!( SubtensorModule::get_last_tx_block_delegate_take(&new_hotkey), delegate_take_block @@ -3011,6 +2942,8 @@ fn test_swap_hotkey_root_claims_changed_if_root() { let staker_coldkey = U256::from(1006); + NetworksAdded::::insert(NetUid::ROOT, true); + // Use neuron_hotkey as subnet creator so it receives root dividends let netuid_1 = add_dynamic_network(&neuron_hotkey, &owner_coldkey); @@ -3184,6 +3117,7 @@ fn test_swap_hotkey_auto_parent_delegation_transferred_on_root() { let new_hotkey = U256::from(1005); let _ = add_dynamic_network(&old_hotkey, &owner_coldkey); + NetworksAdded::::insert(NetUid::ROOT, true); add_balance_to_coldkey_account(&owner_coldkey, 20_000_000_000_000_000_u64.into()); // Opt out of auto parent delegation on the old hotkey. diff --git a/pallets/subtensor/src/tests/uids.rs b/pallets/subtensor/src/tests/uids.rs index 7679f2b727..7e4955d701 100644 --- a/pallets/subtensor/src/tests/uids.rs +++ b/pallets/subtensor/src/tests/uids.rs @@ -62,7 +62,7 @@ fn test_replace_neuron() { NeuronCertificateOf::default(), ); Prometheus::::insert(netuid, hotkey_account_id, PrometheusInfoOf::default()); - AssociatedEvmAddress::::insert(netuid, neuron_uid, (evm_address, 1)); + SubtensorModule::set_associated_evm_address(netuid, neuron_uid, evm_address, 1); // Replace the neuron. SubtensorModule::replace_neuron(netuid, neuron_uid, &new_hotkey_account_id, block_number); @@ -126,6 +126,7 @@ fn test_replace_neuron() { vec![] ); assert_eq!(AssociatedEvmAddress::::get(netuid, neuron_uid), None); + assert!(AssociatedUidsByEvmAddress::::get(netuid, evm_address).is_empty()); }); } @@ -154,7 +155,7 @@ fn test_bonds_cleared_on_replace() { let neuron_uid = SubtensorModule::get_uid_for_net_and_hotkey(netuid, &hotkey_account_id); assert_ok!(neuron_uid); let neuron_uid = neuron_uid.unwrap(); - AssociatedEvmAddress::::insert(netuid, neuron_uid, (evm_address, 1)); + SubtensorModule::set_associated_evm_address(netuid, neuron_uid, evm_address, 1); // Set non-default bonds. Bonds::::insert(NetUidStorageIndex::from(netuid), neuron_uid, vec![(0, 1)]); @@ -187,6 +188,7 @@ fn test_bonds_cleared_on_replace() { vec![] ); assert_eq!(AssociatedEvmAddress::::get(netuid, neuron_uid), None); + assert!(AssociatedUidsByEvmAddress::::get(netuid, evm_address).is_empty()); }); } @@ -314,7 +316,7 @@ fn test_replace_neuron_multiple_subnets() { &hotkey_account_id )); - AssociatedEvmAddress::::insert(netuid, neuron_uid.unwrap(), (evm_address, 1)); + SubtensorModule::set_associated_evm_address(netuid, neuron_uid.unwrap(), evm_address, 1); // Replace the neuron (ONLY on ONE network). SubtensorModule::replace_neuron( @@ -340,6 +342,7 @@ fn test_replace_neuron_multiple_subnets() { AssociatedEvmAddress::::get(netuid, neuron_uid.unwrap()), None ); + assert!(AssociatedUidsByEvmAddress::::get(netuid, evm_address).is_empty()); }); } @@ -375,7 +378,7 @@ fn test_replace_neuron_subnet_owner_not_replaced() { let netuid = add_dynamic_network(&owner_hotkey, &owner_coldkey); let neuron_uid = SubtensorModule::get_uid_for_net_and_hotkey(netuid, &owner_hotkey) .expect("Owner neuron should be registered by add_dynamic_network"); - AssociatedEvmAddress::::insert(netuid, neuron_uid, (evm_address, 1)); + SubtensorModule::set_associated_evm_address(netuid, neuron_uid, evm_address, 1); let current_block = SubtensorModule::get_current_block_as_u64(); SubtensorModule::replace_neuron(netuid, neuron_uid, &new_hotkey_account_id, current_block); @@ -391,6 +394,10 @@ fn test_replace_neuron_subnet_owner_not_replaced() { SubtensorModule::get_uid_for_net_and_hotkey(netuid, &new_hotkey_account_id); assert_err!(new_key_uid, Error::::HotKeyNotRegisteredInSubNet,); assert!(AssociatedEvmAddress::::get(netuid, neuron_uid).is_some()); + assert_eq!( + AssociatedUidsByEvmAddress::::get(netuid, evm_address).into_inner(), + vec![(neuron_uid, 1)] + ); }); } @@ -411,7 +418,7 @@ fn test_replace_neuron_subnet_owner_not_replaced_if_in_sn_owner_hotkey_map() { let owner_uid = SubtensorModule::get_uid_for_net_and_hotkey(netuid, &owner_hotkey) .expect("Owner neuron should already be registered by add_dynamic_network"); - AssociatedEvmAddress::::insert(netuid, owner_uid, (evm_address, 1)); + SubtensorModule::set_associated_evm_address(netuid, owner_uid, evm_address, 1); // Register another hotkey for the owner register_ok_neuron(netuid, other_owner_hotkey, owner_coldkey, 0); @@ -433,10 +440,14 @@ fn test_replace_neuron_subnet_owner_not_replaced_if_in_sn_owner_hotkey_map() { "Owner's first hotkey should remain registered" ); assert!(AssociatedEvmAddress::::get(netuid, owner_uid).is_some()); + assert_eq!( + AssociatedUidsByEvmAddress::::get(netuid, evm_address).into_inner(), + vec![(owner_uid, 1)] + ); let new_key_uid = SubtensorModule::get_uid_for_net_and_hotkey(netuid, &additional_hotkey_1); assert_err!(new_key_uid, Error::::HotKeyNotRegisteredInSubNet,); - AssociatedEvmAddress::::insert(netuid, other_owner_uid, (evm_address, 1)); + SubtensorModule::set_associated_evm_address(netuid, other_owner_uid, evm_address, 1); // Try to replace the other owner hotkey SubtensorModule::replace_neuron( diff --git a/pallets/subtensor/src/utils/cleanup.rs b/pallets/subtensor/src/utils/cleanup.rs new file mode 100644 index 0000000000..f8421010f9 --- /dev/null +++ b/pallets/subtensor/src/utils/cleanup.rs @@ -0,0 +1,46 @@ +use super::*; + +impl Pallet { + pub fn remove_storage_entries_for_netuid( + weight_meter: &mut WeightMeter, + iter: I, + matches_netuid: impl Fn(&I::Item) -> bool, + key_from_item: impl Fn(I::Item) -> K, + ops_based_on_key: impl Fn(&K), + writes_per_match: u64, + ) -> (bool, Option) + where + I: Iterator, + I::Item: Clone, + { + let r = T::DbWeight::get().reads(1); + let w = T::DbWeight::get().writes(writes_per_match); + let mut read_all = true; + + let mut to_rm: sp_std::vec::Vec = sp_std::vec::Vec::new(); + let mut last_item = None; + for item in iter { + if !weight_meter.can_consume(r) { + read_all = false; + break; + } + weight_meter.consume(r); + if matches_netuid(&item) { + if !weight_meter.can_consume(w) { + read_all = false; + break; + } + weight_meter.consume(w); + + to_rm.push(key_from_item(item.clone())); + } + last_item = Some(item); + } + + for hot in to_rm { + ops_based_on_key(&hot); + } + + (read_all, last_item) + } +} diff --git a/pallets/subtensor/src/utils/evm.rs b/pallets/subtensor/src/utils/evm.rs index 1ac5c65b24..7fe16a6557 100644 --- a/pallets/subtensor/src/utils/evm.rs +++ b/pallets/subtensor/src/utils/evm.rs @@ -4,6 +4,7 @@ use frame_support::ensure; use frame_system::ensure_signed; use sp_core::{H160, ecdsa::Signature, hashing::keccak_256}; +use sp_std::collections::btree_map::BTreeMap; use sp_std::vec::Vec; use subtensor_runtime_common::NetUid; @@ -50,6 +51,7 @@ impl Pallet { mut signature: Signature, ) -> dispatch::DispatchResult { let hotkey = ensure_signed(origin)?; + ensure!(Self::if_subnet_exist(netuid), Error::::SubnetNotExists); ensure!( Self::get_owning_coldkey_for_hotkey(&hotkey) != DefaultAccount::::get(), Error::::NonAssociatedColdKey @@ -82,9 +84,11 @@ impl Pallet { Error::::InvalidRecoveredPublicKey ); + Self::ensure_evm_address_index_capacity(netuid, uid, evm_key)?; + let current_block_number = Self::get_current_block_as_u64(); - AssociatedEvmAddress::::insert(netuid, uid, (evm_key, current_block_number)); + Self::set_associated_evm_address(netuid, uid, evm_key, current_block_number); Self::deposit_event(Event::EvmKeyAssociated { netuid, @@ -96,18 +100,132 @@ impl Pallet { Ok(()) } - pub fn uid_lookup(netuid: NetUid, evm_key: H160, limit: u16) -> Vec<(u16, u64)> { - let mut ret_val = AssociatedEvmAddress::::iter_prefix(netuid) - .take(limit as usize) - .filter_map(|(uid, (stored_evm_key, block_associated))| { - if stored_evm_key != evm_key { - return None; - } + /// Ensure `evm_key` can accept an association for `uid` on `netuid` without exceeding + /// [`MAX_ASSOCIATED_UIDS_PER_EVM_ADDRESS`]. + /// + /// Re-associating a UID that is already tracked for this address (e.g. refreshing its + /// block) never consumes a new slot, so it is always permitted. This is the only path + /// that grows a bucket, so enforcing the cap here keeps the forward map + /// (`AssociatedEvmAddress`) and reverse index (`AssociatedUidsByEvmAddress`) consistent. + pub fn ensure_evm_address_index_capacity( + netuid: NetUid, + uid: u16, + evm_key: H160, + ) -> DispatchResult { + let bucket = AssociatedUidsByEvmAddress::::get(netuid, evm_key); + let already_tracked = bucket.iter().any(|(stored_uid, _)| *stored_uid == uid); + ensure!( + already_tracked || (bucket.len() as u32) < crate::MAX_ASSOCIATED_UIDS_PER_EVM_ADDRESS, + Error::::EvmKeyAssociationLimitExceeded + ); + Ok(()) + } + + pub fn set_associated_evm_address( + netuid: NetUid, + uid: u16, + evm_key: H160, + block_associated: u64, + ) { + if let Some((old_evm_key, _)) = AssociatedEvmAddress::::get(netuid, uid) + && old_evm_key != evm_key + { + Self::remove_uid_from_evm_address_index(netuid, old_evm_key, uid); + } + + Self::upsert_uid_in_evm_address_index(netuid, evm_key, uid, block_associated); + AssociatedEvmAddress::::insert(netuid, uid, (evm_key, block_associated)); + } + + pub fn remove_associated_evm_address(netuid: NetUid, uid: u16) { + if let Some((evm_key, _)) = AssociatedEvmAddress::::take(netuid, uid) { + Self::remove_uid_from_evm_address_index(netuid, evm_key, uid); + } + } + + pub fn clear_associated_evm_addresses(netuid: NetUid) { + let _ = AssociatedEvmAddress::::clear_prefix(netuid, u32::MAX, None); + let _ = AssociatedUidsByEvmAddress::::clear_prefix(netuid, u32::MAX, None); + } - Some((uid, block_associated)) + /// Remap the UIDs stored in the EVM-address reverse index after a subnet UID compaction. + /// + /// [`Self::trim_to_max_allowed_uids`] compacts kept UIDs into new, lower positions described + /// by `old_to_new_uid`; trimmed UIDs have already been dropped from both the forward map and + /// this reverse index. Only the UID *positions* of kept associations change (their EVM key + /// and block are untouched), so we rewrite each bucket's UIDs through the mapping rather than + /// clearing the whole index and rebuilding it from the forward map. + /// + /// Work is bounded by the number of distinct EVM addresses associated on the subnet: each + /// reverse-index entry is read once and written at most once, and the forward map is never + /// re-scanned. + pub fn remap_associated_evm_address_index( + netuid: NetUid, + old_to_new_uid: &BTreeMap, + ) { + // Collect first: rewriting entries while iterating the same storage prefix is unsafe. + let updates: Vec<_> = AssociatedUidsByEvmAddress::::iter_prefix(netuid) + .filter_map(|(evm_key, mut bucket)| { + let mut changed = false; + for (uid, _) in bucket.iter_mut() { + if let Some(&new_uid) = old_to_new_uid.get(&(*uid as usize)) + && *uid != new_uid as u16 + { + *uid = new_uid as u16; + changed = true; + } + } + changed.then_some((evm_key, bucket)) }) + .collect(); + + for (evm_key, bucket) in updates { + AssociatedUidsByEvmAddress::::insert(netuid, evm_key, bucket); + } + } + + fn upsert_uid_in_evm_address_index( + netuid: NetUid, + evm_key: H160, + uid: u16, + block_associated: u64, + ) { + AssociatedUidsByEvmAddress::::mutate(netuid, evm_key, |uids| { + if let Some((_, stored_block)) = + uids.iter_mut().find(|(stored_uid, _)| *stored_uid == uid) + { + *stored_block = block_associated; + return; + } + + if uids.try_push((uid, block_associated)).is_err() { + log::error!( + "AssociatedUidsByEvmAddress overflow for netuid {netuid:?}, evm_key {evm_key:?}" + ); + } + }); + } + + fn remove_uid_from_evm_address_index(netuid: NetUid, evm_key: H160, uid: u16) { + AssociatedUidsByEvmAddress::::mutate_exists(netuid, evm_key, |maybe_uids| { + let Some(uids) = maybe_uids else { + return; + }; + + uids.retain(|(stored_uid, _)| *stored_uid != uid); + + if uids.is_empty() { + *maybe_uids = None; + } + }); + } + + pub fn uid_lookup(netuid: NetUid, evm_key: H160, limit: u16) -> Vec<(u16, u64)> { + let mut ret_val = AssociatedUidsByEvmAddress::::get(netuid, evm_key) + .into_iter() .collect::>(); ret_val.sort_by(|(_, block1), (_, block2)| block1.cmp(block2)); + ret_val.truncate(limit as usize); ret_val } diff --git a/pallets/subtensor/src/utils/identity.rs b/pallets/subtensor/src/utils/identity.rs index c2406321ed..511fa929d6 100644 --- a/pallets/subtensor/src/utils/identity.rs +++ b/pallets/subtensor/src/utils/identity.rs @@ -109,6 +109,7 @@ impl Pallet { ) -> dispatch::DispatchResult { // Ensure the call is signed and get the signer's (coldkey) account let coldkey = ensure_signed(origin)?; + ensure!(Self::if_subnet_exist(netuid), Error::::SubnetNotExists); // Ensure that the coldkey owns the subnet ensure!( diff --git a/pallets/subtensor/src/utils/mod.rs b/pallets/subtensor/src/utils/mod.rs index a91875da59..bb7127f007 100644 --- a/pallets/subtensor/src/utils/mod.rs +++ b/pallets/subtensor/src/utils/mod.rs @@ -1,4 +1,5 @@ use super::*; +pub mod cleanup; pub mod evm; pub mod identity; pub mod misc; diff --git a/pallets/subtensor/src/utils/voting_power.rs b/pallets/subtensor/src/utils/voting_power.rs index 0cb191436e..11d8880b97 100644 --- a/pallets/subtensor/src/utils/voting_power.rs +++ b/pallets/subtensor/src/utils/voting_power.rs @@ -43,6 +43,7 @@ impl Pallet { /// Enable voting power tracking for a subnet. pub fn do_enable_voting_power_tracking(netuid: NetUid) -> DispatchResult { + ensure!(Self::if_subnet_exist(netuid), Error::::SubnetNotExists); // Enable tracking VotingPowerTrackingEnabled::::insert(netuid, true); @@ -60,6 +61,7 @@ impl Pallet { /// Schedule disabling of voting power tracking for a subnet. /// Tracking will continue for 14 days, then automatically disable. pub fn do_disable_voting_power_tracking(netuid: NetUid) -> DispatchResult { + ensure!(Self::if_subnet_exist(netuid), Error::::SubnetNotExists); // Check if tracking is enabled ensure!( Self::get_voting_power_tracking_enabled(netuid), @@ -89,6 +91,7 @@ impl Pallet { /// Set the EMA alpha value for voting power calculation on a subnet. pub fn do_set_voting_power_ema_alpha(netuid: NetUid, alpha: u64) -> DispatchResult { + ensure!(Self::if_subnet_exist(netuid), Error::::SubnetNotExists); // Validate alpha (must be <= 1.0, represented as 10^18) ensure!( alpha <= MAX_VOTING_POWER_EMA_ALPHA, diff --git a/pallets/subtensor/src/weights.rs b/pallets/subtensor/src/weights.rs index 478c37ad7c..244a11c46c 100644 --- a/pallets/subtensor/src/weights.rs +++ b/pallets/subtensor/src/weights.rs @@ -2,9 +2,9 @@ //! Autogenerated weights for `pallet_subtensor` //! //! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 49.1.0 -//! DATE: 2026-06-23, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! DATE: 2026-07-06, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` //! WORST CASE MAP SIZE: `1000000` -//! HOSTNAME: `runnervm7b5n9`, CPU: `AMD EPYC 7763 64-Core Processor` +//! HOSTNAME: `runnervmkkn4f`, CPU: `AMD EPYC 9V74 80-Core Processor` //! WASM-EXECUTION: `Compiled`, CHAIN: `None`, DB CACHE: `1024` // Executed Command: @@ -22,7 +22,7 @@ // --no-storage-info // --no-min-squares // --no-median-slopes -// --output=/tmp/tmp.hYIH73ZUrd +// --output=/tmp/tmp.fa7cwyHfwi // --template=/home/runner/work/subtensor/subtensor/.maintain/frame-weight-template.hbs #![cfg_attr(rustfmt, rustfmt_skip)] @@ -89,6 +89,7 @@ pub trait WeightInfo { fn sudo_set_root_claim_threshold() -> Weight; fn set_auto_parent_delegation_enabled() -> Weight; fn add_stake_burn() -> Weight; + fn dissolve_network() -> Weight; fn set_pending_childkey_cooldown() -> Weight; fn lock_stake() -> Weight; fn move_lock() -> Weight; @@ -131,12 +132,12 @@ impl WeightInfo for SubstrateWeight { /// Proof: `SubtensorModule::SubnetMechanism` (`max_values`: None, `max_size`: None, mode: `Measured`) /// Storage: `SubtensorModule::SubnetAlphaIn` (r:1 w:1) /// Proof: `SubtensorModule::SubnetAlphaIn` (`max_values`: None, `max_size`: None, mode: `Measured`) - /// Storage: `Swap::PalSwapInitialized` (r:1 w:1) - /// Proof: `Swap::PalSwapInitialized` (`max_values`: None, `max_size`: Some(11), added: 2486, mode: `MaxEncodedLen`) - /// Storage: `SubtensorModule::SubnetTAO` (r:1 w:1) - /// Proof: `SubtensorModule::SubnetTAO` (`max_values`: None, `max_size`: None, mode: `Measured`) /// Storage: `Swap::FeeRate` (r:1 w:0) /// Proof: `Swap::FeeRate` (`max_values`: None, `max_size`: Some(12), added: 2487, mode: `MaxEncodedLen`) + /// Storage: `SubtensorModule::SubnetTAO` (r:1 w:1) + /// Proof: `SubtensorModule::SubnetTAO` (`max_values`: None, `max_size`: None, mode: `Measured`) + /// Storage: `Swap::PalSwapInitialized` (r:1 w:1) + /// Proof: `Swap::PalSwapInitialized` (`max_values`: None, `max_size`: Some(11), added: 2486, mode: `MaxEncodedLen`) /// Storage: `SubtensorModule::SubnetAlphaOut` (r:1 w:1) /// Proof: `SubtensorModule::SubnetAlphaOut` (`max_values`: None, `max_size`: None, mode: `Measured`) /// Storage: `SubtensorModule::TotalStake` (r:1 w:1) @@ -185,8 +186,8 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `1865` // Estimated: `6148` - // Minimum execution time: 343_840_000 picoseconds. - Weight::from_parts(349_872_000, 6148) + // Minimum execution time: 342_717_000 picoseconds. + Weight::from_parts(345_692_000, 6148) .saturating_add(T::DbWeight::get().reads(34_u64)) .saturating_add(T::DbWeight::get().writes(29_u64)) } @@ -228,27 +229,27 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `188820` // Estimated: `10327410` - // Minimum execution time: 15_317_357_000 picoseconds. - Weight::from_parts(15_513_610_000, 10327410) + // Minimum execution time: 16_435_147_000 picoseconds. + Weight::from_parts(16_846_168_000, 10327410) .saturating_add(T::DbWeight::get().reads(4112_u64)) .saturating_add(T::DbWeight::get().writes(2_u64)) } + /// Storage: `SubtensorModule::SubnetMechanism` (r:1 w:0) + /// Proof: `SubtensorModule::SubnetMechanism` (`max_values`: None, `max_size`: None, mode: `Measured`) + /// Storage: `SubtensorModule::SubnetTAO` (r:1 w:1) + /// Proof: `SubtensorModule::SubnetTAO` (`max_values`: None, `max_size`: None, mode: `Measured`) /// Storage: `SubtensorModule::NetworksAdded` (r:1 w:0) /// Proof: `SubtensorModule::NetworksAdded` (`max_values`: None, `max_size`: None, mode: `Measured`) /// Storage: `SubtensorModule::SubtokenEnabled` (r:1 w:0) /// Proof: `SubtensorModule::SubtokenEnabled` (`max_values`: None, `max_size`: None, mode: `Measured`) - /// Storage: `SubtensorModule::SubnetMechanism` (r:1 w:0) - /// Proof: `SubtensorModule::SubnetMechanism` (`max_values`: None, `max_size`: None, mode: `Measured`) /// Storage: `SubtensorModule::SubnetAlphaIn` (r:1 w:1) /// Proof: `SubtensorModule::SubnetAlphaIn` (`max_values`: None, `max_size`: None, mode: `Measured`) + /// Storage: `Swap::FeeRate` (r:1 w:0) + /// Proof: `Swap::FeeRate` (`max_values`: None, `max_size`: Some(12), added: 2487, mode: `MaxEncodedLen`) /// Storage: `Swap::PalSwapInitialized` (r:1 w:0) /// Proof: `Swap::PalSwapInitialized` (`max_values`: None, `max_size`: Some(11), added: 2486, mode: `MaxEncodedLen`) - /// Storage: `SubtensorModule::SubnetTAO` (r:1 w:1) - /// Proof: `SubtensorModule::SubnetTAO` (`max_values`: None, `max_size`: None, mode: `Measured`) /// Storage: `Swap::SwapBalancer` (r:1 w:0) /// Proof: `Swap::SwapBalancer` (`max_values`: None, `max_size`: Some(18), added: 2493, mode: `MaxEncodedLen`) - /// Storage: `Swap::FeeRate` (r:1 w:0) - /// Proof: `Swap::FeeRate` (`max_values`: None, `max_size`: Some(12), added: 2487, mode: `MaxEncodedLen`) /// Storage: `System::Account` (r:3 w:3) /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(104), added: 2579, mode: `MaxEncodedLen`) /// Storage: `SubtensorModule::Owner` (r:1 w:0) @@ -299,11 +300,13 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `2226` // Estimated: `8727` - // Minimum execution time: 639_698_000 picoseconds. - Weight::from_parts(664_064_000, 8727) + // Minimum execution time: 677_613_000 picoseconds. + Weight::from_parts(693_357_000, 8727) .saturating_add(T::DbWeight::get().reads(32_u64)) .saturating_add(T::DbWeight::get().writes(16_u64)) } + /// Storage: `SubtensorModule::NetworksAdded` (r:1 w:0) + /// Proof: `SubtensorModule::NetworksAdded` (`max_values`: None, `max_size`: None, mode: `Measured`) /// Storage: `SubtensorModule::Uids` (r:1 w:0) /// Proof: `SubtensorModule::Uids` (`max_values`: None, `max_size`: None, mode: `Measured`) /// Storage: `SubtensorModule::Axons` (r:1 w:1) @@ -312,13 +315,15 @@ impl WeightInfo for SubstrateWeight { /// Proof: `SubtensorModule::ServingRateLimit` (`max_values`: None, `max_size`: None, mode: `Measured`) fn serve_axon() -> Weight { // Proof Size summary in bytes: - // Measured: `713` - // Estimated: `4178` - // Minimum execution time: 30_307_000 picoseconds. - Weight::from_parts(31_278_000, 4178) - .saturating_add(T::DbWeight::get().reads(3_u64)) + // Measured: `828` + // Estimated: `4293` + // Minimum execution time: 34_912_000 picoseconds. + Weight::from_parts(36_334_000, 4293) + .saturating_add(T::DbWeight::get().reads(4_u64)) .saturating_add(T::DbWeight::get().writes(1_u64)) } + /// Storage: `SubtensorModule::NetworksAdded` (r:1 w:0) + /// Proof: `SubtensorModule::NetworksAdded` (`max_values`: None, `max_size`: None, mode: `Measured`) /// Storage: `SubtensorModule::Uids` (r:1 w:0) /// Proof: `SubtensorModule::Uids` (`max_values`: None, `max_size`: None, mode: `Measured`) /// Storage: `SubtensorModule::Prometheus` (r:1 w:1) @@ -327,11 +332,11 @@ impl WeightInfo for SubstrateWeight { /// Proof: `SubtensorModule::ServingRateLimit` (`max_values`: None, `max_size`: None, mode: `Measured`) fn serve_prometheus() -> Weight { // Proof Size summary in bytes: - // Measured: `812` - // Estimated: `4277` - // Minimum execution time: 28_262_000 picoseconds. - Weight::from_parts(29_294_000, 4277) - .saturating_add(T::DbWeight::get().reads(3_u64)) + // Measured: `927` + // Estimated: `4392` + // Minimum execution time: 33_329_000 picoseconds. + Weight::from_parts(34_171_000, 4392) + .saturating_add(T::DbWeight::get().reads(4_u64)) .saturating_add(T::DbWeight::get().writes(1_u64)) } /// Storage: `SubtensorModule::NetworksAdded` (r:1 w:0) @@ -358,12 +363,12 @@ impl WeightInfo for SubstrateWeight { /// Proof: `SubtensorModule::SubnetMechanism` (`max_values`: None, `max_size`: None, mode: `Measured`) /// Storage: `SubtensorModule::SubnetAlphaIn` (r:1 w:1) /// Proof: `SubtensorModule::SubnetAlphaIn` (`max_values`: None, `max_size`: None, mode: `Measured`) - /// Storage: `Swap::PalSwapInitialized` (r:1 w:1) - /// Proof: `Swap::PalSwapInitialized` (`max_values`: None, `max_size`: Some(11), added: 2486, mode: `MaxEncodedLen`) - /// Storage: `SubtensorModule::SubnetTAO` (r:1 w:1) - /// Proof: `SubtensorModule::SubnetTAO` (`max_values`: None, `max_size`: None, mode: `Measured`) /// Storage: `Swap::FeeRate` (r:1 w:0) /// Proof: `Swap::FeeRate` (`max_values`: None, `max_size`: Some(12), added: 2487, mode: `MaxEncodedLen`) + /// Storage: `SubtensorModule::SubnetTAO` (r:1 w:1) + /// Proof: `SubtensorModule::SubnetTAO` (`max_values`: None, `max_size`: None, mode: `Measured`) + /// Storage: `Swap::PalSwapInitialized` (r:1 w:1) + /// Proof: `Swap::PalSwapInitialized` (`max_values`: None, `max_size`: Some(11), added: 2486, mode: `MaxEncodedLen`) /// Storage: `SubtensorModule::SubnetAlphaOut` (r:1 w:1) /// Proof: `SubtensorModule::SubnetAlphaOut` (`max_values`: None, `max_size`: None, mode: `Measured`) /// Storage: `SubtensorModule::TotalStake` (r:1 w:1) @@ -412,8 +417,8 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `1798` // Estimated: `6148` - // Minimum execution time: 332_358_000 picoseconds. - Weight::from_parts(336_815_000, 6148) + // Minimum execution time: 341_535_000 picoseconds. + Weight::from_parts(346_603_000, 6148) .saturating_add(T::DbWeight::get().reads(34_u64)) .saturating_add(T::DbWeight::get().writes(29_u64)) } @@ -465,8 +470,8 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `1516` // Estimated: `4981` - // Minimum execution time: 102_089_000 picoseconds. - Weight::from_parts(103_934_000, 4981) + // Minimum execution time: 100_198_000 picoseconds. + Weight::from_parts(103_543_000, 4981) .saturating_add(T::DbWeight::get().reads(19_u64)) .saturating_add(T::DbWeight::get().writes(16_u64)) } @@ -482,6 +487,10 @@ impl WeightInfo for SubstrateWeight { /// Proof: `SubtensorModule::SubnetLimit` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) /// Storage: `SubtensorModule::NetworksAdded` (r:3 w:1) /// Proof: `SubtensorModule::NetworksAdded` (`max_values`: None, `max_size`: None, mode: `Measured`) + /// Storage: `SubtensorModule::DissolveCleanupQueue` (r:1 w:0) + /// Proof: `SubtensorModule::DissolveCleanupQueue` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + /// Storage: `SubtensorModule::NetworkRegistrationQueue` (r:1 w:0) + /// Proof: `SubtensorModule::NetworkRegistrationQueue` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) /// Storage: `SubtensorModule::NetworkLastLockCost` (r:1 w:1) /// Proof: `SubtensorModule::NetworkLastLockCost` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) /// Storage: `SubtensorModule::NetworkMinLockCost` (r:1 w:0) @@ -490,8 +499,6 @@ impl WeightInfo for SubstrateWeight { /// Proof: `SubtensorModule::NetworkLockReductionInterval` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) /// Storage: `SubtensorModule::TotalIssuance` (r:1 w:0) /// Proof: `SubtensorModule::TotalIssuance` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) - /// Storage: `SubtensorModule::BlockEmission` (r:1 w:0) - /// Proof: `SubtensorModule::BlockEmission` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) /// Storage: `System::Account` (r:2 w:2) /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(104), added: 2579, mode: `MaxEncodedLen`) /// Storage: `SubtensorModule::SubnetMechanism` (r:1 w:1) @@ -586,9 +593,9 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `1532` // Estimated: `9947` - // Minimum execution time: 271_405_000 picoseconds. - Weight::from_parts(277_125_000, 9947) - .saturating_add(T::DbWeight::get().reads(40_u64)) + // Minimum execution time: 277_000_000 picoseconds. + Weight::from_parts(284_001_000, 9947) + .saturating_add(T::DbWeight::get().reads(41_u64)) .saturating_add(T::DbWeight::get().writes(48_u64)) } /// Storage: `SubtensorModule::NetworksAdded` (r:1 w:0) @@ -621,11 +628,13 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `1249` // Estimated: `4714` - // Minimum execution time: 67_886_000 picoseconds. - Weight::from_parts(69_049_000, 4714) + // Minimum execution time: 66_649_000 picoseconds. + Weight::from_parts(67_991_000, 4714) .saturating_add(T::DbWeight::get().reads(13_u64)) .saturating_add(T::DbWeight::get().writes(2_u64)) } + /// Storage: `SubtensorModule::NetworksAdded` (r:1 w:0) + /// Proof: `SubtensorModule::NetworksAdded` (`max_values`: None, `max_size`: None, mode: `Measured`) /// Storage: `SubtensorModule::CommitRevealWeightsEnabled` (r:1 w:0) /// Proof: `SubtensorModule::CommitRevealWeightsEnabled` (`max_values`: None, `max_size`: None, mode: `Measured`) /// Storage: `SubtensorModule::WeightCommits` (r:1 w:1) @@ -636,8 +645,6 @@ impl WeightInfo for SubstrateWeight { /// Proof: `SubtensorModule::Tempo` (`max_values`: None, `max_size`: None, mode: `Measured`) /// Storage: `SubtensorModule::RevealPeriodEpochs` (r:1 w:0) /// Proof: `SubtensorModule::RevealPeriodEpochs` (`max_values`: None, `max_size`: None, mode: `Measured`) - /// Storage: `SubtensorModule::NetworksAdded` (r:1 w:0) - /// Proof: `SubtensorModule::NetworksAdded` (`max_values`: None, `max_size`: None, mode: `Measured`) /// Storage: `SubtensorModule::MechanismCountCurrent` (r:1 w:0) /// Proof: `SubtensorModule::MechanismCountCurrent` (`max_values`: None, `max_size`: None, mode: `Measured`) /// Storage: `SubtensorModule::SubnetworkN` (r:1 w:0) @@ -668,8 +675,8 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `1650` // Estimated: `7590` - // Minimum execution time: 109_534_000 picoseconds. - Weight::from_parts(111_227_000, 7590) + // Minimum execution time: 109_712_000 picoseconds. + Weight::from_parts(111_755_000, 7590) .saturating_add(T::DbWeight::get().reads(19_u64)) .saturating_add(T::DbWeight::get().writes(2_u64)) } @@ -679,12 +686,14 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 5_570_000 picoseconds. - Weight::from_parts(5_760_000, 0) + // Minimum execution time: 4_237_000 picoseconds. + Weight::from_parts(4_537_000, 0) .saturating_add(T::DbWeight::get().writes(1_u64)) } /// Storage: `SubtensorModule::Owner` (r:1 w:0) /// Proof: `SubtensorModule::Owner` (`max_values`: None, `max_size`: None, mode: `Measured`) + /// Storage: `SubtensorModule::NetworksAdded` (r:1 w:0) + /// Proof: `SubtensorModule::NetworksAdded` (`max_values`: None, `max_size`: None, mode: `Measured`) /// Storage: `SubtensorModule::MinChildkeyTake` (r:1 w:0) /// Proof: `SubtensorModule::MinChildkeyTake` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) /// Storage: `SubtensorModule::MinChildkeyTakePerSubnet` (r:1 w:0) @@ -701,9 +710,9 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `1033` // Estimated: `4498` - // Minimum execution time: 52_527_000 picoseconds. - Weight::from_parts(53_870_000, 4498) - .saturating_add(T::DbWeight::get().reads(7_u64)) + // Minimum execution time: 52_698_000 picoseconds. + Weight::from_parts(53_880_000, 4498) + .saturating_add(T::DbWeight::get().reads(8_u64)) .saturating_add(T::DbWeight::get().writes(2_u64)) } /// Storage: `SubtensorModule::ColdkeySwapAnnouncements` (r:1 w:1) @@ -718,8 +727,8 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `694` // Estimated: `4159` - // Minimum execution time: 45_574_000 picoseconds. - Weight::from_parts(47_248_000, 4159) + // Minimum execution time: 43_054_000 picoseconds. + Weight::from_parts(43_986_000, 4159) .saturating_add(T::DbWeight::get().reads(4_u64)) .saturating_add(T::DbWeight::get().writes(3_u64)) } @@ -731,6 +740,8 @@ impl WeightInfo for SubstrateWeight { /// Proof: `SubtensorModule::Owner` (`max_values`: None, `max_size`: None, mode: `Measured`) /// Storage: `SubtensorModule::IdentitiesV2` (r:2 w:0) /// Proof: `SubtensorModule::IdentitiesV2` (`max_values`: None, `max_size`: None, mode: `Measured`) + /// Storage: `SubtensorModule::AccountFlags` (r:2 w:2) + /// Proof: `SubtensorModule::AccountFlags` (`max_values`: None, `max_size`: None, mode: `Measured`) /// Storage: `SubtensorModule::NetworksAdded` (r:3 w:0) /// Proof: `SubtensorModule::NetworksAdded` (`max_values`: None, `max_size`: None, mode: `Measured`) /// Storage: `SubtensorModule::SubnetOwner` (r:2 w:0) @@ -761,16 +772,14 @@ impl WeightInfo for SubstrateWeight { /// Proof: `SubtensorModule::DecayingLock` (`max_values`: None, `max_size`: None, mode: `Measured`) /// Storage: `System::Account` (r:2 w:2) /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(104), added: 2579, mode: `MaxEncodedLen`) - /// Storage: `SubtensorModule::LastRateLimitedBlock` (r:0 w:1) - /// Proof: `SubtensorModule::LastRateLimitedBlock` (`max_values`: None, `max_size`: None, mode: `Measured`) fn swap_coldkey_announced() -> Weight { // Proof Size summary in bytes: // Measured: `2110` // Estimated: `13000` - // Minimum execution time: 284_379_000 picoseconds. - Weight::from_parts(288_156_000, 13000) - .saturating_add(T::DbWeight::get().reads(37_u64)) - .saturating_add(T::DbWeight::get().writes(15_u64)) + // Minimum execution time: 297_881_000 picoseconds. + Weight::from_parts(302_117_000, 13000) + .saturating_add(T::DbWeight::get().reads(39_u64)) + .saturating_add(T::DbWeight::get().writes(16_u64)) } /// Storage: `System::Account` (r:2 w:2) /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(104), added: 2579, mode: `MaxEncodedLen`) @@ -782,6 +791,8 @@ impl WeightInfo for SubstrateWeight { /// Proof: `SubtensorModule::Owner` (`max_values`: None, `max_size`: None, mode: `Measured`) /// Storage: `SubtensorModule::IdentitiesV2` (r:2 w:2) /// Proof: `SubtensorModule::IdentitiesV2` (`max_values`: None, `max_size`: None, mode: `Measured`) + /// Storage: `SubtensorModule::AccountFlags` (r:2 w:2) + /// Proof: `SubtensorModule::AccountFlags` (`max_values`: None, `max_size`: None, mode: `Measured`) /// Storage: `SubtensorModule::NetworksAdded` (r:3 w:0) /// Proof: `SubtensorModule::NetworksAdded` (`max_values`: None, `max_size`: None, mode: `Measured`) /// Storage: `SubtensorModule::SubnetOwner` (r:2 w:0) @@ -814,16 +825,14 @@ impl WeightInfo for SubstrateWeight { /// Proof: `SubtensorModule::ColdkeySwapAnnouncements` (`max_values`: None, `max_size`: None, mode: `Measured`) /// Storage: `SubtensorModule::ColdkeySwapDisputes` (r:0 w:1) /// Proof: `SubtensorModule::ColdkeySwapDisputes` (`max_values`: None, `max_size`: None, mode: `Measured`) - /// Storage: `SubtensorModule::LastRateLimitedBlock` (r:0 w:1) - /// Proof: `SubtensorModule::LastRateLimitedBlock` (`max_values`: None, `max_size`: None, mode: `Measured`) fn swap_coldkey() -> Weight { // Proof Size summary in bytes: // Measured: `2166` // Estimated: `13056` - // Minimum execution time: 308_323_000 picoseconds. - Weight::from_parts(312_301_000, 13056) - .saturating_add(T::DbWeight::get().reads(37_u64)) - .saturating_add(T::DbWeight::get().writes(19_u64)) + // Minimum execution time: 319_452_000 picoseconds. + Weight::from_parts(322_968_000, 13056) + .saturating_add(T::DbWeight::get().reads(39_u64)) + .saturating_add(T::DbWeight::get().writes(20_u64)) } /// Storage: `SubtensorModule::ColdkeySwapAnnouncements` (r:1 w:0) /// Proof: `SubtensorModule::ColdkeySwapAnnouncements` (`max_values`: None, `max_size`: None, mode: `Measured`) @@ -833,8 +842,8 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `665` // Estimated: `4130` - // Minimum execution time: 22_442_000 picoseconds. - Weight::from_parts(23_293_000, 4130) + // Minimum execution time: 20_440_000 picoseconds. + Weight::from_parts(21_041_000, 4130) .saturating_add(T::DbWeight::get().reads(2_u64)) .saturating_add(T::DbWeight::get().writes(1_u64)) } @@ -846,8 +855,8 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `613` // Estimated: `4078` - // Minimum execution time: 18_695_000 picoseconds. - Weight::from_parts(19_496_000, 4078) + // Minimum execution time: 16_515_000 picoseconds. + Weight::from_parts(17_406_000, 4078) .saturating_add(T::DbWeight::get().reads(2_u64)) .saturating_add(T::DbWeight::get().writes(1_u64)) } @@ -859,10 +868,12 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 8_335_000 picoseconds. - Weight::from_parts(8_857_000, 0) + // Minimum execution time: 6_790_000 picoseconds. + Weight::from_parts(7_260_000, 0) .saturating_add(T::DbWeight::get().writes(2_u64)) } + /// Storage: `SubtensorModule::NetworksAdded` (r:1 w:0) + /// Proof: `SubtensorModule::NetworksAdded` (`max_values`: None, `max_size`: None, mode: `Measured`) /// Storage: `SubtensorModule::CommitRevealWeightsEnabled` (r:1 w:0) /// Proof: `SubtensorModule::CommitRevealWeightsEnabled` (`max_values`: None, `max_size`: None, mode: `Measured`) /// Storage: `SubtensorModule::WeightCommits` (r:1 w:1) @@ -873,8 +884,6 @@ impl WeightInfo for SubstrateWeight { /// Proof: `SubtensorModule::Tempo` (`max_values`: None, `max_size`: None, mode: `Measured`) /// Storage: `SubtensorModule::RevealPeriodEpochs` (r:1 w:0) /// Proof: `SubtensorModule::RevealPeriodEpochs` (`max_values`: None, `max_size`: None, mode: `Measured`) - /// Storage: `SubtensorModule::NetworksAdded` (r:1 w:0) - /// Proof: `SubtensorModule::NetworksAdded` (`max_values`: None, `max_size`: None, mode: `Measured`) /// Storage: `SubtensorModule::MechanismCountCurrent` (r:1 w:0) /// Proof: `SubtensorModule::MechanismCountCurrent` (`max_values`: None, `max_size`: None, mode: `Measured`) /// Storage: `SubtensorModule::SubnetworkN` (r:1 w:0) @@ -905,8 +914,8 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `2155` // Estimated: `8095` - // Minimum execution time: 412_226_000 picoseconds. - Weight::from_parts(421_363_000, 8095) + // Minimum execution time: 418_649_000 picoseconds. + Weight::from_parts(432_099_000, 8095) .saturating_add(T::DbWeight::get().reads(19_u64)) .saturating_add(T::DbWeight::get().writes(2_u64)) } @@ -940,8 +949,8 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `1754` // Estimated: `5219` - // Minimum execution time: 169_976_000 picoseconds. - Weight::from_parts(172_139_000, 5219) + // Minimum execution time: 172_415_000 picoseconds. + Weight::from_parts(174_608_000, 5219) .saturating_add(T::DbWeight::get().reads(13_u64)) .saturating_add(T::DbWeight::get().writes(6_u64)) } @@ -973,8 +982,8 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `1754` // Estimated: `5219` - // Minimum execution time: 166_359_000 picoseconds. - Weight::from_parts(168_964_000, 5219) + // Minimum execution time: 167_768_000 picoseconds. + Weight::from_parts(170_042_000, 5219) .saturating_add(T::DbWeight::get().reads(12_u64)) .saturating_add(T::DbWeight::get().writes(4_u64)) } @@ -994,23 +1003,23 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `1118` // Estimated: `4583` - // Minimum execution time: 38_482_000 picoseconds. - Weight::from_parts(39_454_000, 4583) + // Minimum execution time: 36_744_000 picoseconds. + Weight::from_parts(38_227_000, 4583) .saturating_add(T::DbWeight::get().reads(5_u64)) .saturating_add(T::DbWeight::get().writes(2_u64)) } /// Storage: `SubtensorModule::SubnetMechanism` (r:1 w:0) /// Proof: `SubtensorModule::SubnetMechanism` (`max_values`: None, `max_size`: None, mode: `Measured`) + /// Storage: `SubtensorModule::SubnetTAO` (r:1 w:1) + /// Proof: `SubtensorModule::SubnetTAO` (`max_values`: None, `max_size`: None, mode: `Measured`) /// Storage: `SubtensorModule::SubnetAlphaIn` (r:1 w:1) /// Proof: `SubtensorModule::SubnetAlphaIn` (`max_values`: None, `max_size`: None, mode: `Measured`) + /// Storage: `Swap::FeeRate` (r:1 w:0) + /// Proof: `Swap::FeeRate` (`max_values`: None, `max_size`: Some(12), added: 2487, mode: `MaxEncodedLen`) /// Storage: `Swap::PalSwapInitialized` (r:1 w:0) /// Proof: `Swap::PalSwapInitialized` (`max_values`: None, `max_size`: Some(11), added: 2486, mode: `MaxEncodedLen`) - /// Storage: `SubtensorModule::SubnetTAO` (r:1 w:1) - /// Proof: `SubtensorModule::SubnetTAO` (`max_values`: None, `max_size`: None, mode: `Measured`) /// Storage: `Swap::SwapBalancer` (r:1 w:0) /// Proof: `Swap::SwapBalancer` (`max_values`: None, `max_size`: Some(18), added: 2493, mode: `MaxEncodedLen`) - /// Storage: `Swap::FeeRate` (r:1 w:0) - /// Proof: `Swap::FeeRate` (`max_values`: None, `max_size`: Some(12), added: 2487, mode: `MaxEncodedLen`) /// Storage: `SubtensorModule::NetworksAdded` (r:1 w:0) /// Proof: `SubtensorModule::NetworksAdded` (`max_values`: None, `max_size`: None, mode: `Measured`) /// Storage: `SubtensorModule::SubtokenEnabled` (r:1 w:0) @@ -1065,8 +1074,8 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `2226` // Estimated: `8727` - // Minimum execution time: 829_031_000 picoseconds. - Weight::from_parts(853_005_000, 8727) + // Minimum execution time: 876_848_000 picoseconds. + Weight::from_parts(895_756_000, 8727) .saturating_add(T::DbWeight::get().reads(32_u64)) .saturating_add(T::DbWeight::get().writes(16_u64)) } @@ -1102,8 +1111,8 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `1979` // Estimated: `7919` - // Minimum execution time: 214_819_000 picoseconds. - Weight::from_parts(216_061_000, 7919) + // Minimum execution time: 217_903_000 picoseconds. + Weight::from_parts(222_279_000, 7919) .saturating_add(T::DbWeight::get().reads(19_u64)) .saturating_add(T::DbWeight::get().writes(7_u64)) } @@ -1119,20 +1128,20 @@ impl WeightInfo for SubstrateWeight { /// Proof: `SubtensorModule::TotalHotkeyShares` (`max_values`: None, `max_size`: None, mode: `Measured`) /// Storage: `SubtensorModule::TotalHotkeySharesV2` (r:1 w:1) /// Proof: `SubtensorModule::TotalHotkeySharesV2` (`max_values`: None, `max_size`: None, mode: `Measured`) - /// Storage: `SubtensorModule::NetworksAdded` (r:3 w:0) - /// Proof: `SubtensorModule::NetworksAdded` (`max_values`: None, `max_size`: None, mode: `Measured`) /// Storage: `SubtensorModule::SubnetMechanism` (r:2 w:0) /// Proof: `SubtensorModule::SubnetMechanism` (`max_values`: None, `max_size`: None, mode: `Measured`) + /// Storage: `SubtensorModule::SubnetAlphaIn` (r:1 w:1) + /// Proof: `SubtensorModule::SubnetAlphaIn` (`max_values`: None, `max_size`: None, mode: `Measured`) + /// Storage: `SubtensorModule::NetworksAdded` (r:3 w:0) + /// Proof: `SubtensorModule::NetworksAdded` (`max_values`: None, `max_size`: None, mode: `Measured`) /// Storage: `SubtensorModule::SubnetTAO` (r:1 w:1) /// Proof: `SubtensorModule::SubnetTAO` (`max_values`: None, `max_size`: None, mode: `Measured`) + /// Storage: `Swap::FeeRate` (r:1 w:0) + /// Proof: `Swap::FeeRate` (`max_values`: None, `max_size`: Some(12), added: 2487, mode: `MaxEncodedLen`) /// Storage: `Swap::PalSwapInitialized` (r:1 w:0) /// Proof: `Swap::PalSwapInitialized` (`max_values`: None, `max_size`: Some(11), added: 2486, mode: `MaxEncodedLen`) - /// Storage: `SubtensorModule::SubnetAlphaIn` (r:1 w:1) - /// Proof: `SubtensorModule::SubnetAlphaIn` (`max_values`: None, `max_size`: None, mode: `Measured`) /// Storage: `Swap::SwapBalancer` (r:1 w:0) /// Proof: `Swap::SwapBalancer` (`max_values`: None, `max_size`: Some(18), added: 2493, mode: `MaxEncodedLen`) - /// Storage: `Swap::FeeRate` (r:1 w:0) - /// Proof: `Swap::FeeRate` (`max_values`: None, `max_size`: Some(12), added: 2487, mode: `MaxEncodedLen`) /// Storage: `SubtensorModule::Owner` (r:1 w:0) /// Proof: `SubtensorModule::Owner` (`max_values`: None, `max_size`: None, mode: `Measured`) /// Storage: `SubtensorModule::StakingHotkeys` (r:1 w:0) @@ -1159,23 +1168,23 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `2142` // Estimated: `10557` - // Minimum execution time: 544_281_000 picoseconds. - Weight::from_parts(569_198_000, 10557) + // Minimum execution time: 565_106_000 picoseconds. + Weight::from_parts(581_160_000, 10557) .saturating_add(T::DbWeight::get().reads(28_u64)) .saturating_add(T::DbWeight::get().writes(13_u64)) } /// Storage: `SubtensorModule::SubnetMechanism` (r:2 w:0) /// Proof: `SubtensorModule::SubnetMechanism` (`max_values`: None, `max_size`: None, mode: `Measured`) + /// Storage: `SubtensorModule::SubnetAlphaIn` (r:1 w:1) + /// Proof: `SubtensorModule::SubnetAlphaIn` (`max_values`: None, `max_size`: None, mode: `Measured`) /// Storage: `SubtensorModule::SubnetTAO` (r:1 w:1) /// Proof: `SubtensorModule::SubnetTAO` (`max_values`: None, `max_size`: None, mode: `Measured`) + /// Storage: `Swap::FeeRate` (r:1 w:0) + /// Proof: `Swap::FeeRate` (`max_values`: None, `max_size`: Some(12), added: 2487, mode: `MaxEncodedLen`) /// Storage: `Swap::PalSwapInitialized` (r:1 w:0) /// Proof: `Swap::PalSwapInitialized` (`max_values`: None, `max_size`: Some(11), added: 2486, mode: `MaxEncodedLen`) - /// Storage: `SubtensorModule::SubnetAlphaIn` (r:1 w:1) - /// Proof: `SubtensorModule::SubnetAlphaIn` (`max_values`: None, `max_size`: None, mode: `Measured`) /// Storage: `Swap::SwapBalancer` (r:1 w:0) /// Proof: `Swap::SwapBalancer` (`max_values`: None, `max_size`: Some(18), added: 2493, mode: `MaxEncodedLen`) - /// Storage: `Swap::FeeRate` (r:1 w:0) - /// Proof: `Swap::FeeRate` (`max_values`: None, `max_size`: Some(12), added: 2487, mode: `MaxEncodedLen`) /// Storage: `SubtensorModule::NetworksAdded` (r:3 w:0) /// Proof: `SubtensorModule::NetworksAdded` (`max_values`: None, `max_size`: None, mode: `Measured`) /// Storage: `SubtensorModule::Alpha` (r:1 w:0) @@ -1214,8 +1223,8 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `2176` // Estimated: `10591` - // Minimum execution time: 717_343_000 picoseconds. - Weight::from_parts(740_696_000, 10591) + // Minimum execution time: 751_993_000 picoseconds. + Weight::from_parts(769_279_000, 10591) .saturating_add(T::DbWeight::get().reads(27_u64)) .saturating_add(T::DbWeight::get().writes(13_u64)) } @@ -1231,16 +1240,16 @@ impl WeightInfo for SubstrateWeight { /// Proof: `SubtensorModule::TotalHotkeySharesV2` (`max_values`: None, `max_size`: None, mode: `Measured`) /// Storage: `SubtensorModule::SubnetMechanism` (r:2 w:0) /// Proof: `SubtensorModule::SubnetMechanism` (`max_values`: None, `max_size`: None, mode: `Measured`) + /// Storage: `SubtensorModule::SubnetAlphaIn` (r:2 w:2) + /// Proof: `SubtensorModule::SubnetAlphaIn` (`max_values`: None, `max_size`: None, mode: `Measured`) /// Storage: `SubtensorModule::SubnetTAO` (r:2 w:2) /// Proof: `SubtensorModule::SubnetTAO` (`max_values`: None, `max_size`: None, mode: `Measured`) + /// Storage: `Swap::FeeRate` (r:1 w:0) + /// Proof: `Swap::FeeRate` (`max_values`: None, `max_size`: Some(12), added: 2487, mode: `MaxEncodedLen`) /// Storage: `Swap::PalSwapInitialized` (r:1 w:0) /// Proof: `Swap::PalSwapInitialized` (`max_values`: None, `max_size`: Some(11), added: 2486, mode: `MaxEncodedLen`) - /// Storage: `SubtensorModule::SubnetAlphaIn` (r:2 w:2) - /// Proof: `SubtensorModule::SubnetAlphaIn` (`max_values`: None, `max_size`: None, mode: `Measured`) /// Storage: `Swap::SwapBalancer` (r:1 w:0) /// Proof: `Swap::SwapBalancer` (`max_values`: None, `max_size`: Some(18), added: 2493, mode: `MaxEncodedLen`) - /// Storage: `Swap::FeeRate` (r:1 w:0) - /// Proof: `Swap::FeeRate` (`max_values`: None, `max_size`: Some(12), added: 2487, mode: `MaxEncodedLen`) /// Storage: `SubtensorModule::NetworksAdded` (r:2 w:0) /// Proof: `SubtensorModule::NetworksAdded` (`max_values`: None, `max_size`: None, mode: `Measured`) /// Storage: `SubtensorModule::SubtokenEnabled` (r:2 w:0) @@ -1287,8 +1296,8 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `2662` // Estimated: `11077` - // Minimum execution time: 921_853_000 picoseconds. - Weight::from_parts(944_515_000, 11077) + // Minimum execution time: 962_414_000 picoseconds. + Weight::from_parts(980_201_000, 11077) .saturating_add(T::DbWeight::get().reads(47_u64)) .saturating_add(T::DbWeight::get().writes(24_u64)) } @@ -1314,8 +1323,6 @@ impl WeightInfo for SubstrateWeight { /// Proof: `SubtensorModule::StakingHotkeys` (`max_values`: None, `max_size`: None, mode: `Measured`) /// Storage: `SubtensorModule::Lock` (r:1 w:0) /// Proof: `SubtensorModule::Lock` (`max_values`: None, `max_size`: None, mode: `Measured`) - /// Storage: `SubtensorModule::AccountFlags` (r:1 w:0) - /// Proof: `SubtensorModule::AccountFlags` (`max_values`: None, `max_size`: None, mode: `Measured`) /// Storage: `SubtensorModule::SubnetMechanism` (r:1 w:0) /// Proof: `SubtensorModule::SubnetMechanism` (`max_values`: None, `max_size`: None, mode: `Measured`) /// Storage: `SubtensorModule::SubnetAlphaIn` (r:1 w:0) @@ -1328,11 +1335,11 @@ impl WeightInfo for SubstrateWeight { /// Proof: `SubtensorModule::LastColdkeyHotkeyStakeBlock` (`max_values`: None, `max_size`: None, mode: `Measured`) fn transfer_stake() -> Weight { // Proof Size summary in bytes: - // Measured: `2054` - // Estimated: `7994` - // Minimum execution time: 254_636_000 picoseconds. - Weight::from_parts(258_541_000, 7994) - .saturating_add(T::DbWeight::get().reads(19_u64)) + // Measured: `1988` + // Estimated: `7928` + // Minimum execution time: 250_521_000 picoseconds. + Weight::from_parts(252_694_000, 7928) + .saturating_add(T::DbWeight::get().reads(18_u64)) .saturating_add(T::DbWeight::get().writes(6_u64)) } /// Storage: `SubtensorModule::Alpha` (r:2 w:0) @@ -1355,14 +1362,14 @@ impl WeightInfo for SubstrateWeight { /// Proof: `SubtensorModule::SubnetMechanism` (`max_values`: None, `max_size`: None, mode: `Measured`) /// Storage: `SubtensorModule::SubnetTAO` (r:2 w:2) /// Proof: `SubtensorModule::SubnetTAO` (`max_values`: None, `max_size`: None, mode: `Measured`) - /// Storage: `Swap::PalSwapInitialized` (r:1 w:0) - /// Proof: `Swap::PalSwapInitialized` (`max_values`: None, `max_size`: Some(11), added: 2486, mode: `MaxEncodedLen`) + /// Storage: `Swap::FeeRate` (r:1 w:0) + /// Proof: `Swap::FeeRate` (`max_values`: None, `max_size`: Some(12), added: 2487, mode: `MaxEncodedLen`) /// Storage: `SubtensorModule::SubnetAlphaIn` (r:2 w:2) /// Proof: `SubtensorModule::SubnetAlphaIn` (`max_values`: None, `max_size`: None, mode: `Measured`) + /// Storage: `Swap::PalSwapInitialized` (r:1 w:0) + /// Proof: `Swap::PalSwapInitialized` (`max_values`: None, `max_size`: Some(11), added: 2486, mode: `MaxEncodedLen`) /// Storage: `Swap::SwapBalancer` (r:1 w:0) /// Proof: `Swap::SwapBalancer` (`max_values`: None, `max_size`: Some(18), added: 2493, mode: `MaxEncodedLen`) - /// Storage: `Swap::FeeRate` (r:1 w:0) - /// Proof: `Swap::FeeRate` (`max_values`: None, `max_size`: Some(12), added: 2487, mode: `MaxEncodedLen`) /// Storage: `SubtensorModule::StakingHotkeys` (r:1 w:0) /// Proof: `SubtensorModule::StakingHotkeys` (`max_values`: None, `max_size`: None, mode: `Measured`) /// Storage: `SubtensorModule::Lock` (r:3 w:1) @@ -1403,8 +1410,8 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `2505` // Estimated: `10920` - // Minimum execution time: 717_052_000 picoseconds. - Weight::from_parts(742_019_000, 10920) + // Minimum execution time: 736_000_000 picoseconds. + Weight::from_parts(755_388_000, 10920) .saturating_add(T::DbWeight::get().reads(47_u64)) .saturating_add(T::DbWeight::get().writes(24_u64)) } @@ -1442,8 +1449,8 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `1300` // Estimated: `4765` - // Minimum execution time: 144_628_000 picoseconds. - Weight::from_parts(147_224_000, 4765) + // Minimum execution time: 144_774_000 picoseconds. + Weight::from_parts(146_056_000, 4765) .saturating_add(T::DbWeight::get().reads(15_u64)) .saturating_add(T::DbWeight::get().writes(2_u64)) } @@ -1483,8 +1490,8 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `1455` // Estimated: `7395` - // Minimum execution time: 100_516_000 picoseconds. - Weight::from_parts(103_092_000, 7395) + // Minimum execution time: 98_445_000 picoseconds. + Weight::from_parts(100_549_000, 7395) .saturating_add(T::DbWeight::get().reads(16_u64)) .saturating_add(T::DbWeight::get().writes(2_u64)) } @@ -1500,8 +1507,8 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `830` // Estimated: `4295` - // Minimum execution time: 29_324_000 picoseconds. - Weight::from_parts(29_895_000, 4295) + // Minimum execution time: 26_119_000 picoseconds. + Weight::from_parts(27_451_000, 4295) .saturating_add(T::DbWeight::get().reads(3_u64)) .saturating_add(T::DbWeight::get().writes(2_u64)) } @@ -1519,8 +1526,8 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `923` // Estimated: `4388` - // Minimum execution time: 36_257_000 picoseconds. - Weight::from_parts(37_710_000, 4388) + // Minimum execution time: 33_670_000 picoseconds. + Weight::from_parts(34_631_000, 4388) .saturating_add(T::DbWeight::get().reads(5_u64)) .saturating_add(T::DbWeight::get().writes(2_u64)) } @@ -1536,6 +1543,10 @@ impl WeightInfo for SubstrateWeight { /// Proof: `SubtensorModule::SubnetLimit` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) /// Storage: `SubtensorModule::NetworksAdded` (r:3 w:1) /// Proof: `SubtensorModule::NetworksAdded` (`max_values`: None, `max_size`: None, mode: `Measured`) + /// Storage: `SubtensorModule::DissolveCleanupQueue` (r:1 w:0) + /// Proof: `SubtensorModule::DissolveCleanupQueue` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + /// Storage: `SubtensorModule::NetworkRegistrationQueue` (r:1 w:0) + /// Proof: `SubtensorModule::NetworkRegistrationQueue` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) /// Storage: `SubtensorModule::NetworkLastLockCost` (r:1 w:1) /// Proof: `SubtensorModule::NetworkLastLockCost` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) /// Storage: `SubtensorModule::NetworkMinLockCost` (r:1 w:0) @@ -1544,8 +1555,6 @@ impl WeightInfo for SubstrateWeight { /// Proof: `SubtensorModule::NetworkLockReductionInterval` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) /// Storage: `SubtensorModule::TotalIssuance` (r:1 w:0) /// Proof: `SubtensorModule::TotalIssuance` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) - /// Storage: `SubtensorModule::BlockEmission` (r:1 w:0) - /// Proof: `SubtensorModule::BlockEmission` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) /// Storage: `SubtensorModule::SubnetMechanism` (r:1 w:1) /// Proof: `SubtensorModule::SubnetMechanism` (`max_values`: None, `max_size`: None, mode: `Measured`) /// Storage: `SubtensorModule::SubnetAlphaIn` (r:1 w:1) @@ -1640,11 +1649,13 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `1468` // Estimated: `9883` - // Minimum execution time: 270_502_000 picoseconds. - Weight::from_parts(275_041_000, 9883) - .saturating_add(T::DbWeight::get().reads(39_u64)) + // Minimum execution time: 281_587_000 picoseconds. + Weight::from_parts(287_927_000, 9883) + .saturating_add(T::DbWeight::get().reads(40_u64)) .saturating_add(T::DbWeight::get().writes(47_u64)) } + /// Storage: `SubtensorModule::NetworksAdded` (r:1 w:0) + /// Proof: `SubtensorModule::NetworksAdded` (`max_values`: None, `max_size`: None, mode: `Measured`) /// Storage: `SubtensorModule::Uids` (r:1 w:0) /// Proof: `SubtensorModule::Uids` (`max_values`: None, `max_size`: None, mode: `Measured`) /// Storage: `SubtensorModule::Axons` (r:1 w:1) @@ -1653,11 +1664,11 @@ impl WeightInfo for SubstrateWeight { /// Proof: `SubtensorModule::ServingRateLimit` (`max_values`: None, `max_size`: None, mode: `Measured`) fn serve_axon_tls() -> Weight { // Proof Size summary in bytes: - // Measured: `684` - // Estimated: `4149` - // Minimum execution time: 29_786_000 picoseconds. - Weight::from_parts(30_617_000, 4149) - .saturating_add(T::DbWeight::get().reads(3_u64)) + // Measured: `799` + // Estimated: `4264` + // Minimum execution time: 34_581_000 picoseconds. + Weight::from_parts(36_053_000, 4264) + .saturating_add(T::DbWeight::get().reads(4_u64)) .saturating_add(T::DbWeight::get().writes(1_u64)) } /// Storage: `SubtensorModule::OwnedHotkeys` (r:1 w:0) @@ -1670,30 +1681,28 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `889` // Estimated: `6829` - // Minimum execution time: 31_168_000 picoseconds. - Weight::from_parts(32_040_000, 6829) + // Minimum execution time: 29_433_000 picoseconds. + Weight::from_parts(30_685_000, 6829) .saturating_add(T::DbWeight::get().reads(3_u64)) .saturating_add(T::DbWeight::get().writes(1_u64)) } + /// Storage: `SubtensorModule::NetworksAdded` (r:1 w:0) + /// Proof: `SubtensorModule::NetworksAdded` (`max_values`: None, `max_size`: None, mode: `Measured`) /// Storage: `SubtensorModule::SubnetOwner` (r:1 w:0) /// Proof: `SubtensorModule::SubnetOwner` (`max_values`: None, `max_size`: None, mode: `Measured`) /// Storage: `SubtensorModule::SubnetIdentitiesV3` (r:0 w:1) /// Proof: `SubtensorModule::SubnetIdentitiesV3` (`max_values`: None, `max_size`: None, mode: `Measured`) fn set_subnet_identity() -> Weight { // Proof Size summary in bytes: - // Measured: `595` - // Estimated: `4060` - // Minimum execution time: 17_122_000 picoseconds. - Weight::from_parts(17_513_000, 4060) - .saturating_add(T::DbWeight::get().reads(1_u64)) + // Measured: `738` + // Estimated: `4203` + // Minimum execution time: 20_360_000 picoseconds. + Weight::from_parts(21_382_000, 4203) + .saturating_add(T::DbWeight::get().reads(2_u64)) .saturating_add(T::DbWeight::get().writes(1_u64)) } /// Storage: `SubtensorModule::Owner` (r:2 w:2) /// Proof: `SubtensorModule::Owner` (`max_values`: None, `max_size`: None, mode: `Measured`) - /// Storage: `SubtensorModule::LastRateLimitedBlock` (r:4 w:7) - /// Proof: `SubtensorModule::LastRateLimitedBlock` (`max_values`: None, `max_size`: None, mode: `Measured`) - /// Storage: `SubtensorModule::TxRateLimit` (r:1 w:0) - /// Proof: `SubtensorModule::TxRateLimit` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) /// Storage: `SubtensorModule::IsNetworkMember` (r:6 w:10) /// Proof: `SubtensorModule::IsNetworkMember` (`max_values`: None, `max_size`: None, mode: `Measured`) /// Storage: `SubtensorModule::RootClaimable` (r:2 w:2) @@ -1702,6 +1711,8 @@ impl WeightInfo for SubstrateWeight { /// Proof: `SubtensorModule::TotalHotkeyAlpha` (`max_values`: None, `max_size`: None, mode: `Measured`) /// Storage: `SubtensorModule::RootClaimed` (r:1 w:0) /// Proof: `SubtensorModule::RootClaimed` (`max_values`: None, `max_size`: None, mode: `Measured`) + /// Storage: `SubtensorModule::LastRateLimitedBlock` (r:2 w:5) + /// Proof: `SubtensorModule::LastRateLimitedBlock` (`max_values`: None, `max_size`: None, mode: `Measured`) /// Storage: `SubtensorModule::NetworksAdded` (r:6 w:0) /// Proof: `SubtensorModule::NetworksAdded` (`max_values`: None, `max_size`: None, mode: `Measured`) /// Storage: `SubtensorModule::ChildKeys` (r:10 w:10) @@ -1766,10 +1777,10 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `3172` // Estimated: `28912` - // Minimum execution time: 1_227_100_000 picoseconds. - Weight::from_parts(1_242_038_000, 28912) - .saturating_add(T::DbWeight::get().reads(182_u64)) - .saturating_add(T::DbWeight::get().writes(99_u64)) + // Minimum execution time: 1_242_920_000 picoseconds. + Weight::from_parts(1_257_572_000, 28912) + .saturating_add(T::DbWeight::get().reads(179_u64)) + .saturating_add(T::DbWeight::get().writes(97_u64)) } /// Storage: `SubtensorModule::Owner` (r:1 w:1) /// Proof: `SubtensorModule::Owner` (`max_values`: None, `max_size`: None, mode: `Measured`) @@ -1781,8 +1792,8 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `818` // Estimated: `4283` - // Minimum execution time: 24_556_000 picoseconds. - Weight::from_parts(25_337_000, 4283) + // Minimum execution time: 23_505_000 picoseconds. + Weight::from_parts(23_936_000, 4283) .saturating_add(T::DbWeight::get().reads(3_u64)) .saturating_add(T::DbWeight::get().writes(3_u64)) } @@ -1796,8 +1807,8 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `774` // Estimated: `9189` - // Minimum execution time: 25_918_000 picoseconds. - Weight::from_parts(26_830_000, 9189) + // Minimum execution time: 24_726_000 picoseconds. + Weight::from_parts(25_568_000, 9189) .saturating_add(T::DbWeight::get().reads(6_u64)) } /// Storage: `SubtensorModule::Owner` (r:1 w:0) @@ -1820,14 +1831,14 @@ impl WeightInfo for SubstrateWeight { /// Proof: `SubtensorModule::SubnetMechanism` (`max_values`: None, `max_size`: None, mode: `Measured`) /// Storage: `SubtensorModule::SubnetTAO` (r:2 w:2) /// Proof: `SubtensorModule::SubnetTAO` (`max_values`: None, `max_size`: None, mode: `Measured`) - /// Storage: `Swap::PalSwapInitialized` (r:1 w:0) - /// Proof: `Swap::PalSwapInitialized` (`max_values`: None, `max_size`: Some(11), added: 2486, mode: `MaxEncodedLen`) + /// Storage: `Swap::FeeRate` (r:1 w:0) + /// Proof: `Swap::FeeRate` (`max_values`: None, `max_size`: Some(12), added: 2487, mode: `MaxEncodedLen`) /// Storage: `SubtensorModule::SubnetAlphaIn` (r:2 w:2) /// Proof: `SubtensorModule::SubnetAlphaIn` (`max_values`: None, `max_size`: None, mode: `Measured`) + /// Storage: `Swap::PalSwapInitialized` (r:1 w:0) + /// Proof: `Swap::PalSwapInitialized` (`max_values`: None, `max_size`: Some(11), added: 2486, mode: `MaxEncodedLen`) /// Storage: `Swap::SwapBalancer` (r:1 w:0) /// Proof: `Swap::SwapBalancer` (`max_values`: None, `max_size`: Some(18), added: 2493, mode: `MaxEncodedLen`) - /// Storage: `Swap::FeeRate` (r:1 w:0) - /// Proof: `Swap::FeeRate` (`max_values`: None, `max_size`: Some(12), added: 2487, mode: `MaxEncodedLen`) /// Storage: `SubtensorModule::StakingHotkeys` (r:1 w:0) /// Proof: `SubtensorModule::StakingHotkeys` (`max_values`: None, `max_size`: None, mode: `Measured`) /// Storage: `SubtensorModule::Lock` (r:2 w:0) @@ -1858,11 +1869,13 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `2235` // Estimated: `11306` - // Minimum execution time: 674_042_000 picoseconds. - Weight::from_parts(700_873_000, 11306) + // Minimum execution time: 698_403_000 picoseconds. + Weight::from_parts(712_865_000, 11306) .saturating_add(T::DbWeight::get().reads(43_u64)) .saturating_add(T::DbWeight::get().writes(25_u64)) } + /// Storage: `SubtensorModule::NetworksAdded` (r:3 w:0) + /// Proof: `SubtensorModule::NetworksAdded` (`max_values`: None, `max_size`: None, mode: `Measured`) /// Storage: `SubtensorModule::Alpha` (r:1 w:0) /// Proof: `SubtensorModule::Alpha` (`max_values`: None, `max_size`: None, mode: `Measured`) /// Storage: `SubtensorModule::AlphaV2` (r:1 w:1) @@ -1875,18 +1888,16 @@ impl WeightInfo for SubstrateWeight { /// Proof: `SubtensorModule::TotalHotkeySharesV2` (`max_values`: None, `max_size`: None, mode: `Measured`) /// Storage: `SubtensorModule::SubnetMechanism` (r:2 w:0) /// Proof: `SubtensorModule::SubnetMechanism` (`max_values`: None, `max_size`: None, mode: `Measured`) + /// Storage: `SubtensorModule::SubnetAlphaIn` (r:1 w:1) + /// Proof: `SubtensorModule::SubnetAlphaIn` (`max_values`: None, `max_size`: None, mode: `Measured`) /// Storage: `SubtensorModule::SubnetTAO` (r:1 w:1) /// Proof: `SubtensorModule::SubnetTAO` (`max_values`: None, `max_size`: None, mode: `Measured`) + /// Storage: `Swap::FeeRate` (r:1 w:0) + /// Proof: `Swap::FeeRate` (`max_values`: None, `max_size`: Some(12), added: 2487, mode: `MaxEncodedLen`) /// Storage: `Swap::PalSwapInitialized` (r:1 w:0) /// Proof: `Swap::PalSwapInitialized` (`max_values`: None, `max_size`: Some(11), added: 2486, mode: `MaxEncodedLen`) - /// Storage: `SubtensorModule::SubnetAlphaIn` (r:1 w:1) - /// Proof: `SubtensorModule::SubnetAlphaIn` (`max_values`: None, `max_size`: None, mode: `Measured`) /// Storage: `Swap::SwapBalancer` (r:1 w:0) /// Proof: `Swap::SwapBalancer` (`max_values`: None, `max_size`: Some(18), added: 2493, mode: `MaxEncodedLen`) - /// Storage: `Swap::FeeRate` (r:1 w:0) - /// Proof: `Swap::FeeRate` (`max_values`: None, `max_size`: Some(12), added: 2487, mode: `MaxEncodedLen`) - /// Storage: `SubtensorModule::NetworksAdded` (r:3 w:0) - /// Proof: `SubtensorModule::NetworksAdded` (`max_values`: None, `max_size`: None, mode: `Measured`) /// Storage: `SubtensorModule::Owner` (r:1 w:0) /// Proof: `SubtensorModule::Owner` (`max_values`: None, `max_size`: None, mode: `Measured`) /// Storage: `SubtensorModule::StakingHotkeys` (r:1 w:0) @@ -1913,8 +1924,8 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `2176` // Estimated: `10591` - // Minimum execution time: 731_620_000 picoseconds. - Weight::from_parts(754_292_000, 10591) + // Minimum execution time: 774_816_000 picoseconds. + Weight::from_parts(792_172_000, 10591) .saturating_add(T::DbWeight::get().reads(27_u64)) .saturating_add(T::DbWeight::get().writes(13_u64)) } @@ -1938,6 +1949,10 @@ impl WeightInfo for SubstrateWeight { /// Proof: `SubtensorModule::SubnetLimit` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) /// Storage: `SubtensorModule::NetworksAdded` (r:3 w:1) /// Proof: `SubtensorModule::NetworksAdded` (`max_values`: None, `max_size`: None, mode: `Measured`) + /// Storage: `SubtensorModule::DissolveCleanupQueue` (r:1 w:0) + /// Proof: `SubtensorModule::DissolveCleanupQueue` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + /// Storage: `SubtensorModule::NetworkRegistrationQueue` (r:1 w:0) + /// Proof: `SubtensorModule::NetworkRegistrationQueue` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) /// Storage: `SubtensorModule::NetworkLastLockCost` (r:1 w:1) /// Proof: `SubtensorModule::NetworkLastLockCost` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) /// Storage: `SubtensorModule::NetworkMinLockCost` (r:1 w:0) @@ -1946,8 +1961,6 @@ impl WeightInfo for SubstrateWeight { /// Proof: `SubtensorModule::NetworkLockReductionInterval` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) /// Storage: `SubtensorModule::TotalIssuance` (r:1 w:0) /// Proof: `SubtensorModule::TotalIssuance` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) - /// Storage: `SubtensorModule::BlockEmission` (r:1 w:0) - /// Proof: `SubtensorModule::BlockEmission` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) /// Storage: `SubtensorModule::SubnetMechanism` (r:1 w:1) /// Proof: `SubtensorModule::SubnetMechanism` (`max_values`: None, `max_size`: None, mode: `Measured`) /// Storage: `SubtensorModule::SubnetAlphaIn` (r:1 w:1) @@ -2053,11 +2066,11 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `1835 + k * (44 ±0)` // Estimated: `10256 + k * (2579 ±0)` - // Minimum execution time: 475_774_000 picoseconds. - Weight::from_parts(309_598_986, 10256) - // Standard Error: 26_969 - .saturating_add(Weight::from_parts(46_027_026, 0).saturating_mul(k.into())) - .saturating_add(T::DbWeight::get().reads(49_u64)) + // Minimum execution time: 484_517_000 picoseconds. + Weight::from_parts(309_951_401, 10256) + // Standard Error: 64_287 + .saturating_add(Weight::from_parts(47_050_119, 0).saturating_mul(k.into())) + .saturating_add(T::DbWeight::get().reads(50_u64)) .saturating_add(T::DbWeight::get().reads((2_u64).saturating_mul(k.into()))) .saturating_add(T::DbWeight::get().writes(53_u64)) .saturating_add(T::DbWeight::get().writes((2_u64).saturating_mul(k.into()))) @@ -2086,10 +2099,10 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `1501 + k * (53 ±0)` // Estimated: `6148 + k * (2529 ±0)` - // Minimum execution time: 127_077_000 picoseconds. - Weight::from_parts(146_402_779, 6148) - // Standard Error: 3_377 - .saturating_add(Weight::from_parts(1_566_532, 0).saturating_mul(k.into())) + // Minimum execution time: 90_524_000 picoseconds. + Weight::from_parts(77_908_272, 6148) + // Standard Error: 5_176 + .saturating_add(Weight::from_parts(1_663_793, 0).saturating_mul(k.into())) .saturating_add(T::DbWeight::get().reads(4_u64)) .saturating_add(T::DbWeight::get().reads((1_u64).saturating_mul(k.into()))) .saturating_add(T::DbWeight::get().writes(7_u64)) @@ -2098,15 +2111,17 @@ impl WeightInfo for SubstrateWeight { } /// Storage: `SubtensorModule::SubnetOwner` (r:1 w:0) /// Proof: `SubtensorModule::SubnetOwner` (`max_values`: None, `max_size`: None, mode: `Measured`) + /// Storage: `SubtensorModule::NetworksAdded` (r:1 w:0) + /// Proof: `SubtensorModule::NetworksAdded` (`max_values`: None, `max_size`: None, mode: `Measured`) /// Storage: `SubtensorModule::TokenSymbol` (r:3 w:1) /// Proof: `SubtensorModule::TokenSymbol` (`max_values`: None, `max_size`: None, mode: `Measured`) fn update_symbol() -> Weight { // Proof Size summary in bytes: - // Measured: `659` - // Estimated: `9074` - // Minimum execution time: 27_300_000 picoseconds. - Weight::from_parts(28_313_000, 9074) - .saturating_add(T::DbWeight::get().reads(4_u64)) + // Measured: `762` + // Estimated: `9177` + // Minimum execution time: 30_125_000 picoseconds. + Weight::from_parts(31_456_000, 9177) + .saturating_add(T::DbWeight::get().reads(5_u64)) .saturating_add(T::DbWeight::get().writes(1_u64)) } /// Storage: `SubtensorModule::NetworksAdded` (r:1 w:0) @@ -2141,8 +2156,8 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `1248` // Estimated: `4713` - // Minimum execution time: 85_228_000 picoseconds. - Weight::from_parts(86_951_000, 4713) + // Minimum execution time: 82_703_000 picoseconds. + Weight::from_parts(84_635_000, 4713) .saturating_add(T::DbWeight::get().reads(14_u64)) .saturating_add(T::DbWeight::get().writes(2_u64)) } @@ -2158,8 +2173,8 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `809` // Estimated: `4274` - // Minimum execution time: 32_871_000 picoseconds. - Weight::from_parts(33_853_000, 4274) + // Minimum execution time: 31_046_000 picoseconds. + Weight::from_parts(32_027_000, 4274) .saturating_add(T::DbWeight::get().reads(4_u64)) .saturating_add(T::DbWeight::get().writes(2_u64)) } @@ -2175,8 +2190,8 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `476` // Estimated: `3941` - // Minimum execution time: 17_463_000 picoseconds. - Weight::from_parts(18_283_000, 3941) + // Minimum execution time: 15_783_000 picoseconds. + Weight::from_parts(16_364_000, 3941) .saturating_add(T::DbWeight::get().reads(2_u64)) .saturating_add(T::DbWeight::get().writes(4_u64)) } @@ -2188,6 +2203,8 @@ impl WeightInfo for SubstrateWeight { /// Proof: `SubtensorModule::RootClaimType` (`max_values`: None, `max_size`: None, mode: `Measured`) /// Storage: `SubtensorModule::RootClaimable` (r:1 w:0) /// Proof: `SubtensorModule::RootClaimable` (`max_values`: None, `max_size`: None, mode: `Measured`) + /// Storage: `SubtensorModule::DissolveCleanupQueue` (r:1 w:0) + /// Proof: `SubtensorModule::DissolveCleanupQueue` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) /// Storage: `SubtensorModule::Alpha` (r:2 w:0) /// Proof: `SubtensorModule::Alpha` (`max_values`: None, `max_size`: None, mode: `Measured`) /// Storage: `SubtensorModule::AlphaV2` (r:2 w:1) @@ -2204,11 +2221,11 @@ impl WeightInfo for SubstrateWeight { /// Proof: `SubtensorModule::RootClaimableThreshold` (`max_values`: None, `max_size`: None, mode: `Measured`) fn claim_root() -> Weight { // Proof Size summary in bytes: - // Measured: `1935` - // Estimated: `7875` - // Minimum execution time: 136_093_000 picoseconds. - Weight::from_parts(138_999_000, 7875) - .saturating_add(T::DbWeight::get().reads(16_u64)) + // Measured: `1969` + // Estimated: `7909` + // Minimum execution time: 139_606_000 picoseconds. + Weight::from_parts(141_219_000, 7909) + .saturating_add(T::DbWeight::get().reads(17_u64)) .saturating_add(T::DbWeight::get().writes(4_u64)) } /// Storage: `SubtensorModule::NumRootClaim` (r:0 w:1) @@ -2217,18 +2234,21 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 2_545_000 picoseconds. - Weight::from_parts(2_735_000, 0) + // Minimum execution time: 2_033_000 picoseconds. + Weight::from_parts(2_144_000, 0) .saturating_add(T::DbWeight::get().writes(1_u64)) } + /// Storage: `SubtensorModule::NetworksAdded` (r:1 w:0) + /// Proof: `SubtensorModule::NetworksAdded` (`max_values`: None, `max_size`: None, mode: `Measured`) /// Storage: `SubtensorModule::RootClaimableThreshold` (r:0 w:1) /// Proof: `SubtensorModule::RootClaimableThreshold` (`max_values`: None, `max_size`: None, mode: `Measured`) fn sudo_set_root_claim_threshold() -> Weight { // Proof Size summary in bytes: - // Measured: `0` - // Estimated: `0` - // Minimum execution time: 5_229_000 picoseconds. - Weight::from_parts(5_681_000, 0) + // Measured: `652` + // Estimated: `4117` + // Minimum execution time: 13_540_000 picoseconds. + Weight::from_parts(14_041_000, 4117) + .saturating_add(T::DbWeight::get().reads(1_u64)) .saturating_add(T::DbWeight::get().writes(1_u64)) } /// Storage: `SubtensorModule::Owner` (r:1 w:0) @@ -2241,25 +2261,25 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `899` // Estimated: `4364` - // Minimum execution time: 26_870_000 picoseconds. - Weight::from_parts(28_023_000, 4364) + // Minimum execution time: 24_416_000 picoseconds. + Weight::from_parts(26_088_000, 4364) .saturating_add(T::DbWeight::get().reads(2_u64)) .saturating_add(T::DbWeight::get().writes(1_u64)) } + /// Storage: `SubtensorModule::NetworksAdded` (r:1 w:0) + /// Proof: `SubtensorModule::NetworksAdded` (`max_values`: None, `max_size`: None, mode: `Measured`) /// Storage: `SubtensorModule::SubnetMechanism` (r:1 w:0) /// Proof: `SubtensorModule::SubnetMechanism` (`max_values`: None, `max_size`: None, mode: `Measured`) + /// Storage: `SubtensorModule::SubnetTAO` (r:1 w:1) + /// Proof: `SubtensorModule::SubnetTAO` (`max_values`: None, `max_size`: None, mode: `Measured`) /// Storage: `SubtensorModule::SubnetAlphaIn` (r:1 w:1) /// Proof: `SubtensorModule::SubnetAlphaIn` (`max_values`: None, `max_size`: None, mode: `Measured`) + /// Storage: `Swap::FeeRate` (r:1 w:0) + /// Proof: `Swap::FeeRate` (`max_values`: None, `max_size`: Some(12), added: 2487, mode: `MaxEncodedLen`) /// Storage: `Swap::PalSwapInitialized` (r:1 w:0) /// Proof: `Swap::PalSwapInitialized` (`max_values`: None, `max_size`: Some(11), added: 2486, mode: `MaxEncodedLen`) - /// Storage: `SubtensorModule::SubnetTAO` (r:1 w:1) - /// Proof: `SubtensorModule::SubnetTAO` (`max_values`: None, `max_size`: None, mode: `Measured`) /// Storage: `Swap::SwapBalancer` (r:1 w:0) /// Proof: `Swap::SwapBalancer` (`max_values`: None, `max_size`: Some(18), added: 2493, mode: `MaxEncodedLen`) - /// Storage: `Swap::FeeRate` (r:1 w:0) - /// Proof: `Swap::FeeRate` (`max_values`: None, `max_size`: Some(12), added: 2487, mode: `MaxEncodedLen`) - /// Storage: `SubtensorModule::NetworksAdded` (r:1 w:0) - /// Proof: `SubtensorModule::NetworksAdded` (`max_values`: None, `max_size`: None, mode: `Measured`) /// Storage: `SubtensorModule::SubtokenEnabled` (r:1 w:0) /// Proof: `SubtensorModule::SubtokenEnabled` (`max_values`: None, `max_size`: None, mode: `Measured`) /// Storage: `System::Account` (r:3 w:3) @@ -2314,21 +2334,42 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `2229` // Estimated: `8727` - // Minimum execution time: 945_077_000 picoseconds. - Weight::from_parts(967_308_000, 8727) + // Minimum execution time: 1_028_031_000 picoseconds. + Weight::from_parts(1_034_060_000, 8727) .saturating_add(T::DbWeight::get().reads(33_u64)) .saturating_add(T::DbWeight::get().writes(17_u64)) } + /// Storage: `SubtensorModule::NetworksAdded` (r:1 w:1) + /// Proof: `SubtensorModule::NetworksAdded` (`max_values`: None, `max_size`: None, mode: `Measured`) + /// Storage: `SubtensorModule::DissolveCleanupQueue` (r:1 w:1) + /// Proof: `SubtensorModule::DissolveCleanupQueue` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + /// Storage: `SubtensorModule::TotalNetworks` (r:1 w:1) + /// Proof: `SubtensorModule::TotalNetworks` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + /// Storage: `SubtensorModule::TotalStake` (r:1 w:1) + /// Proof: `SubtensorModule::TotalStake` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + /// Storage: `SubtensorModule::SubnetTAO` (r:1 w:0) + /// Proof: `SubtensorModule::SubnetTAO` (`max_values`: None, `max_size`: None, mode: `Measured`) + fn dissolve_network() -> Weight { + // Proof Size summary in bytes: + // Measured: `842` + // Estimated: `4307` + // Minimum execution time: 31_587_000 picoseconds. + Weight::from_parts(32_539_000, 4307) + .saturating_add(T::DbWeight::get().reads(5_u64)) + .saturating_add(T::DbWeight::get().writes(4_u64)) + } /// Storage: `SubtensorModule::PendingChildKeyCooldown` (r:0 w:1) /// Proof: `SubtensorModule::PendingChildKeyCooldown` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) fn set_pending_childkey_cooldown() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 2_585_000 picoseconds. - Weight::from_parts(2_805_000, 0) + // Minimum execution time: 1_973_000 picoseconds. + Weight::from_parts(2_133_000, 0) .saturating_add(T::DbWeight::get().writes(1_u64)) } + /// Storage: `SubtensorModule::NetworksAdded` (r:1 w:0) + /// Proof: `SubtensorModule::NetworksAdded` (`max_values`: None, `max_size`: None, mode: `Measured`) /// Storage: `SubtensorModule::Owner` (r:1 w:0) /// Proof: `SubtensorModule::Owner` (`max_values`: None, `max_size`: None, mode: `Measured`) /// Storage: `SubtensorModule::StakingHotkeys` (r:1 w:0) @@ -2365,13 +2406,15 @@ impl WeightInfo for SubstrateWeight { /// Proof: `SubtensorModule::LockingColdkeys` (`max_values`: None, `max_size`: None, mode: `Measured`) fn lock_stake() -> Weight { // Proof Size summary in bytes: - // Measured: `1715` - // Estimated: `7655` - // Minimum execution time: 114_744_000 picoseconds. - Weight::from_parts(115_995_000, 7655) - .saturating_add(T::DbWeight::get().reads(17_u64)) + // Measured: `1830` + // Estimated: `7770` + // Minimum execution time: 121_600_000 picoseconds. + Weight::from_parts(123_313_000, 7770) + .saturating_add(T::DbWeight::get().reads(18_u64)) .saturating_add(T::DbWeight::get().writes(3_u64)) } + /// Storage: `SubtensorModule::NetworksAdded` (r:1 w:0) + /// Proof: `SubtensorModule::NetworksAdded` (`max_values`: None, `max_size`: None, mode: `Measured`) /// Storage: `SubtensorModule::Owner` (r:2 w:0) /// Proof: `SubtensorModule::Owner` (`max_values`: None, `max_size`: None, mode: `Measured`) /// Storage: `SubtensorModule::Lock` (r:2 w:2) @@ -2396,13 +2439,15 @@ impl WeightInfo for SubstrateWeight { /// Proof: `SubtensorModule::LockingColdkeys` (`max_values`: None, `max_size`: None, mode: `Measured`) fn move_lock() -> Weight { // Proof Size summary in bytes: - // Measured: `1399` - // Estimated: `7339` - // Minimum execution time: 147_815_000 picoseconds. - Weight::from_parts(149_819_000, 7339) - .saturating_add(T::DbWeight::get().reads(14_u64)) + // Measured: `1515` + // Estimated: `7455` + // Minimum execution time: 162_571_000 picoseconds. + Weight::from_parts(164_834_000, 7455) + .saturating_add(T::DbWeight::get().reads(15_u64)) .saturating_add(T::DbWeight::get().writes(6_u64)) } + /// Storage: `SubtensorModule::NetworksAdded` (r:1 w:0) + /// Proof: `SubtensorModule::NetworksAdded` (`max_values`: None, `max_size`: None, mode: `Measured`) /// Storage: `SubtensorModule::Owner` (r:1 w:0) /// Proof: `SubtensorModule::Owner` (`max_values`: None, `max_size`: None, mode: `Measured`) /// Storage: `SubtensorModule::Uids` (r:1 w:0) @@ -2411,11 +2456,11 @@ impl WeightInfo for SubstrateWeight { /// Proof: `SubtensorModule::AssociatedEvmAddress` (`max_values`: None, `max_size`: None, mode: `Measured`) fn associate_evm_key() -> Weight { // Proof Size summary in bytes: - // Measured: `950` - // Estimated: `4415` - // Minimum execution time: 665_186_000 picoseconds. - Weight::from_parts(684_242_000, 4415) - .saturating_add(T::DbWeight::get().reads(3_u64)) + // Measured: `1065` + // Estimated: `4530` + // Minimum execution time: 707_727_000 picoseconds. + Weight::from_parts(724_452_000, 4530) + .saturating_add(T::DbWeight::get().reads(4_u64)) .saturating_add(T::DbWeight::get().writes(1_u64)) } /// Storage: `SubtensorModule::SubnetOwner` (r:1 w:0) @@ -2434,8 +2479,8 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `975` // Estimated: `4440` - // Minimum execution time: 45_394_000 picoseconds. - Weight::from_parts(46_407_000, 4440) + // Minimum execution time: 43_113_000 picoseconds. + Weight::from_parts(44_276_000, 4440) .saturating_add(T::DbWeight::get().reads(6_u64)) .saturating_add(T::DbWeight::get().writes(3_u64)) } @@ -2459,8 +2504,8 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `899` // Estimated: `4364` - // Minimum execution time: 38_362_000 picoseconds. - Weight::from_parts(39_193_000, 4364) + // Minimum execution time: 36_284_000 picoseconds. + Weight::from_parts(37_776_000, 4364) .saturating_add(T::DbWeight::get().reads(7_u64)) .saturating_add(T::DbWeight::get().writes(2_u64)) } @@ -2484,8 +2529,8 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `982` // Estimated: `4447` - // Minimum execution time: 41_588_000 picoseconds. - Weight::from_parts(42_539_000, 4447) + // Minimum execution time: 39_639_000 picoseconds. + Weight::from_parts(41_111_000, 4447) .saturating_add(T::DbWeight::get().reads(8_u64)) .saturating_add(T::DbWeight::get().writes(2_u64)) } @@ -2497,8 +2542,8 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `733` // Estimated: `4198` - // Minimum execution time: 16_832_000 picoseconds. - Weight::from_parts(17_583_000, 4198) + // Minimum execution time: 16_324_000 picoseconds. + Weight::from_parts(16_735_000, 4198) .saturating_add(T::DbWeight::get().reads(2_u64)) } /// Storage: `SubtensorModule::SubnetOwnerHotkey` (r:1 w:0) @@ -2525,8 +2570,8 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `1731` // Estimated: `7671` - // Minimum execution time: 52_046_000 picoseconds. - Weight::from_parts(52_958_000, 7671) + // Minimum execution time: 50_485_000 picoseconds. + Weight::from_parts(52_107_000, 7671) .saturating_add(T::DbWeight::get().reads(11_u64)) } /// Storage: `SubtensorModule::CommitRevealWeightsEnabled` (r:1 w:0) @@ -2547,8 +2592,8 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `1019` // Estimated: `4484` - // Minimum execution time: 34_995_000 picoseconds. - Weight::from_parts(35_376_000, 4484) + // Minimum execution time: 33_470_000 picoseconds. + Weight::from_parts(34_380_000, 4484) .saturating_add(T::DbWeight::get().reads(7_u64)) } /// Storage: `SubtensorModule::MinDelegateTake` (r:1 w:0) @@ -2561,8 +2606,8 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `721` // Estimated: `4186` - // Minimum execution time: 14_998_000 picoseconds. - Weight::from_parts(15_378_000, 4186) + // Minimum execution time: 14_392_000 picoseconds. + Weight::from_parts(14_972_000, 4186) .saturating_add(T::DbWeight::get().reads(3_u64)) } /// Storage: `SubtensorModule::Uids` (r:1 w:0) @@ -2575,8 +2620,8 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `647` // Estimated: `4112` - // Minimum execution time: 18_775_000 picoseconds. - Weight::from_parts(19_035_000, 4112) + // Minimum execution time: 17_977_000 picoseconds. + Weight::from_parts(18_548_000, 4112) .saturating_add(T::DbWeight::get().reads(3_u64)) } /// Storage: `SubtensorModule::Uids` (r:1 w:0) @@ -2587,8 +2632,8 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `652` // Estimated: `4117` - // Minimum execution time: 15_018_000 picoseconds. - Weight::from_parts(15_448_000, 4117) + // Minimum execution time: 14_441_000 picoseconds. + Weight::from_parts(14_792_000, 4117) .saturating_add(T::DbWeight::get().reads(2_u64)) } } @@ -2619,12 +2664,12 @@ impl WeightInfo for () { /// Proof: `SubtensorModule::SubnetMechanism` (`max_values`: None, `max_size`: None, mode: `Measured`) /// Storage: `SubtensorModule::SubnetAlphaIn` (r:1 w:1) /// Proof: `SubtensorModule::SubnetAlphaIn` (`max_values`: None, `max_size`: None, mode: `Measured`) - /// Storage: `Swap::PalSwapInitialized` (r:1 w:1) - /// Proof: `Swap::PalSwapInitialized` (`max_values`: None, `max_size`: Some(11), added: 2486, mode: `MaxEncodedLen`) - /// Storage: `SubtensorModule::SubnetTAO` (r:1 w:1) - /// Proof: `SubtensorModule::SubnetTAO` (`max_values`: None, `max_size`: None, mode: `Measured`) /// Storage: `Swap::FeeRate` (r:1 w:0) /// Proof: `Swap::FeeRate` (`max_values`: None, `max_size`: Some(12), added: 2487, mode: `MaxEncodedLen`) + /// Storage: `SubtensorModule::SubnetTAO` (r:1 w:1) + /// Proof: `SubtensorModule::SubnetTAO` (`max_values`: None, `max_size`: None, mode: `Measured`) + /// Storage: `Swap::PalSwapInitialized` (r:1 w:1) + /// Proof: `Swap::PalSwapInitialized` (`max_values`: None, `max_size`: Some(11), added: 2486, mode: `MaxEncodedLen`) /// Storage: `SubtensorModule::SubnetAlphaOut` (r:1 w:1) /// Proof: `SubtensorModule::SubnetAlphaOut` (`max_values`: None, `max_size`: None, mode: `Measured`) /// Storage: `SubtensorModule::TotalStake` (r:1 w:1) @@ -2673,8 +2718,8 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `1865` // Estimated: `6148` - // Minimum execution time: 343_840_000 picoseconds. - Weight::from_parts(349_872_000, 6148) + // Minimum execution time: 342_717_000 picoseconds. + Weight::from_parts(345_692_000, 6148) .saturating_add(RocksDbWeight::get().reads(34_u64)) .saturating_add(RocksDbWeight::get().writes(29_u64)) } @@ -2716,27 +2761,27 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `188820` // Estimated: `10327410` - // Minimum execution time: 15_317_357_000 picoseconds. - Weight::from_parts(15_513_610_000, 10327410) + // Minimum execution time: 16_435_147_000 picoseconds. + Weight::from_parts(16_846_168_000, 10327410) .saturating_add(RocksDbWeight::get().reads(4112_u64)) .saturating_add(RocksDbWeight::get().writes(2_u64)) } + /// Storage: `SubtensorModule::SubnetMechanism` (r:1 w:0) + /// Proof: `SubtensorModule::SubnetMechanism` (`max_values`: None, `max_size`: None, mode: `Measured`) + /// Storage: `SubtensorModule::SubnetTAO` (r:1 w:1) + /// Proof: `SubtensorModule::SubnetTAO` (`max_values`: None, `max_size`: None, mode: `Measured`) /// Storage: `SubtensorModule::NetworksAdded` (r:1 w:0) /// Proof: `SubtensorModule::NetworksAdded` (`max_values`: None, `max_size`: None, mode: `Measured`) /// Storage: `SubtensorModule::SubtokenEnabled` (r:1 w:0) /// Proof: `SubtensorModule::SubtokenEnabled` (`max_values`: None, `max_size`: None, mode: `Measured`) - /// Storage: `SubtensorModule::SubnetMechanism` (r:1 w:0) - /// Proof: `SubtensorModule::SubnetMechanism` (`max_values`: None, `max_size`: None, mode: `Measured`) /// Storage: `SubtensorModule::SubnetAlphaIn` (r:1 w:1) /// Proof: `SubtensorModule::SubnetAlphaIn` (`max_values`: None, `max_size`: None, mode: `Measured`) + /// Storage: `Swap::FeeRate` (r:1 w:0) + /// Proof: `Swap::FeeRate` (`max_values`: None, `max_size`: Some(12), added: 2487, mode: `MaxEncodedLen`) /// Storage: `Swap::PalSwapInitialized` (r:1 w:0) /// Proof: `Swap::PalSwapInitialized` (`max_values`: None, `max_size`: Some(11), added: 2486, mode: `MaxEncodedLen`) - /// Storage: `SubtensorModule::SubnetTAO` (r:1 w:1) - /// Proof: `SubtensorModule::SubnetTAO` (`max_values`: None, `max_size`: None, mode: `Measured`) /// Storage: `Swap::SwapBalancer` (r:1 w:0) /// Proof: `Swap::SwapBalancer` (`max_values`: None, `max_size`: Some(18), added: 2493, mode: `MaxEncodedLen`) - /// Storage: `Swap::FeeRate` (r:1 w:0) - /// Proof: `Swap::FeeRate` (`max_values`: None, `max_size`: Some(12), added: 2487, mode: `MaxEncodedLen`) /// Storage: `System::Account` (r:3 w:3) /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(104), added: 2579, mode: `MaxEncodedLen`) /// Storage: `SubtensorModule::Owner` (r:1 w:0) @@ -2787,11 +2832,13 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `2226` // Estimated: `8727` - // Minimum execution time: 639_698_000 picoseconds. - Weight::from_parts(664_064_000, 8727) + // Minimum execution time: 677_613_000 picoseconds. + Weight::from_parts(693_357_000, 8727) .saturating_add(RocksDbWeight::get().reads(32_u64)) .saturating_add(RocksDbWeight::get().writes(16_u64)) } + /// Storage: `SubtensorModule::NetworksAdded` (r:1 w:0) + /// Proof: `SubtensorModule::NetworksAdded` (`max_values`: None, `max_size`: None, mode: `Measured`) /// Storage: `SubtensorModule::Uids` (r:1 w:0) /// Proof: `SubtensorModule::Uids` (`max_values`: None, `max_size`: None, mode: `Measured`) /// Storage: `SubtensorModule::Axons` (r:1 w:1) @@ -2800,13 +2847,15 @@ impl WeightInfo for () { /// Proof: `SubtensorModule::ServingRateLimit` (`max_values`: None, `max_size`: None, mode: `Measured`) fn serve_axon() -> Weight { // Proof Size summary in bytes: - // Measured: `713` - // Estimated: `4178` - // Minimum execution time: 30_307_000 picoseconds. - Weight::from_parts(31_278_000, 4178) - .saturating_add(RocksDbWeight::get().reads(3_u64)) + // Measured: `828` + // Estimated: `4293` + // Minimum execution time: 34_912_000 picoseconds. + Weight::from_parts(36_334_000, 4293) + .saturating_add(RocksDbWeight::get().reads(4_u64)) .saturating_add(RocksDbWeight::get().writes(1_u64)) } + /// Storage: `SubtensorModule::NetworksAdded` (r:1 w:0) + /// Proof: `SubtensorModule::NetworksAdded` (`max_values`: None, `max_size`: None, mode: `Measured`) /// Storage: `SubtensorModule::Uids` (r:1 w:0) /// Proof: `SubtensorModule::Uids` (`max_values`: None, `max_size`: None, mode: `Measured`) /// Storage: `SubtensorModule::Prometheus` (r:1 w:1) @@ -2815,11 +2864,11 @@ impl WeightInfo for () { /// Proof: `SubtensorModule::ServingRateLimit` (`max_values`: None, `max_size`: None, mode: `Measured`) fn serve_prometheus() -> Weight { // Proof Size summary in bytes: - // Measured: `812` - // Estimated: `4277` - // Minimum execution time: 28_262_000 picoseconds. - Weight::from_parts(29_294_000, 4277) - .saturating_add(RocksDbWeight::get().reads(3_u64)) + // Measured: `927` + // Estimated: `4392` + // Minimum execution time: 33_329_000 picoseconds. + Weight::from_parts(34_171_000, 4392) + .saturating_add(RocksDbWeight::get().reads(4_u64)) .saturating_add(RocksDbWeight::get().writes(1_u64)) } /// Storage: `SubtensorModule::NetworksAdded` (r:1 w:0) @@ -2846,12 +2895,12 @@ impl WeightInfo for () { /// Proof: `SubtensorModule::SubnetMechanism` (`max_values`: None, `max_size`: None, mode: `Measured`) /// Storage: `SubtensorModule::SubnetAlphaIn` (r:1 w:1) /// Proof: `SubtensorModule::SubnetAlphaIn` (`max_values`: None, `max_size`: None, mode: `Measured`) - /// Storage: `Swap::PalSwapInitialized` (r:1 w:1) - /// Proof: `Swap::PalSwapInitialized` (`max_values`: None, `max_size`: Some(11), added: 2486, mode: `MaxEncodedLen`) - /// Storage: `SubtensorModule::SubnetTAO` (r:1 w:1) - /// Proof: `SubtensorModule::SubnetTAO` (`max_values`: None, `max_size`: None, mode: `Measured`) /// Storage: `Swap::FeeRate` (r:1 w:0) /// Proof: `Swap::FeeRate` (`max_values`: None, `max_size`: Some(12), added: 2487, mode: `MaxEncodedLen`) + /// Storage: `SubtensorModule::SubnetTAO` (r:1 w:1) + /// Proof: `SubtensorModule::SubnetTAO` (`max_values`: None, `max_size`: None, mode: `Measured`) + /// Storage: `Swap::PalSwapInitialized` (r:1 w:1) + /// Proof: `Swap::PalSwapInitialized` (`max_values`: None, `max_size`: Some(11), added: 2486, mode: `MaxEncodedLen`) /// Storage: `SubtensorModule::SubnetAlphaOut` (r:1 w:1) /// Proof: `SubtensorModule::SubnetAlphaOut` (`max_values`: None, `max_size`: None, mode: `Measured`) /// Storage: `SubtensorModule::TotalStake` (r:1 w:1) @@ -2900,8 +2949,8 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `1798` // Estimated: `6148` - // Minimum execution time: 332_358_000 picoseconds. - Weight::from_parts(336_815_000, 6148) + // Minimum execution time: 341_535_000 picoseconds. + Weight::from_parts(346_603_000, 6148) .saturating_add(RocksDbWeight::get().reads(34_u64)) .saturating_add(RocksDbWeight::get().writes(29_u64)) } @@ -2953,8 +3002,8 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `1516` // Estimated: `4981` - // Minimum execution time: 102_089_000 picoseconds. - Weight::from_parts(103_934_000, 4981) + // Minimum execution time: 100_198_000 picoseconds. + Weight::from_parts(103_543_000, 4981) .saturating_add(RocksDbWeight::get().reads(19_u64)) .saturating_add(RocksDbWeight::get().writes(16_u64)) } @@ -2970,6 +3019,10 @@ impl WeightInfo for () { /// Proof: `SubtensorModule::SubnetLimit` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) /// Storage: `SubtensorModule::NetworksAdded` (r:3 w:1) /// Proof: `SubtensorModule::NetworksAdded` (`max_values`: None, `max_size`: None, mode: `Measured`) + /// Storage: `SubtensorModule::DissolveCleanupQueue` (r:1 w:0) + /// Proof: `SubtensorModule::DissolveCleanupQueue` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + /// Storage: `SubtensorModule::NetworkRegistrationQueue` (r:1 w:0) + /// Proof: `SubtensorModule::NetworkRegistrationQueue` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) /// Storage: `SubtensorModule::NetworkLastLockCost` (r:1 w:1) /// Proof: `SubtensorModule::NetworkLastLockCost` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) /// Storage: `SubtensorModule::NetworkMinLockCost` (r:1 w:0) @@ -2978,8 +3031,6 @@ impl WeightInfo for () { /// Proof: `SubtensorModule::NetworkLockReductionInterval` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) /// Storage: `SubtensorModule::TotalIssuance` (r:1 w:0) /// Proof: `SubtensorModule::TotalIssuance` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) - /// Storage: `SubtensorModule::BlockEmission` (r:1 w:0) - /// Proof: `SubtensorModule::BlockEmission` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) /// Storage: `System::Account` (r:2 w:2) /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(104), added: 2579, mode: `MaxEncodedLen`) /// Storage: `SubtensorModule::SubnetMechanism` (r:1 w:1) @@ -3074,9 +3125,9 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `1532` // Estimated: `9947` - // Minimum execution time: 271_405_000 picoseconds. - Weight::from_parts(277_125_000, 9947) - .saturating_add(RocksDbWeight::get().reads(40_u64)) + // Minimum execution time: 277_000_000 picoseconds. + Weight::from_parts(284_001_000, 9947) + .saturating_add(RocksDbWeight::get().reads(41_u64)) .saturating_add(RocksDbWeight::get().writes(48_u64)) } /// Storage: `SubtensorModule::NetworksAdded` (r:1 w:0) @@ -3109,11 +3160,13 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `1249` // Estimated: `4714` - // Minimum execution time: 67_886_000 picoseconds. - Weight::from_parts(69_049_000, 4714) + // Minimum execution time: 66_649_000 picoseconds. + Weight::from_parts(67_991_000, 4714) .saturating_add(RocksDbWeight::get().reads(13_u64)) .saturating_add(RocksDbWeight::get().writes(2_u64)) } + /// Storage: `SubtensorModule::NetworksAdded` (r:1 w:0) + /// Proof: `SubtensorModule::NetworksAdded` (`max_values`: None, `max_size`: None, mode: `Measured`) /// Storage: `SubtensorModule::CommitRevealWeightsEnabled` (r:1 w:0) /// Proof: `SubtensorModule::CommitRevealWeightsEnabled` (`max_values`: None, `max_size`: None, mode: `Measured`) /// Storage: `SubtensorModule::WeightCommits` (r:1 w:1) @@ -3124,8 +3177,6 @@ impl WeightInfo for () { /// Proof: `SubtensorModule::Tempo` (`max_values`: None, `max_size`: None, mode: `Measured`) /// Storage: `SubtensorModule::RevealPeriodEpochs` (r:1 w:0) /// Proof: `SubtensorModule::RevealPeriodEpochs` (`max_values`: None, `max_size`: None, mode: `Measured`) - /// Storage: `SubtensorModule::NetworksAdded` (r:1 w:0) - /// Proof: `SubtensorModule::NetworksAdded` (`max_values`: None, `max_size`: None, mode: `Measured`) /// Storage: `SubtensorModule::MechanismCountCurrent` (r:1 w:0) /// Proof: `SubtensorModule::MechanismCountCurrent` (`max_values`: None, `max_size`: None, mode: `Measured`) /// Storage: `SubtensorModule::SubnetworkN` (r:1 w:0) @@ -3156,8 +3207,8 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `1650` // Estimated: `7590` - // Minimum execution time: 109_534_000 picoseconds. - Weight::from_parts(111_227_000, 7590) + // Minimum execution time: 109_712_000 picoseconds. + Weight::from_parts(111_755_000, 7590) .saturating_add(RocksDbWeight::get().reads(19_u64)) .saturating_add(RocksDbWeight::get().writes(2_u64)) } @@ -3167,12 +3218,14 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 5_570_000 picoseconds. - Weight::from_parts(5_760_000, 0) + // Minimum execution time: 4_237_000 picoseconds. + Weight::from_parts(4_537_000, 0) .saturating_add(RocksDbWeight::get().writes(1_u64)) } /// Storage: `SubtensorModule::Owner` (r:1 w:0) /// Proof: `SubtensorModule::Owner` (`max_values`: None, `max_size`: None, mode: `Measured`) + /// Storage: `SubtensorModule::NetworksAdded` (r:1 w:0) + /// Proof: `SubtensorModule::NetworksAdded` (`max_values`: None, `max_size`: None, mode: `Measured`) /// Storage: `SubtensorModule::MinChildkeyTake` (r:1 w:0) /// Proof: `SubtensorModule::MinChildkeyTake` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) /// Storage: `SubtensorModule::MinChildkeyTakePerSubnet` (r:1 w:0) @@ -3189,9 +3242,9 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `1033` // Estimated: `4498` - // Minimum execution time: 52_527_000 picoseconds. - Weight::from_parts(53_870_000, 4498) - .saturating_add(RocksDbWeight::get().reads(7_u64)) + // Minimum execution time: 52_698_000 picoseconds. + Weight::from_parts(53_880_000, 4498) + .saturating_add(RocksDbWeight::get().reads(8_u64)) .saturating_add(RocksDbWeight::get().writes(2_u64)) } /// Storage: `SubtensorModule::ColdkeySwapAnnouncements` (r:1 w:1) @@ -3206,8 +3259,8 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `694` // Estimated: `4159` - // Minimum execution time: 45_574_000 picoseconds. - Weight::from_parts(47_248_000, 4159) + // Minimum execution time: 43_054_000 picoseconds. + Weight::from_parts(43_986_000, 4159) .saturating_add(RocksDbWeight::get().reads(4_u64)) .saturating_add(RocksDbWeight::get().writes(3_u64)) } @@ -3219,6 +3272,8 @@ impl WeightInfo for () { /// Proof: `SubtensorModule::Owner` (`max_values`: None, `max_size`: None, mode: `Measured`) /// Storage: `SubtensorModule::IdentitiesV2` (r:2 w:0) /// Proof: `SubtensorModule::IdentitiesV2` (`max_values`: None, `max_size`: None, mode: `Measured`) + /// Storage: `SubtensorModule::AccountFlags` (r:2 w:2) + /// Proof: `SubtensorModule::AccountFlags` (`max_values`: None, `max_size`: None, mode: `Measured`) /// Storage: `SubtensorModule::NetworksAdded` (r:3 w:0) /// Proof: `SubtensorModule::NetworksAdded` (`max_values`: None, `max_size`: None, mode: `Measured`) /// Storage: `SubtensorModule::SubnetOwner` (r:2 w:0) @@ -3249,16 +3304,14 @@ impl WeightInfo for () { /// Proof: `SubtensorModule::DecayingLock` (`max_values`: None, `max_size`: None, mode: `Measured`) /// Storage: `System::Account` (r:2 w:2) /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(104), added: 2579, mode: `MaxEncodedLen`) - /// Storage: `SubtensorModule::LastRateLimitedBlock` (r:0 w:1) - /// Proof: `SubtensorModule::LastRateLimitedBlock` (`max_values`: None, `max_size`: None, mode: `Measured`) fn swap_coldkey_announced() -> Weight { // Proof Size summary in bytes: // Measured: `2110` // Estimated: `13000` - // Minimum execution time: 284_379_000 picoseconds. - Weight::from_parts(288_156_000, 13000) - .saturating_add(RocksDbWeight::get().reads(37_u64)) - .saturating_add(RocksDbWeight::get().writes(15_u64)) + // Minimum execution time: 297_881_000 picoseconds. + Weight::from_parts(302_117_000, 13000) + .saturating_add(RocksDbWeight::get().reads(39_u64)) + .saturating_add(RocksDbWeight::get().writes(16_u64)) } /// Storage: `System::Account` (r:2 w:2) /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(104), added: 2579, mode: `MaxEncodedLen`) @@ -3270,6 +3323,8 @@ impl WeightInfo for () { /// Proof: `SubtensorModule::Owner` (`max_values`: None, `max_size`: None, mode: `Measured`) /// Storage: `SubtensorModule::IdentitiesV2` (r:2 w:2) /// Proof: `SubtensorModule::IdentitiesV2` (`max_values`: None, `max_size`: None, mode: `Measured`) + /// Storage: `SubtensorModule::AccountFlags` (r:2 w:2) + /// Proof: `SubtensorModule::AccountFlags` (`max_values`: None, `max_size`: None, mode: `Measured`) /// Storage: `SubtensorModule::NetworksAdded` (r:3 w:0) /// Proof: `SubtensorModule::NetworksAdded` (`max_values`: None, `max_size`: None, mode: `Measured`) /// Storage: `SubtensorModule::SubnetOwner` (r:2 w:0) @@ -3302,16 +3357,14 @@ impl WeightInfo for () { /// Proof: `SubtensorModule::ColdkeySwapAnnouncements` (`max_values`: None, `max_size`: None, mode: `Measured`) /// Storage: `SubtensorModule::ColdkeySwapDisputes` (r:0 w:1) /// Proof: `SubtensorModule::ColdkeySwapDisputes` (`max_values`: None, `max_size`: None, mode: `Measured`) - /// Storage: `SubtensorModule::LastRateLimitedBlock` (r:0 w:1) - /// Proof: `SubtensorModule::LastRateLimitedBlock` (`max_values`: None, `max_size`: None, mode: `Measured`) fn swap_coldkey() -> Weight { // Proof Size summary in bytes: // Measured: `2166` // Estimated: `13056` - // Minimum execution time: 308_323_000 picoseconds. - Weight::from_parts(312_301_000, 13056) - .saturating_add(RocksDbWeight::get().reads(37_u64)) - .saturating_add(RocksDbWeight::get().writes(19_u64)) + // Minimum execution time: 319_452_000 picoseconds. + Weight::from_parts(322_968_000, 13056) + .saturating_add(RocksDbWeight::get().reads(39_u64)) + .saturating_add(RocksDbWeight::get().writes(20_u64)) } /// Storage: `SubtensorModule::ColdkeySwapAnnouncements` (r:1 w:0) /// Proof: `SubtensorModule::ColdkeySwapAnnouncements` (`max_values`: None, `max_size`: None, mode: `Measured`) @@ -3321,8 +3374,8 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `665` // Estimated: `4130` - // Minimum execution time: 22_442_000 picoseconds. - Weight::from_parts(23_293_000, 4130) + // Minimum execution time: 20_440_000 picoseconds. + Weight::from_parts(21_041_000, 4130) .saturating_add(RocksDbWeight::get().reads(2_u64)) .saturating_add(RocksDbWeight::get().writes(1_u64)) } @@ -3334,8 +3387,8 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `613` // Estimated: `4078` - // Minimum execution time: 18_695_000 picoseconds. - Weight::from_parts(19_496_000, 4078) + // Minimum execution time: 16_515_000 picoseconds. + Weight::from_parts(17_406_000, 4078) .saturating_add(RocksDbWeight::get().reads(2_u64)) .saturating_add(RocksDbWeight::get().writes(1_u64)) } @@ -3347,10 +3400,12 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 8_335_000 picoseconds. - Weight::from_parts(8_857_000, 0) + // Minimum execution time: 6_790_000 picoseconds. + Weight::from_parts(7_260_000, 0) .saturating_add(RocksDbWeight::get().writes(2_u64)) } + /// Storage: `SubtensorModule::NetworksAdded` (r:1 w:0) + /// Proof: `SubtensorModule::NetworksAdded` (`max_values`: None, `max_size`: None, mode: `Measured`) /// Storage: `SubtensorModule::CommitRevealWeightsEnabled` (r:1 w:0) /// Proof: `SubtensorModule::CommitRevealWeightsEnabled` (`max_values`: None, `max_size`: None, mode: `Measured`) /// Storage: `SubtensorModule::WeightCommits` (r:1 w:1) @@ -3361,8 +3416,6 @@ impl WeightInfo for () { /// Proof: `SubtensorModule::Tempo` (`max_values`: None, `max_size`: None, mode: `Measured`) /// Storage: `SubtensorModule::RevealPeriodEpochs` (r:1 w:0) /// Proof: `SubtensorModule::RevealPeriodEpochs` (`max_values`: None, `max_size`: None, mode: `Measured`) - /// Storage: `SubtensorModule::NetworksAdded` (r:1 w:0) - /// Proof: `SubtensorModule::NetworksAdded` (`max_values`: None, `max_size`: None, mode: `Measured`) /// Storage: `SubtensorModule::MechanismCountCurrent` (r:1 w:0) /// Proof: `SubtensorModule::MechanismCountCurrent` (`max_values`: None, `max_size`: None, mode: `Measured`) /// Storage: `SubtensorModule::SubnetworkN` (r:1 w:0) @@ -3393,8 +3446,8 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `2155` // Estimated: `8095` - // Minimum execution time: 412_226_000 picoseconds. - Weight::from_parts(421_363_000, 8095) + // Minimum execution time: 418_649_000 picoseconds. + Weight::from_parts(432_099_000, 8095) .saturating_add(RocksDbWeight::get().reads(19_u64)) .saturating_add(RocksDbWeight::get().writes(2_u64)) } @@ -3428,8 +3481,8 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `1754` // Estimated: `5219` - // Minimum execution time: 169_976_000 picoseconds. - Weight::from_parts(172_139_000, 5219) + // Minimum execution time: 172_415_000 picoseconds. + Weight::from_parts(174_608_000, 5219) .saturating_add(RocksDbWeight::get().reads(13_u64)) .saturating_add(RocksDbWeight::get().writes(6_u64)) } @@ -3461,8 +3514,8 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `1754` // Estimated: `5219` - // Minimum execution time: 166_359_000 picoseconds. - Weight::from_parts(168_964_000, 5219) + // Minimum execution time: 167_768_000 picoseconds. + Weight::from_parts(170_042_000, 5219) .saturating_add(RocksDbWeight::get().reads(12_u64)) .saturating_add(RocksDbWeight::get().writes(4_u64)) } @@ -3482,23 +3535,23 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `1118` // Estimated: `4583` - // Minimum execution time: 38_482_000 picoseconds. - Weight::from_parts(39_454_000, 4583) + // Minimum execution time: 36_744_000 picoseconds. + Weight::from_parts(38_227_000, 4583) .saturating_add(RocksDbWeight::get().reads(5_u64)) .saturating_add(RocksDbWeight::get().writes(2_u64)) } /// Storage: `SubtensorModule::SubnetMechanism` (r:1 w:0) /// Proof: `SubtensorModule::SubnetMechanism` (`max_values`: None, `max_size`: None, mode: `Measured`) + /// Storage: `SubtensorModule::SubnetTAO` (r:1 w:1) + /// Proof: `SubtensorModule::SubnetTAO` (`max_values`: None, `max_size`: None, mode: `Measured`) /// Storage: `SubtensorModule::SubnetAlphaIn` (r:1 w:1) /// Proof: `SubtensorModule::SubnetAlphaIn` (`max_values`: None, `max_size`: None, mode: `Measured`) + /// Storage: `Swap::FeeRate` (r:1 w:0) + /// Proof: `Swap::FeeRate` (`max_values`: None, `max_size`: Some(12), added: 2487, mode: `MaxEncodedLen`) /// Storage: `Swap::PalSwapInitialized` (r:1 w:0) /// Proof: `Swap::PalSwapInitialized` (`max_values`: None, `max_size`: Some(11), added: 2486, mode: `MaxEncodedLen`) - /// Storage: `SubtensorModule::SubnetTAO` (r:1 w:1) - /// Proof: `SubtensorModule::SubnetTAO` (`max_values`: None, `max_size`: None, mode: `Measured`) /// Storage: `Swap::SwapBalancer` (r:1 w:0) /// Proof: `Swap::SwapBalancer` (`max_values`: None, `max_size`: Some(18), added: 2493, mode: `MaxEncodedLen`) - /// Storage: `Swap::FeeRate` (r:1 w:0) - /// Proof: `Swap::FeeRate` (`max_values`: None, `max_size`: Some(12), added: 2487, mode: `MaxEncodedLen`) /// Storage: `SubtensorModule::NetworksAdded` (r:1 w:0) /// Proof: `SubtensorModule::NetworksAdded` (`max_values`: None, `max_size`: None, mode: `Measured`) /// Storage: `SubtensorModule::SubtokenEnabled` (r:1 w:0) @@ -3553,8 +3606,8 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `2226` // Estimated: `8727` - // Minimum execution time: 829_031_000 picoseconds. - Weight::from_parts(853_005_000, 8727) + // Minimum execution time: 876_848_000 picoseconds. + Weight::from_parts(895_756_000, 8727) .saturating_add(RocksDbWeight::get().reads(32_u64)) .saturating_add(RocksDbWeight::get().writes(16_u64)) } @@ -3590,8 +3643,8 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `1979` // Estimated: `7919` - // Minimum execution time: 214_819_000 picoseconds. - Weight::from_parts(216_061_000, 7919) + // Minimum execution time: 217_903_000 picoseconds. + Weight::from_parts(222_279_000, 7919) .saturating_add(RocksDbWeight::get().reads(19_u64)) .saturating_add(RocksDbWeight::get().writes(7_u64)) } @@ -3607,20 +3660,20 @@ impl WeightInfo for () { /// Proof: `SubtensorModule::TotalHotkeyShares` (`max_values`: None, `max_size`: None, mode: `Measured`) /// Storage: `SubtensorModule::TotalHotkeySharesV2` (r:1 w:1) /// Proof: `SubtensorModule::TotalHotkeySharesV2` (`max_values`: None, `max_size`: None, mode: `Measured`) - /// Storage: `SubtensorModule::NetworksAdded` (r:3 w:0) - /// Proof: `SubtensorModule::NetworksAdded` (`max_values`: None, `max_size`: None, mode: `Measured`) /// Storage: `SubtensorModule::SubnetMechanism` (r:2 w:0) /// Proof: `SubtensorModule::SubnetMechanism` (`max_values`: None, `max_size`: None, mode: `Measured`) + /// Storage: `SubtensorModule::SubnetAlphaIn` (r:1 w:1) + /// Proof: `SubtensorModule::SubnetAlphaIn` (`max_values`: None, `max_size`: None, mode: `Measured`) + /// Storage: `SubtensorModule::NetworksAdded` (r:3 w:0) + /// Proof: `SubtensorModule::NetworksAdded` (`max_values`: None, `max_size`: None, mode: `Measured`) /// Storage: `SubtensorModule::SubnetTAO` (r:1 w:1) /// Proof: `SubtensorModule::SubnetTAO` (`max_values`: None, `max_size`: None, mode: `Measured`) + /// Storage: `Swap::FeeRate` (r:1 w:0) + /// Proof: `Swap::FeeRate` (`max_values`: None, `max_size`: Some(12), added: 2487, mode: `MaxEncodedLen`) /// Storage: `Swap::PalSwapInitialized` (r:1 w:0) /// Proof: `Swap::PalSwapInitialized` (`max_values`: None, `max_size`: Some(11), added: 2486, mode: `MaxEncodedLen`) - /// Storage: `SubtensorModule::SubnetAlphaIn` (r:1 w:1) - /// Proof: `SubtensorModule::SubnetAlphaIn` (`max_values`: None, `max_size`: None, mode: `Measured`) /// Storage: `Swap::SwapBalancer` (r:1 w:0) /// Proof: `Swap::SwapBalancer` (`max_values`: None, `max_size`: Some(18), added: 2493, mode: `MaxEncodedLen`) - /// Storage: `Swap::FeeRate` (r:1 w:0) - /// Proof: `Swap::FeeRate` (`max_values`: None, `max_size`: Some(12), added: 2487, mode: `MaxEncodedLen`) /// Storage: `SubtensorModule::Owner` (r:1 w:0) /// Proof: `SubtensorModule::Owner` (`max_values`: None, `max_size`: None, mode: `Measured`) /// Storage: `SubtensorModule::StakingHotkeys` (r:1 w:0) @@ -3647,23 +3700,23 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `2142` // Estimated: `10557` - // Minimum execution time: 544_281_000 picoseconds. - Weight::from_parts(569_198_000, 10557) + // Minimum execution time: 565_106_000 picoseconds. + Weight::from_parts(581_160_000, 10557) .saturating_add(RocksDbWeight::get().reads(28_u64)) .saturating_add(RocksDbWeight::get().writes(13_u64)) } /// Storage: `SubtensorModule::SubnetMechanism` (r:2 w:0) /// Proof: `SubtensorModule::SubnetMechanism` (`max_values`: None, `max_size`: None, mode: `Measured`) + /// Storage: `SubtensorModule::SubnetAlphaIn` (r:1 w:1) + /// Proof: `SubtensorModule::SubnetAlphaIn` (`max_values`: None, `max_size`: None, mode: `Measured`) /// Storage: `SubtensorModule::SubnetTAO` (r:1 w:1) /// Proof: `SubtensorModule::SubnetTAO` (`max_values`: None, `max_size`: None, mode: `Measured`) + /// Storage: `Swap::FeeRate` (r:1 w:0) + /// Proof: `Swap::FeeRate` (`max_values`: None, `max_size`: Some(12), added: 2487, mode: `MaxEncodedLen`) /// Storage: `Swap::PalSwapInitialized` (r:1 w:0) /// Proof: `Swap::PalSwapInitialized` (`max_values`: None, `max_size`: Some(11), added: 2486, mode: `MaxEncodedLen`) - /// Storage: `SubtensorModule::SubnetAlphaIn` (r:1 w:1) - /// Proof: `SubtensorModule::SubnetAlphaIn` (`max_values`: None, `max_size`: None, mode: `Measured`) /// Storage: `Swap::SwapBalancer` (r:1 w:0) /// Proof: `Swap::SwapBalancer` (`max_values`: None, `max_size`: Some(18), added: 2493, mode: `MaxEncodedLen`) - /// Storage: `Swap::FeeRate` (r:1 w:0) - /// Proof: `Swap::FeeRate` (`max_values`: None, `max_size`: Some(12), added: 2487, mode: `MaxEncodedLen`) /// Storage: `SubtensorModule::NetworksAdded` (r:3 w:0) /// Proof: `SubtensorModule::NetworksAdded` (`max_values`: None, `max_size`: None, mode: `Measured`) /// Storage: `SubtensorModule::Alpha` (r:1 w:0) @@ -3702,8 +3755,8 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `2176` // Estimated: `10591` - // Minimum execution time: 717_343_000 picoseconds. - Weight::from_parts(740_696_000, 10591) + // Minimum execution time: 751_993_000 picoseconds. + Weight::from_parts(769_279_000, 10591) .saturating_add(RocksDbWeight::get().reads(27_u64)) .saturating_add(RocksDbWeight::get().writes(13_u64)) } @@ -3719,16 +3772,16 @@ impl WeightInfo for () { /// Proof: `SubtensorModule::TotalHotkeySharesV2` (`max_values`: None, `max_size`: None, mode: `Measured`) /// Storage: `SubtensorModule::SubnetMechanism` (r:2 w:0) /// Proof: `SubtensorModule::SubnetMechanism` (`max_values`: None, `max_size`: None, mode: `Measured`) + /// Storage: `SubtensorModule::SubnetAlphaIn` (r:2 w:2) + /// Proof: `SubtensorModule::SubnetAlphaIn` (`max_values`: None, `max_size`: None, mode: `Measured`) /// Storage: `SubtensorModule::SubnetTAO` (r:2 w:2) /// Proof: `SubtensorModule::SubnetTAO` (`max_values`: None, `max_size`: None, mode: `Measured`) + /// Storage: `Swap::FeeRate` (r:1 w:0) + /// Proof: `Swap::FeeRate` (`max_values`: None, `max_size`: Some(12), added: 2487, mode: `MaxEncodedLen`) /// Storage: `Swap::PalSwapInitialized` (r:1 w:0) /// Proof: `Swap::PalSwapInitialized` (`max_values`: None, `max_size`: Some(11), added: 2486, mode: `MaxEncodedLen`) - /// Storage: `SubtensorModule::SubnetAlphaIn` (r:2 w:2) - /// Proof: `SubtensorModule::SubnetAlphaIn` (`max_values`: None, `max_size`: None, mode: `Measured`) /// Storage: `Swap::SwapBalancer` (r:1 w:0) /// Proof: `Swap::SwapBalancer` (`max_values`: None, `max_size`: Some(18), added: 2493, mode: `MaxEncodedLen`) - /// Storage: `Swap::FeeRate` (r:1 w:0) - /// Proof: `Swap::FeeRate` (`max_values`: None, `max_size`: Some(12), added: 2487, mode: `MaxEncodedLen`) /// Storage: `SubtensorModule::NetworksAdded` (r:2 w:0) /// Proof: `SubtensorModule::NetworksAdded` (`max_values`: None, `max_size`: None, mode: `Measured`) /// Storage: `SubtensorModule::SubtokenEnabled` (r:2 w:0) @@ -3775,8 +3828,8 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `2662` // Estimated: `11077` - // Minimum execution time: 921_853_000 picoseconds. - Weight::from_parts(944_515_000, 11077) + // Minimum execution time: 962_414_000 picoseconds. + Weight::from_parts(980_201_000, 11077) .saturating_add(RocksDbWeight::get().reads(47_u64)) .saturating_add(RocksDbWeight::get().writes(24_u64)) } @@ -3802,8 +3855,6 @@ impl WeightInfo for () { /// Proof: `SubtensorModule::StakingHotkeys` (`max_values`: None, `max_size`: None, mode: `Measured`) /// Storage: `SubtensorModule::Lock` (r:1 w:0) /// Proof: `SubtensorModule::Lock` (`max_values`: None, `max_size`: None, mode: `Measured`) - /// Storage: `SubtensorModule::AccountFlags` (r:1 w:0) - /// Proof: `SubtensorModule::AccountFlags` (`max_values`: None, `max_size`: None, mode: `Measured`) /// Storage: `SubtensorModule::SubnetMechanism` (r:1 w:0) /// Proof: `SubtensorModule::SubnetMechanism` (`max_values`: None, `max_size`: None, mode: `Measured`) /// Storage: `SubtensorModule::SubnetAlphaIn` (r:1 w:0) @@ -3816,11 +3867,11 @@ impl WeightInfo for () { /// Proof: `SubtensorModule::LastColdkeyHotkeyStakeBlock` (`max_values`: None, `max_size`: None, mode: `Measured`) fn transfer_stake() -> Weight { // Proof Size summary in bytes: - // Measured: `2054` - // Estimated: `7994` - // Minimum execution time: 254_636_000 picoseconds. - Weight::from_parts(258_541_000, 7994) - .saturating_add(RocksDbWeight::get().reads(19_u64)) + // Measured: `1988` + // Estimated: `7928` + // Minimum execution time: 250_521_000 picoseconds. + Weight::from_parts(252_694_000, 7928) + .saturating_add(RocksDbWeight::get().reads(18_u64)) .saturating_add(RocksDbWeight::get().writes(6_u64)) } /// Storage: `SubtensorModule::Alpha` (r:2 w:0) @@ -3843,14 +3894,14 @@ impl WeightInfo for () { /// Proof: `SubtensorModule::SubnetMechanism` (`max_values`: None, `max_size`: None, mode: `Measured`) /// Storage: `SubtensorModule::SubnetTAO` (r:2 w:2) /// Proof: `SubtensorModule::SubnetTAO` (`max_values`: None, `max_size`: None, mode: `Measured`) - /// Storage: `Swap::PalSwapInitialized` (r:1 w:0) - /// Proof: `Swap::PalSwapInitialized` (`max_values`: None, `max_size`: Some(11), added: 2486, mode: `MaxEncodedLen`) + /// Storage: `Swap::FeeRate` (r:1 w:0) + /// Proof: `Swap::FeeRate` (`max_values`: None, `max_size`: Some(12), added: 2487, mode: `MaxEncodedLen`) /// Storage: `SubtensorModule::SubnetAlphaIn` (r:2 w:2) /// Proof: `SubtensorModule::SubnetAlphaIn` (`max_values`: None, `max_size`: None, mode: `Measured`) + /// Storage: `Swap::PalSwapInitialized` (r:1 w:0) + /// Proof: `Swap::PalSwapInitialized` (`max_values`: None, `max_size`: Some(11), added: 2486, mode: `MaxEncodedLen`) /// Storage: `Swap::SwapBalancer` (r:1 w:0) /// Proof: `Swap::SwapBalancer` (`max_values`: None, `max_size`: Some(18), added: 2493, mode: `MaxEncodedLen`) - /// Storage: `Swap::FeeRate` (r:1 w:0) - /// Proof: `Swap::FeeRate` (`max_values`: None, `max_size`: Some(12), added: 2487, mode: `MaxEncodedLen`) /// Storage: `SubtensorModule::StakingHotkeys` (r:1 w:0) /// Proof: `SubtensorModule::StakingHotkeys` (`max_values`: None, `max_size`: None, mode: `Measured`) /// Storage: `SubtensorModule::Lock` (r:3 w:1) @@ -3891,8 +3942,8 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `2505` // Estimated: `10920` - // Minimum execution time: 717_052_000 picoseconds. - Weight::from_parts(742_019_000, 10920) + // Minimum execution time: 736_000_000 picoseconds. + Weight::from_parts(755_388_000, 10920) .saturating_add(RocksDbWeight::get().reads(47_u64)) .saturating_add(RocksDbWeight::get().writes(24_u64)) } @@ -3930,8 +3981,8 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `1300` // Estimated: `4765` - // Minimum execution time: 144_628_000 picoseconds. - Weight::from_parts(147_224_000, 4765) + // Minimum execution time: 144_774_000 picoseconds. + Weight::from_parts(146_056_000, 4765) .saturating_add(RocksDbWeight::get().reads(15_u64)) .saturating_add(RocksDbWeight::get().writes(2_u64)) } @@ -3971,8 +4022,8 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `1455` // Estimated: `7395` - // Minimum execution time: 100_516_000 picoseconds. - Weight::from_parts(103_092_000, 7395) + // Minimum execution time: 98_445_000 picoseconds. + Weight::from_parts(100_549_000, 7395) .saturating_add(RocksDbWeight::get().reads(16_u64)) .saturating_add(RocksDbWeight::get().writes(2_u64)) } @@ -3988,8 +4039,8 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `830` // Estimated: `4295` - // Minimum execution time: 29_324_000 picoseconds. - Weight::from_parts(29_895_000, 4295) + // Minimum execution time: 26_119_000 picoseconds. + Weight::from_parts(27_451_000, 4295) .saturating_add(RocksDbWeight::get().reads(3_u64)) .saturating_add(RocksDbWeight::get().writes(2_u64)) } @@ -4007,8 +4058,8 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `923` // Estimated: `4388` - // Minimum execution time: 36_257_000 picoseconds. - Weight::from_parts(37_710_000, 4388) + // Minimum execution time: 33_670_000 picoseconds. + Weight::from_parts(34_631_000, 4388) .saturating_add(RocksDbWeight::get().reads(5_u64)) .saturating_add(RocksDbWeight::get().writes(2_u64)) } @@ -4024,6 +4075,10 @@ impl WeightInfo for () { /// Proof: `SubtensorModule::SubnetLimit` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) /// Storage: `SubtensorModule::NetworksAdded` (r:3 w:1) /// Proof: `SubtensorModule::NetworksAdded` (`max_values`: None, `max_size`: None, mode: `Measured`) + /// Storage: `SubtensorModule::DissolveCleanupQueue` (r:1 w:0) + /// Proof: `SubtensorModule::DissolveCleanupQueue` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + /// Storage: `SubtensorModule::NetworkRegistrationQueue` (r:1 w:0) + /// Proof: `SubtensorModule::NetworkRegistrationQueue` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) /// Storage: `SubtensorModule::NetworkLastLockCost` (r:1 w:1) /// Proof: `SubtensorModule::NetworkLastLockCost` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) /// Storage: `SubtensorModule::NetworkMinLockCost` (r:1 w:0) @@ -4032,8 +4087,6 @@ impl WeightInfo for () { /// Proof: `SubtensorModule::NetworkLockReductionInterval` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) /// Storage: `SubtensorModule::TotalIssuance` (r:1 w:0) /// Proof: `SubtensorModule::TotalIssuance` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) - /// Storage: `SubtensorModule::BlockEmission` (r:1 w:0) - /// Proof: `SubtensorModule::BlockEmission` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) /// Storage: `SubtensorModule::SubnetMechanism` (r:1 w:1) /// Proof: `SubtensorModule::SubnetMechanism` (`max_values`: None, `max_size`: None, mode: `Measured`) /// Storage: `SubtensorModule::SubnetAlphaIn` (r:1 w:1) @@ -4128,11 +4181,13 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `1468` // Estimated: `9883` - // Minimum execution time: 270_502_000 picoseconds. - Weight::from_parts(275_041_000, 9883) - .saturating_add(RocksDbWeight::get().reads(39_u64)) + // Minimum execution time: 281_587_000 picoseconds. + Weight::from_parts(287_927_000, 9883) + .saturating_add(RocksDbWeight::get().reads(40_u64)) .saturating_add(RocksDbWeight::get().writes(47_u64)) } + /// Storage: `SubtensorModule::NetworksAdded` (r:1 w:0) + /// Proof: `SubtensorModule::NetworksAdded` (`max_values`: None, `max_size`: None, mode: `Measured`) /// Storage: `SubtensorModule::Uids` (r:1 w:0) /// Proof: `SubtensorModule::Uids` (`max_values`: None, `max_size`: None, mode: `Measured`) /// Storage: `SubtensorModule::Axons` (r:1 w:1) @@ -4141,11 +4196,11 @@ impl WeightInfo for () { /// Proof: `SubtensorModule::ServingRateLimit` (`max_values`: None, `max_size`: None, mode: `Measured`) fn serve_axon_tls() -> Weight { // Proof Size summary in bytes: - // Measured: `684` - // Estimated: `4149` - // Minimum execution time: 29_786_000 picoseconds. - Weight::from_parts(30_617_000, 4149) - .saturating_add(RocksDbWeight::get().reads(3_u64)) + // Measured: `799` + // Estimated: `4264` + // Minimum execution time: 34_581_000 picoseconds. + Weight::from_parts(36_053_000, 4264) + .saturating_add(RocksDbWeight::get().reads(4_u64)) .saturating_add(RocksDbWeight::get().writes(1_u64)) } /// Storage: `SubtensorModule::OwnedHotkeys` (r:1 w:0) @@ -4158,30 +4213,28 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `889` // Estimated: `6829` - // Minimum execution time: 31_168_000 picoseconds. - Weight::from_parts(32_040_000, 6829) + // Minimum execution time: 29_433_000 picoseconds. + Weight::from_parts(30_685_000, 6829) .saturating_add(RocksDbWeight::get().reads(3_u64)) .saturating_add(RocksDbWeight::get().writes(1_u64)) } + /// Storage: `SubtensorModule::NetworksAdded` (r:1 w:0) + /// Proof: `SubtensorModule::NetworksAdded` (`max_values`: None, `max_size`: None, mode: `Measured`) /// Storage: `SubtensorModule::SubnetOwner` (r:1 w:0) /// Proof: `SubtensorModule::SubnetOwner` (`max_values`: None, `max_size`: None, mode: `Measured`) /// Storage: `SubtensorModule::SubnetIdentitiesV3` (r:0 w:1) /// Proof: `SubtensorModule::SubnetIdentitiesV3` (`max_values`: None, `max_size`: None, mode: `Measured`) fn set_subnet_identity() -> Weight { // Proof Size summary in bytes: - // Measured: `595` - // Estimated: `4060` - // Minimum execution time: 17_122_000 picoseconds. - Weight::from_parts(17_513_000, 4060) - .saturating_add(RocksDbWeight::get().reads(1_u64)) + // Measured: `738` + // Estimated: `4203` + // Minimum execution time: 20_360_000 picoseconds. + Weight::from_parts(21_382_000, 4203) + .saturating_add(RocksDbWeight::get().reads(2_u64)) .saturating_add(RocksDbWeight::get().writes(1_u64)) } /// Storage: `SubtensorModule::Owner` (r:2 w:2) /// Proof: `SubtensorModule::Owner` (`max_values`: None, `max_size`: None, mode: `Measured`) - /// Storage: `SubtensorModule::LastRateLimitedBlock` (r:4 w:7) - /// Proof: `SubtensorModule::LastRateLimitedBlock` (`max_values`: None, `max_size`: None, mode: `Measured`) - /// Storage: `SubtensorModule::TxRateLimit` (r:1 w:0) - /// Proof: `SubtensorModule::TxRateLimit` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) /// Storage: `SubtensorModule::IsNetworkMember` (r:6 w:10) /// Proof: `SubtensorModule::IsNetworkMember` (`max_values`: None, `max_size`: None, mode: `Measured`) /// Storage: `SubtensorModule::RootClaimable` (r:2 w:2) @@ -4190,6 +4243,8 @@ impl WeightInfo for () { /// Proof: `SubtensorModule::TotalHotkeyAlpha` (`max_values`: None, `max_size`: None, mode: `Measured`) /// Storage: `SubtensorModule::RootClaimed` (r:1 w:0) /// Proof: `SubtensorModule::RootClaimed` (`max_values`: None, `max_size`: None, mode: `Measured`) + /// Storage: `SubtensorModule::LastRateLimitedBlock` (r:2 w:5) + /// Proof: `SubtensorModule::LastRateLimitedBlock` (`max_values`: None, `max_size`: None, mode: `Measured`) /// Storage: `SubtensorModule::NetworksAdded` (r:6 w:0) /// Proof: `SubtensorModule::NetworksAdded` (`max_values`: None, `max_size`: None, mode: `Measured`) /// Storage: `SubtensorModule::ChildKeys` (r:10 w:10) @@ -4254,10 +4309,10 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `3172` // Estimated: `28912` - // Minimum execution time: 1_227_100_000 picoseconds. - Weight::from_parts(1_242_038_000, 28912) - .saturating_add(RocksDbWeight::get().reads(182_u64)) - .saturating_add(RocksDbWeight::get().writes(99_u64)) + // Minimum execution time: 1_242_920_000 picoseconds. + Weight::from_parts(1_257_572_000, 28912) + .saturating_add(RocksDbWeight::get().reads(179_u64)) + .saturating_add(RocksDbWeight::get().writes(97_u64)) } /// Storage: `SubtensorModule::Owner` (r:1 w:1) /// Proof: `SubtensorModule::Owner` (`max_values`: None, `max_size`: None, mode: `Measured`) @@ -4269,8 +4324,8 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `818` // Estimated: `4283` - // Minimum execution time: 24_556_000 picoseconds. - Weight::from_parts(25_337_000, 4283) + // Minimum execution time: 23_505_000 picoseconds. + Weight::from_parts(23_936_000, 4283) .saturating_add(RocksDbWeight::get().reads(3_u64)) .saturating_add(RocksDbWeight::get().writes(3_u64)) } @@ -4284,8 +4339,8 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `774` // Estimated: `9189` - // Minimum execution time: 25_918_000 picoseconds. - Weight::from_parts(26_830_000, 9189) + // Minimum execution time: 24_726_000 picoseconds. + Weight::from_parts(25_568_000, 9189) .saturating_add(RocksDbWeight::get().reads(6_u64)) } /// Storage: `SubtensorModule::Owner` (r:1 w:0) @@ -4308,14 +4363,14 @@ impl WeightInfo for () { /// Proof: `SubtensorModule::SubnetMechanism` (`max_values`: None, `max_size`: None, mode: `Measured`) /// Storage: `SubtensorModule::SubnetTAO` (r:2 w:2) /// Proof: `SubtensorModule::SubnetTAO` (`max_values`: None, `max_size`: None, mode: `Measured`) - /// Storage: `Swap::PalSwapInitialized` (r:1 w:0) - /// Proof: `Swap::PalSwapInitialized` (`max_values`: None, `max_size`: Some(11), added: 2486, mode: `MaxEncodedLen`) + /// Storage: `Swap::FeeRate` (r:1 w:0) + /// Proof: `Swap::FeeRate` (`max_values`: None, `max_size`: Some(12), added: 2487, mode: `MaxEncodedLen`) /// Storage: `SubtensorModule::SubnetAlphaIn` (r:2 w:2) /// Proof: `SubtensorModule::SubnetAlphaIn` (`max_values`: None, `max_size`: None, mode: `Measured`) + /// Storage: `Swap::PalSwapInitialized` (r:1 w:0) + /// Proof: `Swap::PalSwapInitialized` (`max_values`: None, `max_size`: Some(11), added: 2486, mode: `MaxEncodedLen`) /// Storage: `Swap::SwapBalancer` (r:1 w:0) /// Proof: `Swap::SwapBalancer` (`max_values`: None, `max_size`: Some(18), added: 2493, mode: `MaxEncodedLen`) - /// Storage: `Swap::FeeRate` (r:1 w:0) - /// Proof: `Swap::FeeRate` (`max_values`: None, `max_size`: Some(12), added: 2487, mode: `MaxEncodedLen`) /// Storage: `SubtensorModule::StakingHotkeys` (r:1 w:0) /// Proof: `SubtensorModule::StakingHotkeys` (`max_values`: None, `max_size`: None, mode: `Measured`) /// Storage: `SubtensorModule::Lock` (r:2 w:0) @@ -4346,11 +4401,13 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `2235` // Estimated: `11306` - // Minimum execution time: 674_042_000 picoseconds. - Weight::from_parts(700_873_000, 11306) + // Minimum execution time: 698_403_000 picoseconds. + Weight::from_parts(712_865_000, 11306) .saturating_add(RocksDbWeight::get().reads(43_u64)) .saturating_add(RocksDbWeight::get().writes(25_u64)) } + /// Storage: `SubtensorModule::NetworksAdded` (r:3 w:0) + /// Proof: `SubtensorModule::NetworksAdded` (`max_values`: None, `max_size`: None, mode: `Measured`) /// Storage: `SubtensorModule::Alpha` (r:1 w:0) /// Proof: `SubtensorModule::Alpha` (`max_values`: None, `max_size`: None, mode: `Measured`) /// Storage: `SubtensorModule::AlphaV2` (r:1 w:1) @@ -4363,18 +4420,16 @@ impl WeightInfo for () { /// Proof: `SubtensorModule::TotalHotkeySharesV2` (`max_values`: None, `max_size`: None, mode: `Measured`) /// Storage: `SubtensorModule::SubnetMechanism` (r:2 w:0) /// Proof: `SubtensorModule::SubnetMechanism` (`max_values`: None, `max_size`: None, mode: `Measured`) + /// Storage: `SubtensorModule::SubnetAlphaIn` (r:1 w:1) + /// Proof: `SubtensorModule::SubnetAlphaIn` (`max_values`: None, `max_size`: None, mode: `Measured`) /// Storage: `SubtensorModule::SubnetTAO` (r:1 w:1) /// Proof: `SubtensorModule::SubnetTAO` (`max_values`: None, `max_size`: None, mode: `Measured`) + /// Storage: `Swap::FeeRate` (r:1 w:0) + /// Proof: `Swap::FeeRate` (`max_values`: None, `max_size`: Some(12), added: 2487, mode: `MaxEncodedLen`) /// Storage: `Swap::PalSwapInitialized` (r:1 w:0) /// Proof: `Swap::PalSwapInitialized` (`max_values`: None, `max_size`: Some(11), added: 2486, mode: `MaxEncodedLen`) - /// Storage: `SubtensorModule::SubnetAlphaIn` (r:1 w:1) - /// Proof: `SubtensorModule::SubnetAlphaIn` (`max_values`: None, `max_size`: None, mode: `Measured`) /// Storage: `Swap::SwapBalancer` (r:1 w:0) /// Proof: `Swap::SwapBalancer` (`max_values`: None, `max_size`: Some(18), added: 2493, mode: `MaxEncodedLen`) - /// Storage: `Swap::FeeRate` (r:1 w:0) - /// Proof: `Swap::FeeRate` (`max_values`: None, `max_size`: Some(12), added: 2487, mode: `MaxEncodedLen`) - /// Storage: `SubtensorModule::NetworksAdded` (r:3 w:0) - /// Proof: `SubtensorModule::NetworksAdded` (`max_values`: None, `max_size`: None, mode: `Measured`) /// Storage: `SubtensorModule::Owner` (r:1 w:0) /// Proof: `SubtensorModule::Owner` (`max_values`: None, `max_size`: None, mode: `Measured`) /// Storage: `SubtensorModule::StakingHotkeys` (r:1 w:0) @@ -4401,8 +4456,8 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `2176` // Estimated: `10591` - // Minimum execution time: 731_620_000 picoseconds. - Weight::from_parts(754_292_000, 10591) + // Minimum execution time: 774_816_000 picoseconds. + Weight::from_parts(792_172_000, 10591) .saturating_add(RocksDbWeight::get().reads(27_u64)) .saturating_add(RocksDbWeight::get().writes(13_u64)) } @@ -4426,6 +4481,10 @@ impl WeightInfo for () { /// Proof: `SubtensorModule::SubnetLimit` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) /// Storage: `SubtensorModule::NetworksAdded` (r:3 w:1) /// Proof: `SubtensorModule::NetworksAdded` (`max_values`: None, `max_size`: None, mode: `Measured`) + /// Storage: `SubtensorModule::DissolveCleanupQueue` (r:1 w:0) + /// Proof: `SubtensorModule::DissolveCleanupQueue` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + /// Storage: `SubtensorModule::NetworkRegistrationQueue` (r:1 w:0) + /// Proof: `SubtensorModule::NetworkRegistrationQueue` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) /// Storage: `SubtensorModule::NetworkLastLockCost` (r:1 w:1) /// Proof: `SubtensorModule::NetworkLastLockCost` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) /// Storage: `SubtensorModule::NetworkMinLockCost` (r:1 w:0) @@ -4434,8 +4493,6 @@ impl WeightInfo for () { /// Proof: `SubtensorModule::NetworkLockReductionInterval` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) /// Storage: `SubtensorModule::TotalIssuance` (r:1 w:0) /// Proof: `SubtensorModule::TotalIssuance` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) - /// Storage: `SubtensorModule::BlockEmission` (r:1 w:0) - /// Proof: `SubtensorModule::BlockEmission` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) /// Storage: `SubtensorModule::SubnetMechanism` (r:1 w:1) /// Proof: `SubtensorModule::SubnetMechanism` (`max_values`: None, `max_size`: None, mode: `Measured`) /// Storage: `SubtensorModule::SubnetAlphaIn` (r:1 w:1) @@ -4541,11 +4598,11 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `1835 + k * (44 ±0)` // Estimated: `10256 + k * (2579 ±0)` - // Minimum execution time: 475_774_000 picoseconds. - Weight::from_parts(309_598_986, 10256) - // Standard Error: 26_969 - .saturating_add(Weight::from_parts(46_027_026, 0).saturating_mul(k.into())) - .saturating_add(RocksDbWeight::get().reads(49_u64)) + // Minimum execution time: 484_517_000 picoseconds. + Weight::from_parts(309_951_401, 10256) + // Standard Error: 64_287 + .saturating_add(Weight::from_parts(47_050_119, 0).saturating_mul(k.into())) + .saturating_add(RocksDbWeight::get().reads(50_u64)) .saturating_add(RocksDbWeight::get().reads((2_u64).saturating_mul(k.into()))) .saturating_add(RocksDbWeight::get().writes(53_u64)) .saturating_add(RocksDbWeight::get().writes((2_u64).saturating_mul(k.into()))) @@ -4574,10 +4631,10 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `1501 + k * (53 ±0)` // Estimated: `6148 + k * (2529 ±0)` - // Minimum execution time: 127_077_000 picoseconds. - Weight::from_parts(146_402_779, 6148) - // Standard Error: 3_377 - .saturating_add(Weight::from_parts(1_566_532, 0).saturating_mul(k.into())) + // Minimum execution time: 90_524_000 picoseconds. + Weight::from_parts(77_908_272, 6148) + // Standard Error: 5_176 + .saturating_add(Weight::from_parts(1_663_793, 0).saturating_mul(k.into())) .saturating_add(RocksDbWeight::get().reads(4_u64)) .saturating_add(RocksDbWeight::get().reads((1_u64).saturating_mul(k.into()))) .saturating_add(RocksDbWeight::get().writes(7_u64)) @@ -4586,15 +4643,17 @@ impl WeightInfo for () { } /// Storage: `SubtensorModule::SubnetOwner` (r:1 w:0) /// Proof: `SubtensorModule::SubnetOwner` (`max_values`: None, `max_size`: None, mode: `Measured`) + /// Storage: `SubtensorModule::NetworksAdded` (r:1 w:0) + /// Proof: `SubtensorModule::NetworksAdded` (`max_values`: None, `max_size`: None, mode: `Measured`) /// Storage: `SubtensorModule::TokenSymbol` (r:3 w:1) /// Proof: `SubtensorModule::TokenSymbol` (`max_values`: None, `max_size`: None, mode: `Measured`) fn update_symbol() -> Weight { // Proof Size summary in bytes: - // Measured: `659` - // Estimated: `9074` - // Minimum execution time: 27_300_000 picoseconds. - Weight::from_parts(28_313_000, 9074) - .saturating_add(RocksDbWeight::get().reads(4_u64)) + // Measured: `762` + // Estimated: `9177` + // Minimum execution time: 30_125_000 picoseconds. + Weight::from_parts(31_456_000, 9177) + .saturating_add(RocksDbWeight::get().reads(5_u64)) .saturating_add(RocksDbWeight::get().writes(1_u64)) } /// Storage: `SubtensorModule::NetworksAdded` (r:1 w:0) @@ -4629,8 +4688,8 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `1248` // Estimated: `4713` - // Minimum execution time: 85_228_000 picoseconds. - Weight::from_parts(86_951_000, 4713) + // Minimum execution time: 82_703_000 picoseconds. + Weight::from_parts(84_635_000, 4713) .saturating_add(RocksDbWeight::get().reads(14_u64)) .saturating_add(RocksDbWeight::get().writes(2_u64)) } @@ -4646,8 +4705,8 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `809` // Estimated: `4274` - // Minimum execution time: 32_871_000 picoseconds. - Weight::from_parts(33_853_000, 4274) + // Minimum execution time: 31_046_000 picoseconds. + Weight::from_parts(32_027_000, 4274) .saturating_add(RocksDbWeight::get().reads(4_u64)) .saturating_add(RocksDbWeight::get().writes(2_u64)) } @@ -4663,8 +4722,8 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `476` // Estimated: `3941` - // Minimum execution time: 17_463_000 picoseconds. - Weight::from_parts(18_283_000, 3941) + // Minimum execution time: 15_783_000 picoseconds. + Weight::from_parts(16_364_000, 3941) .saturating_add(RocksDbWeight::get().reads(2_u64)) .saturating_add(RocksDbWeight::get().writes(4_u64)) } @@ -4676,6 +4735,8 @@ impl WeightInfo for () { /// Proof: `SubtensorModule::RootClaimType` (`max_values`: None, `max_size`: None, mode: `Measured`) /// Storage: `SubtensorModule::RootClaimable` (r:1 w:0) /// Proof: `SubtensorModule::RootClaimable` (`max_values`: None, `max_size`: None, mode: `Measured`) + /// Storage: `SubtensorModule::DissolveCleanupQueue` (r:1 w:0) + /// Proof: `SubtensorModule::DissolveCleanupQueue` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) /// Storage: `SubtensorModule::Alpha` (r:2 w:0) /// Proof: `SubtensorModule::Alpha` (`max_values`: None, `max_size`: None, mode: `Measured`) /// Storage: `SubtensorModule::AlphaV2` (r:2 w:1) @@ -4692,11 +4753,11 @@ impl WeightInfo for () { /// Proof: `SubtensorModule::RootClaimableThreshold` (`max_values`: None, `max_size`: None, mode: `Measured`) fn claim_root() -> Weight { // Proof Size summary in bytes: - // Measured: `1935` - // Estimated: `7875` - // Minimum execution time: 136_093_000 picoseconds. - Weight::from_parts(138_999_000, 7875) - .saturating_add(RocksDbWeight::get().reads(16_u64)) + // Measured: `1969` + // Estimated: `7909` + // Minimum execution time: 139_606_000 picoseconds. + Weight::from_parts(141_219_000, 7909) + .saturating_add(RocksDbWeight::get().reads(17_u64)) .saturating_add(RocksDbWeight::get().writes(4_u64)) } /// Storage: `SubtensorModule::NumRootClaim` (r:0 w:1) @@ -4705,18 +4766,21 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 2_545_000 picoseconds. - Weight::from_parts(2_735_000, 0) + // Minimum execution time: 2_033_000 picoseconds. + Weight::from_parts(2_144_000, 0) .saturating_add(RocksDbWeight::get().writes(1_u64)) } + /// Storage: `SubtensorModule::NetworksAdded` (r:1 w:0) + /// Proof: `SubtensorModule::NetworksAdded` (`max_values`: None, `max_size`: None, mode: `Measured`) /// Storage: `SubtensorModule::RootClaimableThreshold` (r:0 w:1) /// Proof: `SubtensorModule::RootClaimableThreshold` (`max_values`: None, `max_size`: None, mode: `Measured`) fn sudo_set_root_claim_threshold() -> Weight { // Proof Size summary in bytes: - // Measured: `0` - // Estimated: `0` - // Minimum execution time: 5_229_000 picoseconds. - Weight::from_parts(5_681_000, 0) + // Measured: `652` + // Estimated: `4117` + // Minimum execution time: 13_540_000 picoseconds. + Weight::from_parts(14_041_000, 4117) + .saturating_add(RocksDbWeight::get().reads(1_u64)) .saturating_add(RocksDbWeight::get().writes(1_u64)) } /// Storage: `SubtensorModule::Owner` (r:1 w:0) @@ -4729,25 +4793,25 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `899` // Estimated: `4364` - // Minimum execution time: 26_870_000 picoseconds. - Weight::from_parts(28_023_000, 4364) + // Minimum execution time: 24_416_000 picoseconds. + Weight::from_parts(26_088_000, 4364) .saturating_add(RocksDbWeight::get().reads(2_u64)) .saturating_add(RocksDbWeight::get().writes(1_u64)) } + /// Storage: `SubtensorModule::NetworksAdded` (r:1 w:0) + /// Proof: `SubtensorModule::NetworksAdded` (`max_values`: None, `max_size`: None, mode: `Measured`) /// Storage: `SubtensorModule::SubnetMechanism` (r:1 w:0) /// Proof: `SubtensorModule::SubnetMechanism` (`max_values`: None, `max_size`: None, mode: `Measured`) + /// Storage: `SubtensorModule::SubnetTAO` (r:1 w:1) + /// Proof: `SubtensorModule::SubnetTAO` (`max_values`: None, `max_size`: None, mode: `Measured`) /// Storage: `SubtensorModule::SubnetAlphaIn` (r:1 w:1) /// Proof: `SubtensorModule::SubnetAlphaIn` (`max_values`: None, `max_size`: None, mode: `Measured`) + /// Storage: `Swap::FeeRate` (r:1 w:0) + /// Proof: `Swap::FeeRate` (`max_values`: None, `max_size`: Some(12), added: 2487, mode: `MaxEncodedLen`) /// Storage: `Swap::PalSwapInitialized` (r:1 w:0) /// Proof: `Swap::PalSwapInitialized` (`max_values`: None, `max_size`: Some(11), added: 2486, mode: `MaxEncodedLen`) - /// Storage: `SubtensorModule::SubnetTAO` (r:1 w:1) - /// Proof: `SubtensorModule::SubnetTAO` (`max_values`: None, `max_size`: None, mode: `Measured`) /// Storage: `Swap::SwapBalancer` (r:1 w:0) /// Proof: `Swap::SwapBalancer` (`max_values`: None, `max_size`: Some(18), added: 2493, mode: `MaxEncodedLen`) - /// Storage: `Swap::FeeRate` (r:1 w:0) - /// Proof: `Swap::FeeRate` (`max_values`: None, `max_size`: Some(12), added: 2487, mode: `MaxEncodedLen`) - /// Storage: `SubtensorModule::NetworksAdded` (r:1 w:0) - /// Proof: `SubtensorModule::NetworksAdded` (`max_values`: None, `max_size`: None, mode: `Measured`) /// Storage: `SubtensorModule::SubtokenEnabled` (r:1 w:0) /// Proof: `SubtensorModule::SubtokenEnabled` (`max_values`: None, `max_size`: None, mode: `Measured`) /// Storage: `System::Account` (r:3 w:3) @@ -4802,21 +4866,42 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `2229` // Estimated: `8727` - // Minimum execution time: 945_077_000 picoseconds. - Weight::from_parts(967_308_000, 8727) + // Minimum execution time: 1_028_031_000 picoseconds. + Weight::from_parts(1_034_060_000, 8727) .saturating_add(RocksDbWeight::get().reads(33_u64)) .saturating_add(RocksDbWeight::get().writes(17_u64)) } + /// Storage: `SubtensorModule::NetworksAdded` (r:1 w:1) + /// Proof: `SubtensorModule::NetworksAdded` (`max_values`: None, `max_size`: None, mode: `Measured`) + /// Storage: `SubtensorModule::DissolveCleanupQueue` (r:1 w:1) + /// Proof: `SubtensorModule::DissolveCleanupQueue` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + /// Storage: `SubtensorModule::TotalNetworks` (r:1 w:1) + /// Proof: `SubtensorModule::TotalNetworks` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + /// Storage: `SubtensorModule::TotalStake` (r:1 w:1) + /// Proof: `SubtensorModule::TotalStake` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + /// Storage: `SubtensorModule::SubnetTAO` (r:1 w:0) + /// Proof: `SubtensorModule::SubnetTAO` (`max_values`: None, `max_size`: None, mode: `Measured`) + fn dissolve_network() -> Weight { + // Proof Size summary in bytes: + // Measured: `842` + // Estimated: `4307` + // Minimum execution time: 31_587_000 picoseconds. + Weight::from_parts(32_539_000, 4307) + .saturating_add(RocksDbWeight::get().reads(5_u64)) + .saturating_add(RocksDbWeight::get().writes(4_u64)) + } /// Storage: `SubtensorModule::PendingChildKeyCooldown` (r:0 w:1) /// Proof: `SubtensorModule::PendingChildKeyCooldown` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) fn set_pending_childkey_cooldown() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 2_585_000 picoseconds. - Weight::from_parts(2_805_000, 0) + // Minimum execution time: 1_973_000 picoseconds. + Weight::from_parts(2_133_000, 0) .saturating_add(RocksDbWeight::get().writes(1_u64)) } + /// Storage: `SubtensorModule::NetworksAdded` (r:1 w:0) + /// Proof: `SubtensorModule::NetworksAdded` (`max_values`: None, `max_size`: None, mode: `Measured`) /// Storage: `SubtensorModule::Owner` (r:1 w:0) /// Proof: `SubtensorModule::Owner` (`max_values`: None, `max_size`: None, mode: `Measured`) /// Storage: `SubtensorModule::StakingHotkeys` (r:1 w:0) @@ -4853,13 +4938,15 @@ impl WeightInfo for () { /// Proof: `SubtensorModule::LockingColdkeys` (`max_values`: None, `max_size`: None, mode: `Measured`) fn lock_stake() -> Weight { // Proof Size summary in bytes: - // Measured: `1715` - // Estimated: `7655` - // Minimum execution time: 114_744_000 picoseconds. - Weight::from_parts(115_995_000, 7655) - .saturating_add(RocksDbWeight::get().reads(17_u64)) + // Measured: `1830` + // Estimated: `7770` + // Minimum execution time: 121_600_000 picoseconds. + Weight::from_parts(123_313_000, 7770) + .saturating_add(RocksDbWeight::get().reads(18_u64)) .saturating_add(RocksDbWeight::get().writes(3_u64)) } + /// Storage: `SubtensorModule::NetworksAdded` (r:1 w:0) + /// Proof: `SubtensorModule::NetworksAdded` (`max_values`: None, `max_size`: None, mode: `Measured`) /// Storage: `SubtensorModule::Owner` (r:2 w:0) /// Proof: `SubtensorModule::Owner` (`max_values`: None, `max_size`: None, mode: `Measured`) /// Storage: `SubtensorModule::Lock` (r:2 w:2) @@ -4884,13 +4971,15 @@ impl WeightInfo for () { /// Proof: `SubtensorModule::LockingColdkeys` (`max_values`: None, `max_size`: None, mode: `Measured`) fn move_lock() -> Weight { // Proof Size summary in bytes: - // Measured: `1399` - // Estimated: `7339` - // Minimum execution time: 147_815_000 picoseconds. - Weight::from_parts(149_819_000, 7339) - .saturating_add(RocksDbWeight::get().reads(14_u64)) + // Measured: `1515` + // Estimated: `7455` + // Minimum execution time: 162_571_000 picoseconds. + Weight::from_parts(164_834_000, 7455) + .saturating_add(RocksDbWeight::get().reads(15_u64)) .saturating_add(RocksDbWeight::get().writes(6_u64)) } + /// Storage: `SubtensorModule::NetworksAdded` (r:1 w:0) + /// Proof: `SubtensorModule::NetworksAdded` (`max_values`: None, `max_size`: None, mode: `Measured`) /// Storage: `SubtensorModule::Owner` (r:1 w:0) /// Proof: `SubtensorModule::Owner` (`max_values`: None, `max_size`: None, mode: `Measured`) /// Storage: `SubtensorModule::Uids` (r:1 w:0) @@ -4899,11 +4988,11 @@ impl WeightInfo for () { /// Proof: `SubtensorModule::AssociatedEvmAddress` (`max_values`: None, `max_size`: None, mode: `Measured`) fn associate_evm_key() -> Weight { // Proof Size summary in bytes: - // Measured: `950` - // Estimated: `4415` - // Minimum execution time: 665_186_000 picoseconds. - Weight::from_parts(684_242_000, 4415) - .saturating_add(RocksDbWeight::get().reads(3_u64)) + // Measured: `1065` + // Estimated: `4530` + // Minimum execution time: 707_727_000 picoseconds. + Weight::from_parts(724_452_000, 4530) + .saturating_add(RocksDbWeight::get().reads(4_u64)) .saturating_add(RocksDbWeight::get().writes(1_u64)) } /// Storage: `SubtensorModule::SubnetOwner` (r:1 w:0) @@ -4922,8 +5011,8 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `975` // Estimated: `4440` - // Minimum execution time: 45_394_000 picoseconds. - Weight::from_parts(46_407_000, 4440) + // Minimum execution time: 43_113_000 picoseconds. + Weight::from_parts(44_276_000, 4440) .saturating_add(RocksDbWeight::get().reads(6_u64)) .saturating_add(RocksDbWeight::get().writes(3_u64)) } @@ -4947,8 +5036,8 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `899` // Estimated: `4364` - // Minimum execution time: 38_362_000 picoseconds. - Weight::from_parts(39_193_000, 4364) + // Minimum execution time: 36_284_000 picoseconds. + Weight::from_parts(37_776_000, 4364) .saturating_add(RocksDbWeight::get().reads(7_u64)) .saturating_add(RocksDbWeight::get().writes(2_u64)) } @@ -4972,8 +5061,8 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `982` // Estimated: `4447` - // Minimum execution time: 41_588_000 picoseconds. - Weight::from_parts(42_539_000, 4447) + // Minimum execution time: 39_639_000 picoseconds. + Weight::from_parts(41_111_000, 4447) .saturating_add(RocksDbWeight::get().reads(8_u64)) .saturating_add(RocksDbWeight::get().writes(2_u64)) } @@ -4985,8 +5074,8 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `733` // Estimated: `4198` - // Minimum execution time: 16_832_000 picoseconds. - Weight::from_parts(17_583_000, 4198) + // Minimum execution time: 16_324_000 picoseconds. + Weight::from_parts(16_735_000, 4198) .saturating_add(RocksDbWeight::get().reads(2_u64)) } /// Storage: `SubtensorModule::SubnetOwnerHotkey` (r:1 w:0) @@ -5013,8 +5102,8 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `1731` // Estimated: `7671` - // Minimum execution time: 52_046_000 picoseconds. - Weight::from_parts(52_958_000, 7671) + // Minimum execution time: 50_485_000 picoseconds. + Weight::from_parts(52_107_000, 7671) .saturating_add(RocksDbWeight::get().reads(11_u64)) } /// Storage: `SubtensorModule::CommitRevealWeightsEnabled` (r:1 w:0) @@ -5035,8 +5124,8 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `1019` // Estimated: `4484` - // Minimum execution time: 34_995_000 picoseconds. - Weight::from_parts(35_376_000, 4484) + // Minimum execution time: 33_470_000 picoseconds. + Weight::from_parts(34_380_000, 4484) .saturating_add(RocksDbWeight::get().reads(7_u64)) } /// Storage: `SubtensorModule::MinDelegateTake` (r:1 w:0) @@ -5049,8 +5138,8 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `721` // Estimated: `4186` - // Minimum execution time: 14_998_000 picoseconds. - Weight::from_parts(15_378_000, 4186) + // Minimum execution time: 14_392_000 picoseconds. + Weight::from_parts(14_972_000, 4186) .saturating_add(RocksDbWeight::get().reads(3_u64)) } /// Storage: `SubtensorModule::Uids` (r:1 w:0) @@ -5063,8 +5152,8 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `647` // Estimated: `4112` - // Minimum execution time: 18_775_000 picoseconds. - Weight::from_parts(19_035_000, 4112) + // Minimum execution time: 17_977_000 picoseconds. + Weight::from_parts(18_548_000, 4112) .saturating_add(RocksDbWeight::get().reads(3_u64)) } /// Storage: `SubtensorModule::Uids` (r:1 w:0) @@ -5075,8 +5164,8 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `652` // Estimated: `4117` - // Minimum execution time: 15_018_000 picoseconds. - Weight::from_parts(15_448_000, 4117) + // Minimum execution time: 14_441_000 picoseconds. + Weight::from_parts(14_792_000, 4117) .saturating_add(RocksDbWeight::get().reads(2_u64)) } } diff --git a/pallets/swap/src/pallet/balancer.rs b/pallets/swap/src/pallet/balancer.rs index 2ffd04fdba..244e4ff9eb 100644 --- a/pallets/swap/src/pallet/balancer.rs +++ b/pallets/swap/src/pallet/balancer.rs @@ -149,7 +149,7 @@ impl Balancer { let w1: u128 = self.get_base_weight().deconstruct() as u128; let w2: u128 = self.get_quote_weight().deconstruct() as u128; - let precision = 1024; + let precision = 256; let x_safe = SafeInt::from(x); let w1_safe = SafeInt::from(w1); let w2_safe = SafeInt::from(w2); @@ -187,8 +187,13 @@ impl Balancer { if let Some(result_safe_int) = maybe_result_safe_int && let Some(result_u64) = result_safe_int.to_u64() { - return U64F64::saturating_from_num(result_u64) + let result = U64F64::saturating_from_num(result_u64) .safe_div(U64F64::saturating_from_num(ACCURACY)); + return if dx >= 0 { + result.min(U64F64::from_num(1)) + } else { + result + }; } U64F64::saturating_from_num(0) } @@ -791,6 +796,12 @@ mod tests { let dy1 = y_fixed * (one - e1); let dy2 = y_fixed * (one - e2); + if dx > x.saturating_mul(1_000) { + assert!(e1 <= one); + assert!(e2 <= one); + return; + } + let w1 = perquintill_to_f64(bal.get_base_weight()); let w2 = perquintill_to_f64(bal.get_quote_weight()); let e1_expected = (x as f64 / (x as f64 + dx as f64)).powf(w1 / w2); @@ -928,6 +939,7 @@ mod tests { } } + // cargo test --package pallet-subtensor-swap --lib -- pallet::balancer::tests::test_exp_quote_fuzzy --include-ignored --exact --nocapture #[ignore] #[test] fn test_exp_quote_fuzzy() { @@ -993,7 +1005,7 @@ mod tests { // Print progress let done = counter.fetch_add(1, Ordering::Relaxed) + 1; - if done % 100_000_000 == 0 { + if done % 10_000_000 == 0 { let progress = done as f64 / ITERATIONS as f64 * 100.0; // Replace with println for real-time progress log::debug!("progress = {progress:.4}%"); diff --git a/pallets/swap/src/pallet/impls.rs b/pallets/swap/src/pallet/impls.rs index c3e0b2f1d3..966bb4721b 100644 --- a/pallets/swap/src/pallet/impls.rs +++ b/pallets/swap/src/pallet/impls.rs @@ -3,10 +3,11 @@ use frame_support::{ ensure, pallet_prelude::{DispatchError, Zero}, traits::Get, + weights::WeightMeter, }; use safe_math::*; use sp_arithmetic::Perquintill; -use sp_runtime::{DispatchResult, traits::AccountIdConversion}; +use sp_runtime::traits::AccountIdConversion; use substrate_fixed::types::U64F64; use subtensor_runtime_common::{AlphaBalance, NetUid, SubnetInfo, TaoBalance, Token, TokenReserve}; use subtensor_swap_interface::{ @@ -14,7 +15,7 @@ use subtensor_swap_interface::{ }; use super::pallet::*; -use super::swap_step::{BasicSwapStep, SwapStep}; +use super::swap_step::{BasicSwapStep, MAX_SWAP_INPUT_RESERVE_MULTIPLIER, SwapStep}; use crate::{pallet::Balancer, pallet::balancer::BalancerError}; impl Pallet { @@ -154,8 +155,13 @@ impl Pallet { transactional::with_transaction(|| { let reserve = Order::ReserveOut::reserve(netuid.into()); - let result = Self::swap_inner::(netuid, order, limit_price, drop_fees) - .map_err(Into::into); + let result = Self::ensure_swap_input_within_reserve_limit::( + netuid, + order.amount(), + drop_fees, + ) + .and_then(|_| Self::swap_inner::(netuid, order, limit_price, drop_fees)) + .map_err(Into::into); if simulate || result.is_err() { // Simulation only @@ -177,6 +183,23 @@ impl Pallet { }) } + fn ensure_swap_input_within_reserve_limit( + netuid: NetUid, + amount: Order::PaidIn, + drop_fees: bool, + ) -> Result<(), Error> + where + Order: OrderT, + { + let fee = Self::calculate_fee_amount(netuid, amount, drop_fees); + let net_amount = amount.saturating_sub(fee); + let input_reserve = Order::ReserveIn::reserve(netuid); + let max_amount = input_reserve.saturating_mul(MAX_SWAP_INPUT_RESERVE_MULTIPLIER.into()); + + ensure!(net_amount <= max_amount, Error::::SwapInputTooLarge); + Ok(()) + } + fn swap_inner( netuid: NetUid, order: Order, @@ -264,8 +287,13 @@ impl Pallet { } /// Clear **protocol-owned** liquidity and wipe all swap state for `netuid`. - pub fn do_clear_protocol_liquidity(netuid: NetUid) -> DispatchResult { - // 1) Force-close protocol liquidity, burning proceeds. + pub fn do_clear_protocol_liquidity(netuid: NetUid, weight_meter: &mut WeightMeter) -> bool { + let clear_weight = T::DbWeight::get().reads_writes(4, 5); + if !weight_meter.can_consume(clear_weight) { + return false; + } + weight_meter.consume(clear_weight); + // / 1) Force-close protocol liquidity, burning proceeds. let burned_tao = T::TaoReserve::reserve(netuid.into()); let burned_alpha = T::AlphaReserve::reserve(netuid.into()); @@ -281,7 +309,7 @@ impl Pallet { "clear_protocol_liquidity: netuid={netuid:?}, protocol_burned: τ={burned_tao:?}, α={burned_alpha:?}; state cleared" ); - Ok(()) + true } } @@ -386,6 +414,9 @@ impl SwapHandler for Pallet { Self::max_price_inner() } + fn clear_protocol_liquidity(netuid: NetUid, weight_meter: &mut WeightMeter) -> bool { + Self::do_clear_protocol_liquidity(netuid, weight_meter) + } fn adjust_protocol_liquidity( netuid: NetUid, tao_delta: TaoBalance, @@ -394,10 +425,6 @@ impl SwapHandler for Pallet { Self::adjust_protocol_liquidity(netuid, tao_delta, alpha_delta) } - fn clear_protocol_liquidity(netuid: NetUid) -> DispatchResult { - Self::do_clear_protocol_liquidity(netuid) - } - fn init_swap(netuid: NetUid, maybe_price: Option) { Self::maybe_initialize_palswap(netuid, maybe_price).unwrap_or_default(); } diff --git a/pallets/swap/src/pallet/mod.rs b/pallets/swap/src/pallet/mod.rs index 1d2fd07c59..b9044d4e82 100644 --- a/pallets/swap/src/pallet/mod.rs +++ b/pallets/swap/src/pallet/mod.rs @@ -165,6 +165,9 @@ mod pallet { /// Swap reserves are too imbalanced ReservesOutOfBalance, + /// Swap input is too large relative to input-side liquidity + SwapInputTooLarge, + /// The extrinsic is deprecated Deprecated, } diff --git a/pallets/swap/src/pallet/swap_step.rs b/pallets/swap/src/pallet/swap_step.rs index 7f10bff65a..3d4d516d1f 100644 --- a/pallets/swap/src/pallet/swap_step.rs +++ b/pallets/swap/src/pallet/swap_step.rs @@ -7,6 +7,8 @@ use subtensor_runtime_common::{AlphaBalance, NetUid, TaoBalance, Token, TokenRes use super::pallet::*; +pub(crate) const MAX_SWAP_INPUT_RESERVE_MULTIPLIER: u64 = 1_000; + /// A struct representing a single swap step with all its parameters and state pub(crate) struct BasicSwapStep where diff --git a/pallets/swap/src/pallet/tests.rs b/pallets/swap/src/pallet/tests.rs index b1071294d3..9eeb5f43a8 100644 --- a/pallets/swap/src/pallet/tests.rs +++ b/pallets/swap/src/pallet/tests.rs @@ -6,12 +6,13 @@ )] use approx::assert_abs_diff_eq; +use frame_support::weights::WeightMeter; use frame_support::{assert_noop, assert_ok}; use sp_arithmetic::Perquintill; use sp_runtime::DispatchError; use substrate_fixed::types::U64F64; use subtensor_runtime_common::{NetUid, Token}; -use subtensor_swap_interface::Order as OrderT; +use subtensor_swap_interface::{Order as OrderT, SwapHandler}; use super::*; use crate::mock::*; @@ -721,6 +722,78 @@ fn test_rollback_works() { }) } +#[test] +fn test_swap_rejects_input_over_1000x_input_reserve() { + new_test_ext().execute_with(|| { + let netuid = NetUid::from(1); + TaoReserve::set_mock_reserve(netuid, TaoBalance::from(1_000)); + AlphaReserve::set_mock_reserve(netuid, AlphaBalance::from(1_000)); + + assert_noop!( + Pallet::::do_swap( + netuid, + GetTaoForAlpha::with_amount(1_000_001), + get_min_price(), + true, + false, + ), + Error::::SwapInputTooLarge + ); + assert_noop!( + Pallet::::do_swap( + netuid, + GetAlphaForTao::with_amount(1_000_001), + get_max_price(), + true, + false, + ), + Error::::SwapInputTooLarge + ); + }); +} + +#[test] +fn test_sim_swap_rejects_input_over_1000x_input_reserve() { + new_test_ext().execute_with(|| { + let netuid = NetUid::from(1); + TaoReserve::set_mock_reserve(netuid, TaoBalance::from(1_000)); + AlphaReserve::set_mock_reserve(netuid, AlphaBalance::from(1_000)); + + assert_noop!( + Pallet::::sim_swap(netuid, GetTaoForAlpha::with_amount(1_001_000)), + Error::::SwapInputTooLarge + ); + assert_noop!( + Pallet::::sim_swap(netuid, GetAlphaForTao::with_amount(1_001_000)), + Error::::SwapInputTooLarge + ); + }); +} + +#[test] +fn test_swap_allows_input_at_1000x_input_reserve() { + new_test_ext().execute_with(|| { + let netuid = NetUid::from(1); + TaoReserve::set_mock_reserve(netuid, TaoBalance::from(1_000)); + AlphaReserve::set_mock_reserve(netuid, AlphaBalance::from(1_000)); + + assert_ok!(Pallet::::do_swap( + netuid, + GetTaoForAlpha::with_amount(1_000_000), + get_min_price(), + true, + true, + )); + assert_ok!(Pallet::::do_swap( + netuid, + GetAlphaForTao::with_amount(1_000_000), + get_max_price(), + true, + true, + )); + }); +} + #[allow(dead_code)] fn bbox(t: U64F64, a: U64F64, b: U64F64) -> U64F64 { if t < a { @@ -757,7 +830,10 @@ fn test_liquidate_pal_simple_ok_and_clears() { assert!(PalSwapInitialized::::get(netuid)); // ACT - assert_ok!(Pallet::::do_clear_protocol_liquidity(netuid)); + assert!(Pallet::::do_clear_protocol_liquidity( + netuid, + &mut WeightMeter::with_limit(Weight::from_parts(u64::MAX, u64::MAX)) + )); // All single-key maps should not have the key after liquidation assert!(!FeeRate::::contains_key(netuid)); @@ -781,7 +857,10 @@ fn test_clear_protocol_liquidity_green_path() { // --- Act --- // Green path: just clear protocol liquidity and wipe all V3 state. - assert_ok!(Pallet::::do_clear_protocol_liquidity(netuid)); + assert!(Pallet::::do_clear_protocol_liquidity( + netuid, + &mut WeightMeter::with_limit(Weight::from_parts(u64::MAX, u64::MAX)) + )); // Flags assert!(!PalSwapInitialized::::contains_key(netuid)); @@ -790,7 +869,10 @@ fn test_clear_protocol_liquidity_green_path() { assert!(!FeeRate::::contains_key(netuid)); // --- And it's idempotent --- - assert_ok!(Pallet::::do_clear_protocol_liquidity(netuid)); + assert!(Pallet::::do_clear_protocol_liquidity( + netuid, + &mut WeightMeter::with_limit(Weight::from_parts(u64::MAX, u64::MAX)) + )); assert!(!PalSwapInitialized::::contains_key(netuid)); }); } diff --git a/pallets/transaction-fee/Cargo.toml b/pallets/transaction-fee/Cargo.toml index 22f89b5f4c..8b6c139c52 100644 --- a/pallets/transaction-fee/Cargo.toml +++ b/pallets/transaction-fee/Cargo.toml @@ -87,7 +87,7 @@ runtime-benchmarks = [ "pallet-evm/runtime-benchmarks", "pallet-grandpa/runtime-benchmarks", "pallet-preimage/runtime-benchmarks", - "pallet-scheduler/runtime-benchmarks", + "pallet-scheduler/runtime-benchmarks", "pallet-subtensor/runtime-benchmarks", "pallet-subtensor-swap/runtime-benchmarks", "subtensor-runtime-common/runtime-benchmarks", diff --git a/pallets/transaction-fee/src/tests/mock.rs b/pallets/transaction-fee/src/tests/mock.rs index 7d8508c5c0..98ff259495 100644 --- a/pallets/transaction-fee/src/tests/mock.rs +++ b/pallets/transaction-fee/src/tests/mock.rs @@ -451,7 +451,12 @@ impl PrivilegeCmp for OriginPrivilegeCmp { pub struct CommitmentsI; impl pallet_subtensor::CommitmentsInterface for CommitmentsI { - fn purge_netuid(_netuid: NetUid) {} + fn purge_netuid( + _netuid: NetUid, + _weight_meter: &mut frame_support::weights::WeightMeter, + ) -> bool { + true + } } parameter_types! { diff --git a/pallets/utility/src/weights.rs b/pallets/utility/src/weights.rs index 928aef0269..30d2417a6b 100644 --- a/pallets/utility/src/weights.rs +++ b/pallets/utility/src/weights.rs @@ -2,9 +2,9 @@ //! Autogenerated weights for `pallet_subtensor_utility` //! //! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 49.1.0 -//! DATE: 2026-06-23, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! DATE: 2026-07-06, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` //! WORST CASE MAP SIZE: `1000000` -//! HOSTNAME: `runnervm7b5n9`, CPU: `AMD EPYC 7763 64-Core Processor` +//! HOSTNAME: `runnervmkkn4f`, CPU: `AMD EPYC 9V74 80-Core Processor` //! WASM-EXECUTION: `Compiled`, CHAIN: `None`, DB CACHE: `1024` // Executed Command: @@ -22,7 +22,7 @@ // --no-storage-info // --no-min-squares // --no-median-slopes -// --output=/tmp/tmp.5J6YDU3hE3 +// --output=/tmp/tmp.laN3Zsn8Gx // --template=/home/runner/work/subtensor/subtensor/.maintain/frame-weight-template.hbs #![cfg_attr(rustfmt, rustfmt_skip)] @@ -57,10 +57,10 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `518` // Estimated: `3983` - // Minimum execution time: 4_638_000 picoseconds. - Weight::from_parts(19_446_696, 3983) - // Standard Error: 1_676 - .saturating_add(Weight::from_parts(5_450_264, 0).saturating_mul(c.into())) + // Minimum execution time: 3_906_000 picoseconds. + Weight::from_parts(18_484_592, 3983) + // Standard Error: 2_474 + .saturating_add(Weight::from_parts(5_537_660, 0).saturating_mul(c.into())) .saturating_add(T::DbWeight::get().reads(2_u64)) } /// Storage: `SafeMode::EnteredUntil` (r:1 w:0) @@ -71,8 +71,8 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `518` // Estimated: `3983` - // Minimum execution time: 14_878_000 picoseconds. - Weight::from_parts(15_378_000, 3983) + // Minimum execution time: 13_130_000 picoseconds. + Weight::from_parts(13_651_000, 3983) .saturating_add(T::DbWeight::get().reads(2_u64)) } /// Storage: `SafeMode::EnteredUntil` (r:1 w:0) @@ -84,18 +84,18 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `518` // Estimated: `3983` - // Minimum execution time: 4_588_000 picoseconds. - Weight::from_parts(13_428_230, 3983) - // Standard Error: 2_092 - .saturating_add(Weight::from_parts(5_663_250, 0).saturating_mul(c.into())) + // Minimum execution time: 3_785_000 picoseconds. + Weight::from_parts(14_854_793, 3983) + // Standard Error: 9_495 + .saturating_add(Weight::from_parts(5_797_212, 0).saturating_mul(c.into())) .saturating_add(T::DbWeight::get().reads(2_u64)) } fn dispatch_as() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 6_542_000 picoseconds. - Weight::from_parts(6_893_000, 0) + // Minimum execution time: 5_328_000 picoseconds. + Weight::from_parts(5_738_000, 0) } /// Storage: `SafeMode::EnteredUntil` (r:1 w:0) /// Proof: `SafeMode::EnteredUntil` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) @@ -106,18 +106,18 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `518` // Estimated: `3983` - // Minimum execution time: 4_699_000 picoseconds. - Weight::from_parts(12_134_867, 3983) - // Standard Error: 3_490 - .saturating_add(Weight::from_parts(5_477_293, 0).saturating_mul(c.into())) + // Minimum execution time: 3_835_000 picoseconds. + Weight::from_parts(6_263_661, 3983) + // Standard Error: 3_838 + .saturating_add(Weight::from_parts(5_580_740, 0).saturating_mul(c.into())) .saturating_add(T::DbWeight::get().reads(2_u64)) } fn dispatch_as_fallible() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 6_813_000 picoseconds. - Weight::from_parts(7_113_000, 0) + // Minimum execution time: 5_318_000 picoseconds. + Weight::from_parts(5_738_000, 0) } /// Storage: `SafeMode::EnteredUntil` (r:1 w:0) /// Proof: `SafeMode::EnteredUntil` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) @@ -127,8 +127,8 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `518` // Estimated: `3983` - // Minimum execution time: 21_140_000 picoseconds. - Weight::from_parts(21_831_000, 3983) + // Minimum execution time: 18_887_000 picoseconds. + Weight::from_parts(19_950_000, 3983) .saturating_add(T::DbWeight::get().reads(2_u64)) } } @@ -144,10 +144,10 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `518` // Estimated: `3983` - // Minimum execution time: 4_638_000 picoseconds. - Weight::from_parts(19_446_696, 3983) - // Standard Error: 1_676 - .saturating_add(Weight::from_parts(5_450_264, 0).saturating_mul(c.into())) + // Minimum execution time: 3_906_000 picoseconds. + Weight::from_parts(18_484_592, 3983) + // Standard Error: 2_474 + .saturating_add(Weight::from_parts(5_537_660, 0).saturating_mul(c.into())) .saturating_add(RocksDbWeight::get().reads(2_u64)) } /// Storage: `SafeMode::EnteredUntil` (r:1 w:0) @@ -158,8 +158,8 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `518` // Estimated: `3983` - // Minimum execution time: 14_878_000 picoseconds. - Weight::from_parts(15_378_000, 3983) + // Minimum execution time: 13_130_000 picoseconds. + Weight::from_parts(13_651_000, 3983) .saturating_add(RocksDbWeight::get().reads(2_u64)) } /// Storage: `SafeMode::EnteredUntil` (r:1 w:0) @@ -171,18 +171,18 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `518` // Estimated: `3983` - // Minimum execution time: 4_588_000 picoseconds. - Weight::from_parts(13_428_230, 3983) - // Standard Error: 2_092 - .saturating_add(Weight::from_parts(5_663_250, 0).saturating_mul(c.into())) + // Minimum execution time: 3_785_000 picoseconds. + Weight::from_parts(14_854_793, 3983) + // Standard Error: 9_495 + .saturating_add(Weight::from_parts(5_797_212, 0).saturating_mul(c.into())) .saturating_add(RocksDbWeight::get().reads(2_u64)) } fn dispatch_as() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 6_542_000 picoseconds. - Weight::from_parts(6_893_000, 0) + // Minimum execution time: 5_328_000 picoseconds. + Weight::from_parts(5_738_000, 0) } /// Storage: `SafeMode::EnteredUntil` (r:1 w:0) /// Proof: `SafeMode::EnteredUntil` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) @@ -193,18 +193,18 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `518` // Estimated: `3983` - // Minimum execution time: 4_699_000 picoseconds. - Weight::from_parts(12_134_867, 3983) - // Standard Error: 3_490 - .saturating_add(Weight::from_parts(5_477_293, 0).saturating_mul(c.into())) + // Minimum execution time: 3_835_000 picoseconds. + Weight::from_parts(6_263_661, 3983) + // Standard Error: 3_838 + .saturating_add(Weight::from_parts(5_580_740, 0).saturating_mul(c.into())) .saturating_add(RocksDbWeight::get().reads(2_u64)) } fn dispatch_as_fallible() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 6_813_000 picoseconds. - Weight::from_parts(7_113_000, 0) + // Minimum execution time: 5_318_000 picoseconds. + Weight::from_parts(5_738_000, 0) } /// Storage: `SafeMode::EnteredUntil` (r:1 w:0) /// Proof: `SafeMode::EnteredUntil` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) @@ -214,8 +214,8 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `518` // Estimated: `3983` - // Minimum execution time: 21_140_000 picoseconds. - Weight::from_parts(21_831_000, 3983) + // Minimum execution time: 18_887_000 picoseconds. + Weight::from_parts(19_950_000, 3983) .saturating_add(RocksDbWeight::get().reads(2_u64)) } } diff --git a/precompiles/Cargo.toml b/precompiles/Cargo.toml index dd5e20dfd0..d7df04dc14 100644 --- a/precompiles/Cargo.toml +++ b/precompiles/Cargo.toml @@ -2,10 +2,10 @@ name = "subtensor-precompiles" version = "0.1.0" edition.workspace = true -authors = ["Opentensor Foundation "] +authors = ["Opentensor Foundation "] homepage = "https://opentensor.ai/" publish = false -repository = "https://github.com/opentensor/subtensor/" +repository = "https://github.com/RaoFoundation/subtensor/" [package.metadata.docs.rs] targets = ["x86_64-unknown-linux-gnu"] diff --git a/precompiles/src/mock.rs b/precompiles/src/mock.rs index 2cd917b43c..e256761b2a 100644 --- a/precompiles/src/mock.rs +++ b/precompiles/src/mock.rs @@ -407,7 +407,12 @@ impl AuthorshipInfo for MockAuthorshipProvider { pub struct CommitmentsI; impl pallet_subtensor::CommitmentsInterface for CommitmentsI { - fn purge_netuid(_netuid: NetUid) {} + fn purge_netuid( + _netuid: NetUid, + _weight_meter: &mut frame_support::weights::WeightMeter, + ) -> bool { + true + } } impl pallet_subtensor::Config for Runtime { diff --git a/precompiles/src/solidity/ed25519Verify.sol b/precompiles/src/solidity/ed25519Verify.sol index 035feb4cc4..e422dc5250 100644 --- a/precompiles/src/solidity/ed25519Verify.sol +++ b/precompiles/src/solidity/ed25519Verify.sol @@ -6,7 +6,7 @@ address constant IED25519VERIFY_ADDRESS = 0x000000000000000000000000000000000000 interface IEd25519Verify { /** * @dev Verifies Ed25519 signature using provided message and public key. - * + * * @param message The 32-byte signature payload message. * @param publicKey 32-byte public key matching to private key used to sign the message. * @param r The Ed25519 signature commitment (first 32 bytes). diff --git a/precompiles/src/solidity/leasing.sol b/precompiles/src/solidity/leasing.sol index 184b832a10..1b9a406fac 100644 --- a/precompiles/src/solidity/leasing.sol +++ b/precompiles/src/solidity/leasing.sol @@ -12,7 +12,7 @@ interface ILeasing { /** * @dev Retrieves the contributor share for a given lease id and contributor. - * The share is returned as a tuple of two uint128 values, where the first value + * The share is returned as a tuple of two uint128 values, where the first value * is the integer part and the second value is the fractional part. * @param leaseId The id of the lease to get contributor share for. * @param contributor The contributor to get share for. diff --git a/precompiles/src/solidity/metagraph.sol b/precompiles/src/solidity/metagraph.sol index 3a19281a57..4018c2f170 100644 --- a/precompiles/src/solidity/metagraph.sol +++ b/precompiles/src/solidity/metagraph.sol @@ -12,7 +12,7 @@ struct AxonInfo { } interface IMetagraph { - + /** * @dev Returns the count of unique identifiers (UIDs) associated with a given network identifier (netuid). * @param netuid The network identifier for which to retrieve the UID count. diff --git a/precompiles/src/solidity/subnet.abi b/precompiles/src/solidity/subnet.abi index 60e8b49906..0fba532e54 100644 --- a/precompiles/src/solidity/subnet.abi +++ b/precompiles/src/solidity/subnet.abi @@ -213,6 +213,25 @@ "stateMutability": "view", "type": "function" }, + { + "inputs": [ + { + "internalType": "uint16", + "name": "netuid", + "type": "uint16" + } + ], + "name": "isSubnetDissolving", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, { "inputs": [ { diff --git a/precompiles/src/solidity/subnet.sol b/precompiles/src/solidity/subnet.sol index c454781cb5..43509effd2 100644 --- a/precompiles/src/solidity/subnet.sol +++ b/precompiles/src/solidity/subnet.sol @@ -172,6 +172,8 @@ interface ISubnet { function getLiquidAlphaEnabled(uint16 netuid) external view returns (bool); + function isSubnetDissolving(uint16 netuid) external view returns (bool); + function setLiquidAlphaEnabled( uint16 netuid, bool liquidAlphaEnabled diff --git a/precompiles/src/subnet.rs b/precompiles/src/subnet.rs index 9992bd1cf3..32ecf82595 100644 --- a/precompiles/src/subnet.rs +++ b/precompiles/src/subnet.rs @@ -852,6 +852,13 @@ where RawOrigin::Signed(handle.caller_account_id::()), ) } + + #[precompile::public("isSubnetDissolving(uint16)")] + #[precompile::view] + fn is_subnet_dissolving(handle: &mut impl PrecompileHandle, netuid: u16) -> EvmResult { + handle.record_db_reads::(1)?; + Ok(pallet_subtensor::DissolveCleanupQueue::::get().contains(&NetUid::from(netuid))) + } } #[cfg(test)] @@ -1346,4 +1353,38 @@ mod tests { ); }); } + + #[test] + fn subnet_precompile_is_subnet_dissolving() { + new_test_ext().execute_with(|| { + let caller = addr_from_index(0x5003); + let netuid = setup_owner_subnet(caller); + let precompiles = precompiles::>(); + let precompile_addr = addr_from_index(SubnetPrecompile::::INDEX); + + assert_static_call( + &precompiles, + caller, + precompile_addr, + encode_with_selector( + selector_u32("isSubnetDissolving(uint16)"), + (TEST_NETUID_U16,), + ), + U256::zero(), + ); + + pallet_subtensor::DissolveCleanupQueue::::set(vec![netuid]); + + assert_static_call( + &precompiles, + caller, + precompile_addr, + encode_with_selector( + selector_u32("isSubnetDissolving(uint16)"), + (TEST_NETUID_U16,), + ), + U256::one(), + ); + }); + } } diff --git a/precompiles/src/uid_lookup.rs b/precompiles/src/uid_lookup.rs index dc65501ba1..9846eb0463 100644 --- a/precompiles/src/uid_lookup.rs +++ b/precompiles/src/uid_lookup.rs @@ -44,7 +44,7 @@ where evm_address: Address, limit: u16, ) -> EvmResult> { - handle.record_db_reads::(u64::from(limit))?; + handle.record_db_reads::(1)?; Ok(pallet_subtensor::Pallet::::uid_lookup( netuid.into(), evm_address.0, @@ -59,6 +59,7 @@ mod tests { use super::*; use crate::mock::{Runtime, addr_from_index, new_test_ext, precompiles, selector_u32}; + use precompile_utils::prelude::RuntimeHelper; use precompile_utils::solidity::{codec::Address, encode_return_value, encode_with_selector}; use precompile_utils::testing::PrecompileTesterExt; use subtensor_runtime_common::NetUid; @@ -78,10 +79,11 @@ mod tests { let block_associated = 42u64; let limit = 1024u16; - pallet_subtensor::AssociatedEvmAddress::::insert( + pallet_subtensor::Pallet::::set_associated_evm_address( netuid, uid, - (evm_address, block_associated), + evm_address, + block_associated, ); let expected = @@ -98,6 +100,7 @@ mod tests { ), ) .with_static_call(true) + .expect_cost(RuntimeHelper::::db_read_gas_cost()) .execute_returns_raw(encode_return_value(expected)); }); } diff --git a/primitives/swap-interface/src/lib.rs b/primitives/swap-interface/src/lib.rs index 9980604707..ed2bbf010d 100644 --- a/primitives/swap-interface/src/lib.rs +++ b/primitives/swap-interface/src/lib.rs @@ -3,12 +3,12 @@ use core::ops::Neg; use frame_support::pallet_prelude::*; +use frame_support::weights::WeightMeter; +pub use order::*; use substrate_fixed::types::U64F64; use subtensor_macros::freeze_struct; use subtensor_runtime_common::{AlphaBalance, NetUid, TaoBalance, Token}; -pub use order::*; - mod order; pub trait SwapEngine: DefaultPriceLimit { @@ -47,7 +47,7 @@ pub trait SwapHandler { tao_delta: TaoBalance, alpha_delta: AlphaBalance, ) -> (TaoBalance, AlphaBalance); - fn clear_protocol_liquidity(netuid: NetUid) -> DispatchResult; + fn clear_protocol_liquidity(netuid: NetUid, weight_meter: &mut WeightMeter) -> bool; fn init_swap(netuid: NetUid, maybe_price: Option); fn get_alpha_amount_for_tao(netuid: NetUid, tao_amount: TaoBalance) -> AlphaBalance; } diff --git a/runtime/Cargo.toml b/runtime/Cargo.toml index 946e797f21..dd76b1fd93 100644 --- a/runtime/Cargo.toml +++ b/runtime/Cargo.toml @@ -2,12 +2,12 @@ name = "node-subtensor-runtime" version = "4.0.0-dev" description = "Subtensor network" -authors = ["Opentensor Foundation "] +authors = ["Opentensor Foundation "] homepage = "https://opentensor.ai/" edition.workspace = true license = "Unlicense" publish = false -repository = "https://github.com/opentensor/subtensor/" +repository = "https://github.com/RaoFoundation/subtensor/" [lints] workspace = true @@ -329,7 +329,7 @@ runtime-benchmarks = [ # Smart Tx fees pallet "subtensor-transaction-fee/runtime-benchmarks", "pallet-shield/runtime-benchmarks", - + "subtensor-runtime-common/runtime-benchmarks", "subtensor-chain-extensions/runtime-benchmarks", "pallet-limit-orders/runtime-benchmarks", diff --git a/runtime/src/fee_filters.rs b/runtime/src/fee_filters.rs new file mode 100644 index 0000000000..ca9875b0ee --- /dev/null +++ b/runtime/src/fee_filters.rs @@ -0,0 +1,41 @@ +//! Extrinsics whose transaction fee is charged to the signing hotkey's owning +//! coldkey instead of the hotkey itself. Every entry resolves its signed origin as +//! a hotkey; the coldkey is charged only when that hotkey has an owner, otherwise +//! the signer pays (see [`ColdkeyFeeCallFilter`]). Extend one line per extrinsic as +//! more hotkey-origin calls are opted in. + +use crate::transaction_payment_wrapper::ColdkeyFeeCallFilter; +use crate::{Runtime, RuntimeCall}; +use frame_support::traits::Contains; +use pallet_commitments::Call as CommitmentsCall; +use pallet_subtensor::Call as SubtensorCall; +use subtensor_macros::call_filter_group; + +call_filter_group!( + ColdkeyPaysFeeCalls, + [ + RuntimeCall::SubtensorModule(SubtensorCall::set_weights), + RuntimeCall::SubtensorModule(SubtensorCall::set_mechanism_weights), + RuntimeCall::SubtensorModule(SubtensorCall::batch_set_weights), + RuntimeCall::SubtensorModule(SubtensorCall::commit_weights), + RuntimeCall::SubtensorModule(SubtensorCall::commit_mechanism_weights), + RuntimeCall::SubtensorModule(SubtensorCall::batch_commit_weights), + RuntimeCall::SubtensorModule(SubtensorCall::commit_crv3_mechanism_weights), + RuntimeCall::SubtensorModule(SubtensorCall::commit_timelocked_weights), + RuntimeCall::SubtensorModule(SubtensorCall::commit_timelocked_mechanism_weights), + RuntimeCall::SubtensorModule(SubtensorCall::reveal_weights), + RuntimeCall::SubtensorModule(SubtensorCall::reveal_mechanism_weights), + RuntimeCall::SubtensorModule(SubtensorCall::batch_reveal_weights), + RuntimeCall::SubtensorModule(SubtensorCall::serve_axon), + RuntimeCall::SubtensorModule(SubtensorCall::serve_axon_tls), + RuntimeCall::SubtensorModule(SubtensorCall::serve_prometheus), + RuntimeCall::SubtensorModule(SubtensorCall::associate_evm_key), + RuntimeCall::Commitments(CommitmentsCall::set_commitment), + ] +); + +impl ColdkeyFeeCallFilter for Runtime { + fn charges_coldkey(call: &RuntimeCall) -> bool { + ColdkeyPaysFeeCalls::contains(call) + } +} diff --git a/runtime/src/lib.rs b/runtime/src/lib.rs index a39c473880..f5f5ddfcc7 100644 --- a/runtime/src/lib.rs +++ b/runtime/src/lib.rs @@ -12,7 +12,8 @@ use core::num::NonZeroU64; pub mod check_mortality; pub mod check_nonce; -mod migrations; +mod fee_filters; +mod proxy_filters; pub mod sudo_wrapper; pub mod transaction_payment_wrapper; @@ -234,7 +235,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: 423, + spec_version: 427, impl_version: 1, apis: RUNTIME_API_VERSIONS, transaction_version: 1, @@ -584,195 +585,6 @@ parameter_types! { pub const AnnouncementDepositFactor: Balance = deposit(0, 68); } -// Proxy filter definitions. This macro is the single source of truth for both -// on-chain InstanceFilter::filter() logic and the ProxyFilterRuntimeApi response. -// -// Syntax: -// pallets { Alias => (RuntimeVariant, module_path), ... } -// ProxyType => allow { Pallet::call, ... } — allowlist -// ProxyType => deny { Pallet::call, ... } — denylist -// ProxyType => allow_all; — permit everything -// ProxyType => deny_all; — permit nothing -// ProxyType => allow { ... } except { ... } — allowlist with exceptions -// ProxyType => allow_conditional { Pallet::call where (param) < LIMIT, ... } -// ProxyType => allow_nested { Pallet::call where nested(arg) == Target::method, ... } -// Pallet::* in a list means all calls in that pallet. -// -// To add a new extrinsic to an existing proxy type, append Pallet::call_name -// to the relevant block. To add a new pallet, register it in the pallets {} section first. -// -// Human-readable descriptions of each extrinsic are available to clients via -// runtime metadata (v14/v15) which includes doc comments from pallet call definitions. -subtensor_macros::define_proxy_filters! { - pallets { - Balances => (Balances, pallet_balances), - SubtensorModule => (SubtensorModule, pallet_subtensor), - AdminUtils => (AdminUtils, pallet_admin_utils), - Sudo => (Sudo, pallet_sudo), - System => (System, frame_system), - } - - Any => allow_all; - - NonTransfer => deny { - Balances::*, - SubtensorModule::transfer_stake, - SubtensorModule::schedule_swap_coldkey, - SubtensorModule::swap_coldkey, - SubtensorModule::announce_coldkey_swap, - SubtensorModule::swap_coldkey_announced, - SubtensorModule::clear_coldkey_swap_announcement, - SubtensorModule::dispute_coldkey_swap, - } - - NonFungible => deny { - Balances::*, - SubtensorModule::add_stake, - SubtensorModule::add_stake_limit, - SubtensorModule::remove_stake, - SubtensorModule::remove_stake_limit, - SubtensorModule::remove_stake_full_limit, - SubtensorModule::unstake_all, - SubtensorModule::unstake_all_alpha, - SubtensorModule::swap_stake, - SubtensorModule::swap_stake_limit, - SubtensorModule::move_stake, - SubtensorModule::transfer_stake, - SubtensorModule::burned_register, - SubtensorModule::root_register, - SubtensorModule::schedule_swap_coldkey, - SubtensorModule::swap_coldkey, - SubtensorModule::announce_coldkey_swap, - SubtensorModule::swap_coldkey_announced, - SubtensorModule::clear_coldkey_swap_announcement, - SubtensorModule::dispute_coldkey_swap, - SubtensorModule::swap_hotkey, - SubtensorModule::swap_hotkey_v2, - } - - Transfer => allow { - Balances::transfer_keep_alive, - Balances::transfer_allow_death, - Balances::transfer_all, - SubtensorModule::transfer_stake, - } - - SmallTransfer => allow_conditional { - Balances::transfer_keep_alive where (value) < SMALL_TRANSFER_LIMIT, - Balances::transfer_allow_death where (value) < SMALL_TRANSFER_LIMIT, - SubtensorModule::transfer_stake where (alpha_amount) < SMALL_ALPHA_TRANSFER_LIMIT, - } - - Owner => allow { - AdminUtils::*, - SubtensorModule::set_subnet_identity, - SubtensorModule::update_symbol, - } except { - AdminUtils::sudo_set_sn_owner_hotkey, - AdminUtils::sudo_set_subnet_owner_hotkey, - } - - NonCritical => deny { - SubtensorModule::dissolve_network, - SubtensorModule::root_register, - SubtensorModule::burned_register, - Sudo::*, - SubtensorModule::announce_coldkey_swap, - SubtensorModule::swap_coldkey_announced, - SubtensorModule::clear_coldkey_swap_announcement, - SubtensorModule::dispute_coldkey_swap, - } - - Triumvirate => deny_all; - Senate => deny_all; - Governance => deny_all; - - Staking => allow { - SubtensorModule::add_stake, - SubtensorModule::remove_stake, - SubtensorModule::unstake_all, - SubtensorModule::unstake_all_alpha, - SubtensorModule::swap_stake, - SubtensorModule::swap_stake_limit, - SubtensorModule::move_stake, - SubtensorModule::add_stake_limit, - SubtensorModule::remove_stake_limit, - SubtensorModule::remove_stake_full_limit, - SubtensorModule::set_root_claim_type, - } - - Registration => allow { - SubtensorModule::burned_register, - SubtensorModule::register, - SubtensorModule::register_limit, - } - - RootWeights => deny_all; - - ChildKeys => allow { - SubtensorModule::set_children, - SubtensorModule::set_childkey_take, - } - - SudoUncheckedSetCode => allow_nested { - Sudo::sudo_unchecked_weight where nested(call) == System::set_code, - } - - SwapHotkey => allow { - SubtensorModule::swap_hotkey, - SubtensorModule::swap_hotkey_v2, - } - - SubnetLeaseBeneficiary => allow { - SubtensorModule::start_call, - AdminUtils::sudo_set_serving_rate_limit, - AdminUtils::sudo_set_min_difficulty, - AdminUtils::sudo_set_max_difficulty, - AdminUtils::sudo_set_weights_version_key, - AdminUtils::sudo_set_adjustment_alpha, - AdminUtils::sudo_set_immunity_period, - AdminUtils::sudo_set_min_allowed_weights, - AdminUtils::sudo_set_kappa, - AdminUtils::sudo_set_rho, - AdminUtils::sudo_set_activity_cutoff, - AdminUtils::sudo_set_network_registration_allowed, - AdminUtils::sudo_set_network_pow_registration_allowed, - AdminUtils::sudo_set_max_burn, - AdminUtils::sudo_set_bonds_moving_average, - AdminUtils::sudo_set_bonds_penalty, - AdminUtils::sudo_set_commit_reveal_weights_enabled, - AdminUtils::sudo_set_liquid_alpha_enabled, - AdminUtils::sudo_set_alpha_values, - AdminUtils::sudo_set_commit_reveal_weights_interval, - AdminUtils::sudo_set_toggle_transfer, - AdminUtils::sudo_set_subnet_emission_enabled, - AdminUtils::sudo_set_min_childkey_take_per_subnet, - } - - RootClaim => allow { - SubtensorModule::claim_root, - } -} - -impl InstanceFilter for ProxyType { - fn filter(&self, c: &RuntimeCall) -> bool { - proxy_type_filter(self, c) - } - fn is_superset(&self, o: &Self) -> bool { - match (self, o) { - (x, y) if x == y => true, - (ProxyType::Any, _) => true, - (_, ProxyType::Any) => false, - (ProxyType::NonTransfer, _) => { - // NonTransfer is NOT a superset of Transfer or SmallTransfer - !matches!(o, ProxyType::Transfer | ProxyType::SmallTransfer) - } - (ProxyType::Transfer, ProxyType::SmallTransfer) => true, - _ => false, - } - } -} - impl pallet_proxy::Config for Runtime { type RuntimeCall = RuntimeCall; type Currency = Balances; @@ -814,8 +626,11 @@ impl ProxyInterface for Proxier { pub struct CommitmentsI; impl CommitmentsInterface for CommitmentsI { - fn purge_netuid(netuid: NetUid) { - pallet_commitments::Pallet::::purge_netuid(netuid); + fn purge_netuid( + netuid: NetUid, + weight_meter: &mut frame_support::weights::WeightMeter, + ) -> bool { + pallet_commitments::Pallet::::purge_netuid(netuid, weight_meter) } } @@ -899,7 +714,8 @@ pub struct AllowCommitments; impl CanCommit for AllowCommitments { #[cfg(not(feature = "runtime-benchmarks"))] fn can_commit(netuid: NetUid, address: &AccountId) -> bool { - SubtensorModule::is_hotkey_registered_on_network(netuid, address) + SubtensorModule::if_subnet_exist(netuid) + && SubtensorModule::is_hotkey_registered_on_network(netuid, address) } #[cfg(feature = "runtime-benchmarks")] @@ -1675,7 +1491,6 @@ type Migrations = ( pallet_subtensor::migrations::migrate_init_total_issuance::initialise_total_issuance::Migration< Runtime, >, - migrations::PalletRegistryCleanupMigration, ); // Unchecked extrinsic type as expected by this runtime. @@ -1767,59 +1582,6 @@ fn generate_genesis_json() -> Vec { type EventRecord = frame_system::EventRecord; -fn call_info_by_name(name: &str) -> CallInfo { - let names = C::get_call_names(); - let indices = C::get_call_indices(); - let pos = names - .iter() - .position(|n| *n == name) - .unwrap_or_else(|| panic!("Call '{}' not found in pallet '{}'", name, P::name())); - CallInfo { - pallet_name: P::name().as_bytes().to_vec(), - pallet_index: P::index() as u8, - call_name: Some(name.as_bytes().to_vec()), - call_index: Some( - indices.get(pos).copied().unwrap_or_else(|| { - panic!("Call '{}' index out of bounds in '{}'", name, P::name()) - }), - ), - condition: None, - } -} - -fn call_info_by_name_conditional( - name: &str, - condition: CallCondition, -) -> CallInfo { - let mut info = call_info_by_name::(name); - info.condition = Some(condition); - info -} - -fn pallet_wildcard() -> CallInfo { - CallInfo { - pallet_name: P::name().as_bytes().to_vec(), - pallet_index: P::index() as u8, - call_name: None, - call_index: None, - condition: None, - } -} - -pub fn get_all_proxy_type_infos() -> Vec { - (0u8..=u8::MAX) - .filter_map(|i: u8| { - ProxyType::try_from(i) - .ok() - .map(|pt: ProxyType| ProxyTypeInfo { - name: alloc::format!("{:?}", pt).into_bytes(), - index: i, - deprecated: pt.is_deprecated(), - }) - }) - .collect() -} - impl_runtime_apis! { impl sp_api::Core for Runtime { fn version() -> RuntimeVersion { @@ -2537,6 +2299,13 @@ impl_runtime_apis! { fn get_next_epoch_start_block(netuid: NetUid) -> Option { SubtensorModule::get_next_epoch_start_block(netuid) } + + fn get_block_emission() -> TaoBalance { + match SubtensorModule::calculate_block_emission() { + Ok(block_emission) => block_emission.into(), + Err(_) => TaoBalance::ZERO, + } + } } impl subtensor_custom_rpc_runtime_api::StakeInfoRuntimeApi for Runtime { @@ -2581,15 +2350,11 @@ impl_runtime_apis! { impl subtensor_custom_rpc_runtime_api::ProxyFilterRuntimeApi for Runtime { fn get_proxy_types() -> Vec { - get_all_proxy_type_infos() + proxy_filters::get_all_proxy_type_infos() } - fn get_proxy_filter(proxy_type: Option) -> Vec { - let all = get_all_proxy_filters(); - match proxy_type { - None => all, - Some(idx) => all.into_iter().filter(|f| f.proxy_type == idx).collect(), - } + fn get_proxy_filters(proxy_types: Option>) -> Vec { + proxy_filters::get_proxy_filters(proxy_types) } } diff --git a/runtime/src/migrations/mod.rs b/runtime/src/migrations/mod.rs deleted file mode 100644 index d0fdf7f3da..0000000000 --- a/runtime/src/migrations/mod.rs +++ /dev/null @@ -1,3 +0,0 @@ -mod pallet_registry_cleanup_migration; - -pub use pallet_registry_cleanup_migration::*; diff --git a/runtime/src/migrations/pallet_registry_cleanup_migration.rs b/runtime/src/migrations/pallet_registry_cleanup_migration.rs deleted file mode 100644 index f12cdbdc36..0000000000 --- a/runtime/src/migrations/pallet_registry_cleanup_migration.rs +++ /dev/null @@ -1,543 +0,0 @@ -use crate::{Runtime, RuntimeHoldReason}; -use alloc::string::String; -#[cfg(feature = "try-runtime")] -use alloc::vec::Vec; -#[cfg(feature = "try-runtime")] -use codec::{Decode, Encode}; -use deprecated::RegistryHoldReason as OldRegistryHoldReason; -use deprecated::RuntimeHoldReason as OldRuntimeHoldReason; -use frame_support::{ - BoundedVec, - pallet_prelude::Zero, - storage::unhashed, - traits::{OnRuntimeUpgrade, StoredMap, tokens::IdAmount}, - weights::Weight, -}; -use sp_io::hashing::twox_128; -use sp_runtime::Saturating; -#[cfg(feature = "try-runtime")] -use subtensor_macros::freeze_struct; - -type DbWeightOf = ::DbWeight; -#[cfg(feature = "try-runtime")] -type AccountIdOf = ::AccountId; -type BalanceOf = ::Balance; -type AccountStoreOf = ::AccountStore; - -const MIGRATION_NAME: &[u8] = b"pallet_registry_cleanup_migration"; -const REGISTRY_PALLET_NAME: &[u8] = b"Registry"; -#[cfg(test)] -const REGISTRY_IDENTITY_OF_STORAGE_NAME: &[u8] = b"IdentityOf"; - -mod deprecated { - use super::BalanceOf; - use crate::Runtime; - use codec::Decode; - use frame_support::{ - BoundedVec, - traits::{ConstU32, tokens::IdAmount}, - }; - - #[cfg_attr(test, derive(codec::Encode))] - #[derive(Decode, Copy, Clone, Eq, PartialEq, Debug)] - pub(super) enum RegistryHoldReason { - #[codec(index = 0)] - RegistryIdentity, - } - - #[cfg_attr(test, derive(codec::Encode))] - #[derive(Decode, Copy, Clone, Eq, PartialEq, Debug)] - pub(super) enum RuntimeHoldReason { - #[codec(index = 14)] - Preimage(pallet_preimage::HoldReason), - #[codec(index = 17)] - Registry(RegistryHoldReason), - #[codec(index = 20)] - SafeMode(pallet_safe_mode::HoldReason), - #[codec(index = 29)] - Contracts(pallet_contracts::HoldReason), - } - - // Aggregated variant count across all pallets defining a - // composite HoldReason when the pallet was removed. - pub(super) const VARIANT_COUNT: u32 = 5; - - pub(super) type Holds = - BoundedVec>, ConstU32>; -} - -pub struct PalletRegistryCleanupMigration; - -impl OnRuntimeUpgrade for PalletRegistryCleanupMigration { - fn on_runtime_upgrade() -> Weight { - let migration_name = MIGRATION_NAME.to_vec(); - let mut weight = Weight::zero(); - - if pallet_subtensor::HasMigrationRun::::get(&migration_name) { - log::info!( - "Migration '{}' has already run. Skipping.", - String::from_utf8_lossy(&migration_name) - ); - return weight; - } - - log::info!( - "Running migration '{}'", - String::from_utf8_lossy(&migration_name) - ); - - pallet_balances::Holds::::translate::( - |account_id, old_holds| { - weight.saturating_accrue(DbWeightOf::::get().reads_writes(1, 1)); - let mut current_holds = BoundedVec::new(); - let mut unlocked_amount = BalanceOf::::zero(); - - // Translate old holds to new holds and keep track of cleaned up amount. - for hold in old_holds { - match map_reason(hold.id) { - Some(id) => { - if current_holds - .try_push(IdAmount { - id, - amount: hold.amount, - }) - .is_err() - { - log::error!( - "too many balance holds after migration for account {:?}", - account_id - ); - } - } - None => { - unlocked_amount = unlocked_amount.saturating_add(hold.amount); - } - } - } - - // Unlock the balance if there is any. - if !unlocked_amount.is_zero() { - weight.saturating_accrue(DbWeightOf::::get().reads_writes(1, 1)); - if let Err(error) = AccountStoreOf::::mutate(&account_id, |account| { - account.reserved = account.reserved.saturating_sub(unlocked_amount); - account.free = account.free.saturating_add(unlocked_amount); - }) { - log::error!( - "failed to unlock balance during holds migration: {:?}", - error - ); - } - } - - (!current_holds.is_empty()).then_some(current_holds) - }, - ); - - let registry_prefix = twox_128(REGISTRY_PALLET_NAME); - let result = unhashed::clear_prefix(®istry_prefix, Some(u32::MAX), None); - weight.saturating_accrue( - DbWeightOf::::get().reads_writes(result.loops as u64, result.unique as u64), - ); - log::info!( - "Removed {} entries from Registry pallet storage.", - result.unique - ); - - pallet_subtensor::HasMigrationRun::::insert(&migration_name, true); - weight = weight.saturating_add(DbWeightOf::::get().writes(1)); - - log::info!( - "Migration '{}' completed successfully.", - String::from_utf8_lossy(&migration_name) - ); - - weight - } - - #[cfg(feature = "try-runtime")] - fn pre_upgrade() -> Result, sp_runtime::TryRuntimeError> { - let mut affected_accounts = Vec::new(); - - for account_id in pallet_balances::Holds::::iter_keys() { - let old_holds = decode_deprecated_holds(&account_id)?; - let mut unlocked_amount = BalanceOf::::zero(); - - for hold in old_holds { - if matches!(hold.id, OldRuntimeHoldReason::Registry(_)) { - unlocked_amount = unlocked_amount.saturating_add(hold.amount); - } - } - - if !unlocked_amount.is_zero() { - let account = AccountStoreOf::::get(&account_id); - affected_accounts.push(AffectedAccount { - account_id, - free: account.free, - reserved: account.reserved, - unlocked: unlocked_amount, - }); - } - } - - let state = PreUpgradeState { - total_issuance: pallet_balances::TotalIssuance::::get(), - affected_accounts, - }; - - Ok(state.encode()) - } - - #[cfg(feature = "try-runtime")] - fn post_upgrade(state: Vec) -> Result<(), sp_runtime::TryRuntimeError> { - let state = PreUpgradeState::decode(&mut state.as_slice()) - .map_err(|_| "failed to decode registry cleanup pre-upgrade state")?; - - if !pallet_subtensor::HasMigrationRun::::get(MIGRATION_NAME.to_vec()) { - return Err("registry cleanup migration marker was not set".into()); - } - - if pallet_balances::TotalIssuance::::get() != state.total_issuance { - return Err("registry cleanup migration changed total issuance".into()); - } - - for affected_account in state.affected_accounts { - let account = AccountStoreOf::::get(&affected_account.account_id); - let expected_free = affected_account - .free - .saturating_add(affected_account.unlocked); - let expected_reserved = affected_account - .reserved - .saturating_sub(affected_account.unlocked); - - if account.free != expected_free { - return Err("registry cleanup migration did not unlock free balance".into()); - } - - if account.reserved != expected_reserved { - return Err("registry cleanup migration did not reduce reserved balance".into()); - } - } - - for account_id in pallet_balances::Holds::::iter_keys() { - pallet_balances::Holds::::try_get(&account_id) - .map_err(|_| "failed to decode migrated balances holds")?; - } - - let registry_prefix = twox_128(REGISTRY_PALLET_NAME); - if unhashed::contains_prefixed_key(®istry_prefix) { - return Err("registry pallet storage was not cleared".into()); - } - - Ok(()) - } -} - -#[cfg(test)] -fn registry_storage_prefix(storage_name: &[u8]) -> Vec { - let mut prefix = twox_128(REGISTRY_PALLET_NAME).to_vec(); - prefix.extend_from_slice(&twox_128(storage_name)); - prefix -} - -fn map_reason(reason: OldRuntimeHoldReason) -> Option { - match reason { - OldRuntimeHoldReason::Preimage(reason) => Some(RuntimeHoldReason::Preimage(reason)), - OldRuntimeHoldReason::SafeMode(reason) => Some(RuntimeHoldReason::SafeMode(reason)), - OldRuntimeHoldReason::Contracts(reason) => Some(RuntimeHoldReason::Contracts(reason)), - OldRuntimeHoldReason::Registry(OldRegistryHoldReason::RegistryIdentity) => None, - } -} - -#[cfg(feature = "try-runtime")] -#[derive(Encode, Decode)] -#[freeze_struct("d1c269899b95593c")] -struct PreUpgradeState { - total_issuance: BalanceOf, - affected_accounts: Vec, -} - -#[cfg(feature = "try-runtime")] -#[derive(Encode, Decode)] -#[freeze_struct("dd446b32ea403051")] -struct AffectedAccount { - account_id: AccountIdOf, - free: BalanceOf, - reserved: BalanceOf, - unlocked: BalanceOf, -} - -#[cfg(feature = "try-runtime")] -fn decode_deprecated_holds( - account_id: &AccountIdOf, -) -> Result { - let key = pallet_balances::Holds::::hashed_key_for(account_id); - unhashed::get::(&key) - .ok_or("failed to decode deprecated balances holds".into()) -} - -#[cfg(test)] -#[allow(clippy::expect_used)] -mod tests { - use super::*; - use alloc::vec; - use codec::Encode; - use frame_support::{ - assert_ok, - storage::unhashed, - traits::{Currency, ReservableCurrency}, - }; - use sp_runtime::{AccountId32, BuildStorage}; - - fn new_test_ext() -> sp_io::TestExternalities { - let mut ext: sp_io::TestExternalities = crate::RuntimeGenesisConfig::default() - .build_storage() - .expect("runtime genesis storage should build") - .into(); - ext.execute_with(|| crate::System::set_block_number(1)); - ext - } - - fn account(seed: u8) -> AccountId32 { - AccountId32::new([seed; 32]) - } - - fn balance(amount: u64) -> BalanceOf { - amount.into() - } - - fn old_hold( - id: OldRuntimeHoldReason, - amount: u64, - ) -> IdAmount> { - IdAmount { - id, - amount: balance(amount), - } - } - - fn old_holds( - holds: alloc::vec::Vec>>, - ) -> deprecated::Holds { - holds - .try_into() - .expect("test old holds should fit the deprecated bound") - } - - fn holds_key(account_id: &AccountId32) -> alloc::vec::Vec { - pallet_balances::Holds::::hashed_key_for(account_id) - } - - fn insert_old_holds(account_id: &AccountId32, holds: deprecated::Holds) { - unhashed::put_raw(&holds_key(account_id), &holds.encode()); - } - - fn registry_identity_prefix() -> alloc::vec::Vec { - registry_storage_prefix(REGISTRY_IDENTITY_OF_STORAGE_NAME) - } - - fn insert_old_registry_identity_storage(suffix: &[u8]) -> alloc::vec::Vec { - let mut key = registry_identity_prefix(); - key.extend_from_slice(suffix); - unhashed::put_raw(&key, &[1]); - key - } - - fn insert_old_registry_storage_version() -> alloc::vec::Vec { - let key = registry_storage_prefix(b":__STORAGE_VERSION__:"); - unhashed::put_raw(&key, &[1]); - key - } - - #[test] - fn drops_registry_holds_and_unlocks_their_balance() { - new_test_ext().execute_with(|| { - let account_id = account(1); - - assert!(!pallet_subtensor::HasMigrationRun::::get( - MIGRATION_NAME.to_vec() - )); - - let _ = crate::Balances::make_free_balance_be(&account_id, balance(10_000)); - assert_ok!(crate::Balances::reserve(&account_id, balance(225))); - - insert_old_holds( - &account_id, - old_holds(vec![ - old_hold( - OldRuntimeHoldReason::Registry(OldRegistryHoldReason::RegistryIdentity), - 125, - ), - old_hold( - OldRuntimeHoldReason::Preimage(pallet_preimage::HoldReason::Preimage), - 75, - ), - old_hold( - OldRuntimeHoldReason::SafeMode(pallet_safe_mode::HoldReason::EnterOrExtend), - 25, - ), - ]), - ); - - let registry_identity_key = insert_old_registry_identity_storage(b"account-1"); - let registry_storage_version_key = insert_old_registry_storage_version(); - assert!(unhashed::contains_prefixed_key(&twox_128( - REGISTRY_PALLET_NAME - ))); - - let issuance_before = crate::Balances::total_issuance(); - - let weight = PalletRegistryCleanupMigration::on_runtime_upgrade(); - - let account = crate::System::account(&account_id).data; - assert!(!weight.is_zero()); - assert_eq!(account.free, balance(9_900)); - assert_eq!(account.reserved, balance(100)); - assert_eq!(crate::Balances::total_issuance(), issuance_before); - - let current_holds = pallet_balances::Holds::::get(&account_id); - assert_eq!(current_holds.len(), 2); - assert!(current_holds.contains(&IdAmount { - id: RuntimeHoldReason::Preimage(pallet_preimage::HoldReason::Preimage), - amount: balance(75), - })); - assert!(current_holds.contains(&IdAmount { - id: RuntimeHoldReason::SafeMode(pallet_safe_mode::HoldReason::EnterOrExtend), - amount: balance(25), - })); - - assert!(pallet_subtensor::HasMigrationRun::::get( - MIGRATION_NAME.to_vec() - )); - assert!(unhashed::get_raw(®istry_identity_key).is_none()); - assert!(unhashed::get_raw(®istry_storage_version_key).is_none()); - assert!(!unhashed::contains_prefixed_key(&twox_128( - REGISTRY_PALLET_NAME - ))); - - let second_weight = PalletRegistryCleanupMigration::on_runtime_upgrade(); - let account_after_second = crate::System::account(&account_id).data; - - assert!(second_weight.is_zero()); - assert_eq!(account_after_second.free, account.free); - assert_eq!(account_after_second.reserved, account.reserved); - assert_eq!(account_after_second.frozen, account.frozen); - assert_eq!( - pallet_balances::Holds::::get(&account_id), - current_holds - ); - }); - } - - #[test] - fn removes_holds_storage_when_only_registry_holds_remain() { - new_test_ext().execute_with(|| { - let account_id = account(2); - - let _ = crate::Balances::make_free_balance_be(&account_id, balance(10_000)); - assert_ok!(crate::Balances::reserve(&account_id, balance(125))); - - insert_old_holds( - &account_id, - old_holds(vec![old_hold( - OldRuntimeHoldReason::Registry(OldRegistryHoldReason::RegistryIdentity), - 125, - )]), - ); - - let storage_key = holds_key(&account_id); - let issuance_before = crate::Balances::total_issuance(); - - PalletRegistryCleanupMigration::on_runtime_upgrade(); - - let account = crate::System::account(&account_id).data; - assert_eq!(account.free, balance(10_000)); - assert_eq!(account.reserved, balance(0)); - assert_eq!(crate::Balances::total_issuance(), issuance_before); - assert!(pallet_balances::Holds::::get(&account_id).is_empty()); - assert!(unhashed::get_raw(&storage_key).is_none()); - }); - } - - #[test] - fn preserves_non_registry_holds_without_changing_balances() { - new_test_ext().execute_with(|| { - let account_id = account(3); - - let _ = crate::Balances::make_free_balance_be(&account_id, balance(10_000)); - assert_ok!(crate::Balances::reserve(&account_id, balance(100))); - - insert_old_holds( - &account_id, - old_holds(vec![ - old_hold( - OldRuntimeHoldReason::Preimage(pallet_preimage::HoldReason::Preimage), - 70, - ), - old_hold( - OldRuntimeHoldReason::Contracts( - pallet_contracts::HoldReason::StorageDepositReserve, - ), - 30, - ), - ]), - ); - - let issuance_before = crate::Balances::total_issuance(); - - PalletRegistryCleanupMigration::on_runtime_upgrade(); - - let account = crate::System::account(&account_id).data; - assert_eq!(account.free, balance(9_900)); - assert_eq!(account.reserved, balance(100)); - assert_eq!(crate::Balances::total_issuance(), issuance_before); - - let current_holds = pallet_balances::Holds::::get(&account_id); - assert_eq!(current_holds.len(), 2); - assert!(current_holds.contains(&IdAmount { - id: RuntimeHoldReason::Preimage(pallet_preimage::HoldReason::Preimage), - amount: balance(70), - })); - assert!(current_holds.contains(&IdAmount { - id: RuntimeHoldReason::Contracts( - pallet_contracts::HoldReason::StorageDepositReserve, - ), - amount: balance(30), - })); - }); - } - - #[cfg(feature = "try-runtime")] - #[test] - fn try_runtime_checks_validate_cleanup() { - new_test_ext().execute_with(|| { - let account_id = account(4); - - let _ = crate::Balances::make_free_balance_be(&account_id, balance(10_000)); - assert_ok!(crate::Balances::reserve(&account_id, balance(150))); - - insert_old_holds( - &account_id, - old_holds(vec![ - old_hold( - OldRuntimeHoldReason::Registry(OldRegistryHoldReason::RegistryIdentity), - 100, - ), - old_hold( - OldRuntimeHoldReason::Preimage(pallet_preimage::HoldReason::Preimage), - 50, - ), - ]), - ); - - insert_old_registry_identity_storage(b"account-4"); - - let state = PalletRegistryCleanupMigration::pre_upgrade() - .expect("pre-upgrade check should decode old holds"); - - PalletRegistryCleanupMigration::on_runtime_upgrade(); - - PalletRegistryCleanupMigration::post_upgrade(state) - .expect("post-upgrade check should validate migrated holds"); - }); - } -} diff --git a/runtime/src/proxy_filters/call_groups.rs b/runtime/src/proxy_filters/call_groups.rs new file mode 100644 index 0000000000..6099b2ad65 --- /dev/null +++ b/runtime/src/proxy_filters/call_groups.rs @@ -0,0 +1,780 @@ +//! Runtime call inventory for proxy filters. +//! +//! Keep this file boring: one generated group per call-bearing runtime pallet. +//! Proxy-specific policy belongs in `mod.rs`. + +use frame_system::Call as SystemCall; +use pallet_admin_utils::Call as AdminUtilsCall; +use pallet_balances::Call as BalancesCall; +use pallet_base_fee::Call as BaseFeeCall; +use pallet_commitments::Call as CommitmentsCall; +use pallet_contracts::Call as ContractsCall; +use pallet_crowdloan::Call as CrowdloanCall; +use pallet_drand::Call as DrandCall; +use pallet_ethereum::Call as EthereumCall; +use pallet_evm::Call as EvmCall; +use pallet_grandpa::Call as GrandpaCall; +use pallet_limit_orders::Call as LimitOrdersCall; +use pallet_multisig::Call as MultisigCall; +use pallet_preimage::Call as PreimageCall; +use pallet_safe_mode::Call as SafeModeCall; +use pallet_scheduler::Call as SchedulerCall; +use pallet_shield::Call as MevShieldCall; +use pallet_subtensor::Call as SubtensorCall; +use pallet_subtensor_proxy::Call as ProxyCall; +use pallet_subtensor_swap::Call as SwapCall; +use pallet_subtensor_utility::Call as UtilityCall; +use pallet_sudo::Call as SudoCall; +use pallet_timestamp::Call as TimestampCall; +use subtensor_macros::call_filter_group; +use subtensor_runtime_common::{SMALL_ALPHA_TRANSFER_LIMIT, SMALL_TRANSFER_LIMIT}; + +call_filter_group!( + SystemCalls, + [ + RuntimeCall::System(SystemCall::remark), + RuntimeCall::System(SystemCall::remark_with_event), + RuntimeCall::System(SystemCall::set_heap_pages), + RuntimeCall::System(SystemCall::set_code), + RuntimeCall::System(SystemCall::set_code_without_checks), + RuntimeCall::System(SystemCall::set_storage), + RuntimeCall::System(SystemCall::kill_storage), + RuntimeCall::System(SystemCall::kill_prefix), + RuntimeCall::System(SystemCall::authorize_upgrade), + RuntimeCall::System(SystemCall::authorize_upgrade_without_checks), + RuntimeCall::System(SystemCall::apply_authorized_upgrade), + ] +); + +call_filter_group!( + TimestampCalls, + [RuntimeCall::Timestamp(TimestampCall::set),] +); + +call_filter_group!( + GrandpaCalls, + [ + RuntimeCall::Grandpa(GrandpaCall::report_equivocation), + RuntimeCall::Grandpa(GrandpaCall::report_equivocation_unsigned), + RuntimeCall::Grandpa(GrandpaCall::note_stalled), + ] +); + +call_filter_group!( + SafeModeCalls, + [ + RuntimeCall::SafeMode(SafeModeCall::enter), + RuntimeCall::SafeMode(SafeModeCall::force_enter), + RuntimeCall::SafeMode(SafeModeCall::extend), + RuntimeCall::SafeMode(SafeModeCall::force_extend), + RuntimeCall::SafeMode(SafeModeCall::force_exit), + RuntimeCall::SafeMode(SafeModeCall::force_slash_deposit), + RuntimeCall::SafeMode(SafeModeCall::release_deposit), + RuntimeCall::SafeMode(SafeModeCall::force_release_deposit), + ] +); + +call_filter_group!( + EthereumCalls, + [RuntimeCall::Ethereum(EthereumCall::transact),] +); + +call_filter_group!( + EvmCalls, + [ + RuntimeCall::EVM(EvmCall::withdraw), + RuntimeCall::EVM(EvmCall::call), + RuntimeCall::EVM(EvmCall::create), + RuntimeCall::EVM(EvmCall::create2), + RuntimeCall::EVM(EvmCall::set_whitelist), + RuntimeCall::EVM(EvmCall::disable_whitelist), + ] +); + +call_filter_group!( + BaseFeeCalls, + [ + RuntimeCall::BaseFee(BaseFeeCall::set_base_fee_per_gas), + RuntimeCall::BaseFee(BaseFeeCall::set_elasticity), + ] +); + +call_filter_group!( + DrandCalls, + [ + RuntimeCall::Drand(DrandCall::write_pulse), + RuntimeCall::Drand(DrandCall::set_beacon_config), + RuntimeCall::Drand(DrandCall::set_oldest_stored_round), + ] +); + +call_filter_group!( + CrowdloanCalls, + [ + RuntimeCall::Crowdloan(CrowdloanCall::create), + RuntimeCall::Crowdloan(CrowdloanCall::contribute), + RuntimeCall::Crowdloan(CrowdloanCall::withdraw), + RuntimeCall::Crowdloan(CrowdloanCall::finalize), + RuntimeCall::Crowdloan(CrowdloanCall::refund), + RuntimeCall::Crowdloan(CrowdloanCall::dissolve), + RuntimeCall::Crowdloan(CrowdloanCall::update_min_contribution), + RuntimeCall::Crowdloan(CrowdloanCall::update_end), + RuntimeCall::Crowdloan(CrowdloanCall::update_cap), + RuntimeCall::Crowdloan(CrowdloanCall::set_max_contribution), + ] +); + +call_filter_group!( + SwapCalls, + [ + RuntimeCall::Swap(SwapCall::toggle_user_liquidity), + RuntimeCall::Swap(SwapCall::add_liquidity), + RuntimeCall::Swap(SwapCall::remove_liquidity), + RuntimeCall::Swap(SwapCall::modify_position), + RuntimeCall::Swap(SwapCall::disable_lp), + RuntimeCall::Swap(SwapCall::set_fee_rate), + ] +); + +call_filter_group!( + ContractsCalls, + [ + RuntimeCall::Contracts(ContractsCall::call_old_weight), + RuntimeCall::Contracts(ContractsCall::instantiate_with_code_old_weight), + RuntimeCall::Contracts(ContractsCall::instantiate_old_weight), + RuntimeCall::Contracts(ContractsCall::upload_code), + RuntimeCall::Contracts(ContractsCall::remove_code), + RuntimeCall::Contracts(ContractsCall::set_code), + RuntimeCall::Contracts(ContractsCall::call), + RuntimeCall::Contracts(ContractsCall::instantiate_with_code), + RuntimeCall::Contracts(ContractsCall::instantiate), + RuntimeCall::Contracts(ContractsCall::migrate), + ] +); + +call_filter_group!( + MevShieldCalls, + [ + RuntimeCall::MevShield(MevShieldCall::announce_next_key), + RuntimeCall::MevShield(MevShieldCall::submit_encrypted), + RuntimeCall::MevShield(MevShieldCall::store_encrypted), + RuntimeCall::MevShield(MevShieldCall::set_max_pending_extrinsics_number), + RuntimeCall::MevShield(MevShieldCall::set_on_initialize_weight), + RuntimeCall::MevShield(MevShieldCall::set_stored_extrinsic_lifetime), + RuntimeCall::MevShield(MevShieldCall::set_max_extrinsic_weight), + ] +); + +call_filter_group!( + LimitOrdersCalls, + [ + RuntimeCall::LimitOrders(LimitOrdersCall::execute_orders), + RuntimeCall::LimitOrders(LimitOrdersCall::execute_batched_orders), + RuntimeCall::LimitOrders(LimitOrdersCall::cancel_order), + RuntimeCall::LimitOrders(LimitOrdersCall::set_pallet_status), + ] +); + +call_filter_group!( + UtilityCalls, + [ + RuntimeCall::Utility(UtilityCall::batch), + RuntimeCall::Utility(UtilityCall::as_derivative), + RuntimeCall::Utility(UtilityCall::batch_all), + RuntimeCall::Utility(UtilityCall::dispatch_as), + RuntimeCall::Utility(UtilityCall::force_batch), + RuntimeCall::Utility(UtilityCall::with_weight), + RuntimeCall::Utility(UtilityCall::if_else), + RuntimeCall::Utility(UtilityCall::dispatch_as_fallible), + ] +); + +call_filter_group!( + SudoCalls, + [ + RuntimeCall::Sudo(SudoCall::sudo), + RuntimeCall::Sudo(SudoCall::sudo_unchecked_weight), + RuntimeCall::Sudo(SudoCall::set_key), + RuntimeCall::Sudo(SudoCall::sudo_as), + RuntimeCall::Sudo(SudoCall::remove_key), + ] +); + +call_filter_group!( + MultisigCalls, + [ + RuntimeCall::Multisig(MultisigCall::as_multi_threshold_1), + RuntimeCall::Multisig(MultisigCall::as_multi), + RuntimeCall::Multisig(MultisigCall::approve_as_multi), + RuntimeCall::Multisig(MultisigCall::cancel_as_multi), + RuntimeCall::Multisig(MultisigCall::poke_deposit), + ] +); + +call_filter_group!( + PreimageCalls, + [ + RuntimeCall::Preimage(PreimageCall::note_preimage), + RuntimeCall::Preimage(PreimageCall::unnote_preimage), + RuntimeCall::Preimage(PreimageCall::request_preimage), + RuntimeCall::Preimage(PreimageCall::unrequest_preimage), + RuntimeCall::Preimage(PreimageCall::ensure_updated), + ] +); + +call_filter_group!( + SchedulerCalls, + [ + RuntimeCall::Scheduler(SchedulerCall::schedule), + RuntimeCall::Scheduler(SchedulerCall::cancel), + RuntimeCall::Scheduler(SchedulerCall::schedule_named), + RuntimeCall::Scheduler(SchedulerCall::cancel_named), + RuntimeCall::Scheduler(SchedulerCall::schedule_after), + RuntimeCall::Scheduler(SchedulerCall::schedule_named_after), + RuntimeCall::Scheduler(SchedulerCall::set_retry), + RuntimeCall::Scheduler(SchedulerCall::set_retry_named), + RuntimeCall::Scheduler(SchedulerCall::cancel_retry), + RuntimeCall::Scheduler(SchedulerCall::cancel_retry_named), + ] +); + +call_filter_group!( + ProxyCalls, + [ + RuntimeCall::Proxy(ProxyCall::proxy), + RuntimeCall::Proxy(ProxyCall::add_proxy), + RuntimeCall::Proxy(ProxyCall::remove_proxy), + RuntimeCall::Proxy(ProxyCall::remove_proxies), + RuntimeCall::Proxy(ProxyCall::create_pure), + RuntimeCall::Proxy(ProxyCall::kill_pure), + RuntimeCall::Proxy(ProxyCall::announce), + RuntimeCall::Proxy(ProxyCall::remove_announcement), + RuntimeCall::Proxy(ProxyCall::reject_announcement), + RuntimeCall::Proxy(ProxyCall::proxy_announced), + RuntimeCall::Proxy(ProxyCall::poke_deposit), + RuntimeCall::Proxy(ProxyCall::set_real_pays_fee), + ] +); + +call_filter_group!( + CommitmentsCalls, + [ + RuntimeCall::Commitments(CommitmentsCall::set_commitment), + RuntimeCall::Commitments(CommitmentsCall::set_max_space), + ] +); + +// Ordinary balance transfers — moving your own free balance. +call_filter_group!( + BalanceTransferCalls, + [ + RuntimeCall::Balances(BalancesCall::transfer_keep_alive), + RuntimeCall::Balances(BalancesCall::transfer_allow_death), + RuntimeCall::Balances(BalancesCall::transfer_all), + ] +); + +// Privileged, root-only balance operations (force transfer/unreserve, burn, +// issuance adjustment). +call_filter_group!( + BalanceMaintenanceCalls, + [ + RuntimeCall::Balances(BalancesCall::force_transfer), + RuntimeCall::Balances(BalancesCall::force_unreserve), + RuntimeCall::Balances(BalancesCall::upgrade_accounts), + RuntimeCall::Balances(BalancesCall::force_set_balance), + RuntimeCall::Balances(BalancesCall::force_adjust_total_issuance), + RuntimeCall::Balances(BalancesCall::burn), + ] +); + +// Managing your own stake: add, remove, and move between hotkeys/subnets. +call_filter_group!( + StakeManagementCalls, + [ + RuntimeCall::SubtensorModule(SubtensorCall::add_stake), + RuntimeCall::SubtensorModule(SubtensorCall::add_stake_limit), + RuntimeCall::SubtensorModule(SubtensorCall::remove_stake), + RuntimeCall::SubtensorModule(SubtensorCall::remove_stake_limit), + RuntimeCall::SubtensorModule(SubtensorCall::remove_stake_full_limit), + RuntimeCall::SubtensorModule(SubtensorCall::unstake_all), + RuntimeCall::SubtensorModule(SubtensorCall::unstake_all_alpha), + RuntimeCall::SubtensorModule(SubtensorCall::move_stake), + RuntimeCall::SubtensorModule(SubtensorCall::swap_stake), + RuntimeCall::SubtensorModule(SubtensorCall::swap_stake_limit), + ] +); + +// Moving staked value to another coldkey — the stake analogue of a transfer. +call_filter_group!( + StakeTransferCalls, + [RuntimeCall::SubtensorModule(SubtensorCall::transfer_stake),] +); + +// Permissionless proof-of-work registration (costs no TAO). +call_filter_group!( + PowRegistrationCalls, + [ + RuntimeCall::SubtensorModule(SubtensorCall::register), + RuntimeCall::SubtensorModule(SubtensorCall::register_limit), + ] +); + +// Registration paid by burning TAO (spends value, unlike POW registration). +call_filter_group!( + BurnedRegistrationCalls, + [RuntimeCall::SubtensorModule(SubtensorCall::burned_register),] +); + +// Registration into the root subnet. +call_filter_group!( + RootRegistrationCalls, + [RuntimeCall::SubtensorModule(SubtensorCall::root_register),] +); + +// Development/testnet faucet. This call only exists when the runtime is built +// with `pow-faucet`, so the group is empty in production-feature builds. +#[cfg(feature = "pow-faucet")] +call_filter_group!( + FaucetCalls, + [RuntimeCall::SubtensorModule(SubtensorCall::faucet),] +); + +#[cfg(not(feature = "pow-faucet"))] +pub(super) struct FaucetCalls; + +#[cfg(not(feature = "pow-faucet"))] +impl frame_support::traits::Contains for FaucetCalls { + fn contains(_call: &crate::RuntimeCall) -> bool { + false + } +} + +#[cfg(not(feature = "pow-faucet"))] +impl subtensor_runtime_common::CallFilterMetadata for FaucetCalls { + fn call_infos() -> ::alloc::vec::Vec { + ::alloc::vec::Vec::new() + } +} + +// Rotating a neuron's hotkey. +call_filter_group!( + HotkeySwapCalls, + [ + RuntimeCall::SubtensorModule(SubtensorCall::swap_hotkey), + RuntimeCall::SubtensorModule(SubtensorCall::swap_hotkey_v2), + ] +); + +// Rotating a coldkey — the full account-takeover surface. +call_filter_group!( + ColdkeySwapCalls, + [ + RuntimeCall::SubtensorModule(SubtensorCall::schedule_swap_coldkey), + RuntimeCall::SubtensorModule(SubtensorCall::swap_coldkey), + RuntimeCall::SubtensorModule(SubtensorCall::announce_coldkey_swap), + RuntimeCall::SubtensorModule(SubtensorCall::swap_coldkey_announced), + RuntimeCall::SubtensorModule(SubtensorCall::clear_coldkey_swap_announcement), + RuntimeCall::SubtensorModule(SubtensorCall::dispute_coldkey_swap), + RuntimeCall::SubtensorModule(SubtensorCall::reset_coldkey_swap), + ] +); + +// Dissolving a subnet — irreversible network destruction. +call_filter_group!( + CriticalNetworkCalls, + [ + RuntimeCall::SubtensorModule(SubtensorCall::dissolve_network), + RuntimeCall::SubtensorModule(SubtensorCall::root_dissolve_network), + ] +); + +// Delegating a hotkey's work to child keys. +call_filter_group!( + ChildKeyCalls, + [ + RuntimeCall::SubtensorModule(SubtensorCall::set_children), + RuntimeCall::SubtensorModule(SubtensorCall::set_childkey_take), + ] +); + +// Claiming accumulated root dividends. +call_filter_group!( + RootClaimCalls, + [RuntimeCall::SubtensorModule(SubtensorCall::claim_root),] +); + +// Selecting how root dividends are claimed (a staking-side setting). +call_filter_group!( + RootClaimTypeCalls, + [RuntimeCall::SubtensorModule( + SubtensorCall::set_root_claim_type + ),] +); + +// A subnet's public identity and token symbol. +call_filter_group!( + SubnetIdentityCalls, + [ + RuntimeCall::SubtensorModule(SubtensorCall::set_subnet_identity), + RuntimeCall::SubtensorModule(SubtensorCall::update_symbol), + ] +); + +// Starting a subnet's emission schedule (start_call). +call_filter_group!( + SubnetActivationCalls, + [RuntimeCall::SubtensorModule(SubtensorCall::start_call),] +); + +// Residual pallet-subtensor calls that no proxy needs to grant on their own: +// weights, serving, delegate-take, alpha lock/burn/preferences, network +// registration, childkey admin, account association, tempo control, voting +// power, root-claim admin, and lease teardown. +call_filter_group!( + SubtensorCommonCalls, + [ + RuntimeCall::SubtensorModule(SubtensorCall::set_weights), + RuntimeCall::SubtensorModule(SubtensorCall::set_mechanism_weights), + RuntimeCall::SubtensorModule(SubtensorCall::batch_set_weights), + RuntimeCall::SubtensorModule(SubtensorCall::commit_weights), + RuntimeCall::SubtensorModule(SubtensorCall::commit_mechanism_weights), + RuntimeCall::SubtensorModule(SubtensorCall::batch_commit_weights), + RuntimeCall::SubtensorModule(SubtensorCall::commit_crv3_mechanism_weights), + RuntimeCall::SubtensorModule(SubtensorCall::commit_timelocked_weights), + RuntimeCall::SubtensorModule(SubtensorCall::commit_timelocked_mechanism_weights), + RuntimeCall::SubtensorModule(SubtensorCall::reveal_weights), + RuntimeCall::SubtensorModule(SubtensorCall::reveal_mechanism_weights), + RuntimeCall::SubtensorModule(SubtensorCall::batch_reveal_weights), + RuntimeCall::SubtensorModule(SubtensorCall::add_stake_burn), + RuntimeCall::SubtensorModule(SubtensorCall::lock_stake), + RuntimeCall::SubtensorModule(SubtensorCall::move_lock), + RuntimeCall::SubtensorModule(SubtensorCall::set_perpetual_lock), + RuntimeCall::SubtensorModule(SubtensorCall::set_reject_locked_alpha), + RuntimeCall::SubtensorModule(SubtensorCall::recycle_alpha), + RuntimeCall::SubtensorModule(SubtensorCall::burn_alpha), + RuntimeCall::SubtensorModule(SubtensorCall::register_network), + RuntimeCall::SubtensorModule(SubtensorCall::register_network_with_identity), + RuntimeCall::SubtensorModule(SubtensorCall::register_leased_network), + RuntimeCall::SubtensorModule(SubtensorCall::decrease_take), + RuntimeCall::SubtensorModule(SubtensorCall::increase_take), + RuntimeCall::SubtensorModule(SubtensorCall::serve_axon), + RuntimeCall::SubtensorModule(SubtensorCall::serve_axon_tls), + RuntimeCall::SubtensorModule(SubtensorCall::serve_prometheus), + RuntimeCall::SubtensorModule(SubtensorCall::set_identity), + RuntimeCall::SubtensorModule(SubtensorCall::try_associate_hotkey), + RuntimeCall::SubtensorModule(SubtensorCall::associate_evm_key), + RuntimeCall::SubtensorModule(SubtensorCall::set_coldkey_auto_stake_hotkey), + RuntimeCall::SubtensorModule(SubtensorCall::set_pending_childkey_cooldown), + RuntimeCall::SubtensorModule(SubtensorCall::set_auto_parent_delegation_enabled), + RuntimeCall::SubtensorModule(SubtensorCall::sudo_set_tx_childkey_take_rate_limit), + RuntimeCall::SubtensorModule(SubtensorCall::sudo_set_min_childkey_take), + RuntimeCall::SubtensorModule(SubtensorCall::sudo_set_max_childkey_take), + RuntimeCall::SubtensorModule(SubtensorCall::terminate_lease), + RuntimeCall::SubtensorModule(SubtensorCall::set_tempo), + RuntimeCall::SubtensorModule(SubtensorCall::set_activity_cutoff_factor), + RuntimeCall::SubtensorModule(SubtensorCall::trigger_epoch), + RuntimeCall::SubtensorModule(SubtensorCall::sudo_set_num_root_claims), + RuntimeCall::SubtensorModule(SubtensorCall::sudo_set_root_claim_threshold), + RuntimeCall::SubtensorModule(SubtensorCall::enable_voting_power_tracking), + RuntimeCall::SubtensorModule(SubtensorCall::disable_voting_power_tracking), + RuntimeCall::SubtensorModule(SubtensorCall::sudo_set_voting_power_ema_alpha), + ] +); + +// Subnet parameters a subnet owner may set directly (the admin-utils calls +// guarded by `ensure_sn_owner_or_root`). These are the genuine owner/lease +// management surface, as opposed to the root-only `RootConfigCalls`. +call_filter_group!( + SubnetManagementCalls, + [ + RuntimeCall::AdminUtils(AdminUtilsCall::sudo_set_serving_rate_limit), + RuntimeCall::AdminUtils(AdminUtilsCall::sudo_set_max_difficulty), + RuntimeCall::AdminUtils(AdminUtilsCall::sudo_set_weights_version_key), + RuntimeCall::AdminUtils(AdminUtilsCall::sudo_set_adjustment_alpha), + RuntimeCall::AdminUtils(AdminUtilsCall::sudo_set_immunity_period), + RuntimeCall::AdminUtils(AdminUtilsCall::sudo_set_min_allowed_weights), + RuntimeCall::AdminUtils(AdminUtilsCall::sudo_set_rho), + RuntimeCall::AdminUtils(AdminUtilsCall::sudo_set_activity_cutoff), + RuntimeCall::AdminUtils(AdminUtilsCall::sudo_set_min_burn), + RuntimeCall::AdminUtils(AdminUtilsCall::sudo_set_max_burn), + RuntimeCall::AdminUtils(AdminUtilsCall::sudo_set_bonds_moving_average), + RuntimeCall::AdminUtils(AdminUtilsCall::sudo_set_bonds_penalty), + RuntimeCall::AdminUtils(AdminUtilsCall::sudo_set_min_childkey_take_per_subnet), + RuntimeCall::AdminUtils(AdminUtilsCall::sudo_set_commit_reveal_weights_enabled), + RuntimeCall::AdminUtils(AdminUtilsCall::sudo_set_liquid_alpha_enabled), + RuntimeCall::AdminUtils(AdminUtilsCall::sudo_set_alpha_values), + RuntimeCall::AdminUtils(AdminUtilsCall::sudo_set_commit_reveal_weights_interval), + RuntimeCall::AdminUtils(AdminUtilsCall::sudo_set_toggle_transfer), + RuntimeCall::AdminUtils(AdminUtilsCall::sudo_set_recycle_or_burn), + RuntimeCall::AdminUtils(AdminUtilsCall::sudo_set_alpha_sigmoid_steepness), + RuntimeCall::AdminUtils(AdminUtilsCall::sudo_set_yuma3_enabled), + RuntimeCall::AdminUtils(AdminUtilsCall::sudo_set_bonds_reset_enabled), + RuntimeCall::AdminUtils(AdminUtilsCall::sudo_set_owner_immune_neuron_limit), + RuntimeCall::AdminUtils(AdminUtilsCall::sudo_set_mechanism_count), + RuntimeCall::AdminUtils(AdminUtilsCall::sudo_set_mechanism_emission_split), + RuntimeCall::AdminUtils(AdminUtilsCall::sudo_trim_to_max_allowed_uids), + RuntimeCall::AdminUtils(AdminUtilsCall::sudo_set_max_allowed_uids), + RuntimeCall::AdminUtils(AdminUtilsCall::sudo_set_burn_half_life), + RuntimeCall::AdminUtils(AdminUtilsCall::sudo_set_burn_increase_mult), + RuntimeCall::AdminUtils(AdminUtilsCall::sudo_set_owner_cut_enabled), + RuntimeCall::AdminUtils(AdminUtilsCall::sudo_set_owner_cut_auto_lock_enabled), + ] +); + +// Admin parameters that require root (set via sudo / governance). A subnet +// owner cannot call these; they reach the broad proxies only as inert grants +// (the dispatch's `ensure_root` rejects a proxy's signed origin). Includes the +// two deprecated extrinsics that always error. +call_filter_group!( + RootConfigCalls, + [ + RuntimeCall::AdminUtils(AdminUtilsCall::swap_authorities), + RuntimeCall::AdminUtils(AdminUtilsCall::schedule_grandpa_change), + RuntimeCall::AdminUtils(AdminUtilsCall::sudo_set_default_take), + RuntimeCall::AdminUtils(AdminUtilsCall::sudo_set_tx_rate_limit), + RuntimeCall::AdminUtils(AdminUtilsCall::sudo_set_min_difficulty), + RuntimeCall::AdminUtils(AdminUtilsCall::sudo_set_weights_set_rate_limit), + RuntimeCall::AdminUtils(AdminUtilsCall::sudo_set_adjustment_interval), + RuntimeCall::AdminUtils(AdminUtilsCall::sudo_set_kappa), + RuntimeCall::AdminUtils(AdminUtilsCall::sudo_set_network_registration_allowed), + RuntimeCall::AdminUtils(AdminUtilsCall::sudo_set_network_pow_registration_allowed), + RuntimeCall::AdminUtils(AdminUtilsCall::sudo_set_target_registrations_per_interval), + RuntimeCall::AdminUtils(AdminUtilsCall::sudo_set_difficulty), + RuntimeCall::AdminUtils(AdminUtilsCall::sudo_set_max_allowed_validators), + RuntimeCall::AdminUtils(AdminUtilsCall::sudo_set_max_registrations_per_block), + RuntimeCall::AdminUtils(AdminUtilsCall::sudo_set_subnet_owner_cut), + RuntimeCall::AdminUtils(AdminUtilsCall::sudo_set_network_rate_limit), + RuntimeCall::AdminUtils(AdminUtilsCall::sudo_set_tempo), + RuntimeCall::AdminUtils(AdminUtilsCall::sudo_set_total_issuance), + RuntimeCall::AdminUtils(AdminUtilsCall::sudo_set_network_immunity_period), + RuntimeCall::AdminUtils(AdminUtilsCall::sudo_set_network_min_lock_cost), + RuntimeCall::AdminUtils(AdminUtilsCall::sudo_set_subnet_limit), + RuntimeCall::AdminUtils(AdminUtilsCall::sudo_set_lock_reduction_interval), + RuntimeCall::AdminUtils(AdminUtilsCall::sudo_set_rao_recycled), + RuntimeCall::AdminUtils(AdminUtilsCall::sudo_set_stake_threshold), + RuntimeCall::AdminUtils(AdminUtilsCall::sudo_set_nominator_min_required_stake), + RuntimeCall::AdminUtils(AdminUtilsCall::sudo_set_tx_delegate_take_rate_limit), + RuntimeCall::AdminUtils(AdminUtilsCall::sudo_set_min_delegate_take), + RuntimeCall::AdminUtils(AdminUtilsCall::sudo_set_dissolve_network_schedule_duration), + RuntimeCall::AdminUtils(AdminUtilsCall::sudo_set_evm_chain_id), + RuntimeCall::AdminUtils(AdminUtilsCall::sudo_toggle_evm_precompile), + RuntimeCall::AdminUtils(AdminUtilsCall::sudo_set_subnet_moving_alpha), + RuntimeCall::AdminUtils(AdminUtilsCall::sudo_set_ema_price_halving_period), + RuntimeCall::AdminUtils(AdminUtilsCall::sudo_set_subtoken_enabled), + RuntimeCall::AdminUtils(AdminUtilsCall::sudo_set_commit_reveal_version), + RuntimeCall::AdminUtils(AdminUtilsCall::sudo_set_ck_burn), + RuntimeCall::AdminUtils(AdminUtilsCall::sudo_set_admin_freeze_window), + RuntimeCall::AdminUtils(AdminUtilsCall::sudo_set_owner_hparam_rate_limit), + RuntimeCall::AdminUtils(AdminUtilsCall::sudo_set_min_allowed_uids), + RuntimeCall::AdminUtils(AdminUtilsCall::sudo_set_min_non_immune_uids), + RuntimeCall::AdminUtils(AdminUtilsCall::sudo_set_tao_flow_cutoff), + RuntimeCall::AdminUtils(AdminUtilsCall::sudo_set_tao_flow_normalization_exponent), + RuntimeCall::AdminUtils(AdminUtilsCall::sudo_set_tao_flow_smoothing_factor), + RuntimeCall::AdminUtils(AdminUtilsCall::sudo_set_net_tao_flow_enabled), + RuntimeCall::AdminUtils(AdminUtilsCall::sudo_set_max_mechanism_count), + RuntimeCall::AdminUtils(AdminUtilsCall::sudo_set_start_call_delay), + RuntimeCall::AdminUtils(AdminUtilsCall::sudo_set_coldkey_swap_announcement_delay), + RuntimeCall::AdminUtils(AdminUtilsCall::sudo_set_coldkey_swap_reannouncement_delay), + RuntimeCall::AdminUtils(AdminUtilsCall::sudo_set_subnet_emission_enabled), + RuntimeCall::AdminUtils(AdminUtilsCall::sudo_set_max_epochs_per_block), + ] +); + +// Rotating a subnet's owner key — an ownership-takeover vector, deliberately +// kept out of the Owner proxy. +call_filter_group!( + OwnerKeyCalls, + [RuntimeCall::AdminUtils( + AdminUtilsCall::sudo_set_sn_owner_hotkey + ),] +); + +// ============================================================================ +// Conditional, proxy-specific groups +// +// These carry amount / nested-call constraints, so they overlap the inventory +// groups above (e.g. `transfer_keep_alive` is also in `BalanceTransferCalls`). +// They are deliberately excluded from `AllCalls` to keep that a non-overlapping +// partition. Generating them with the same macro keeps the executable filter +// and the client-facing metadata in lockstep. +// ============================================================================ + +// `SmallTransfer`: amount-bounded balance and stake transfers. +call_filter_group!(SmallTransferCalls, [ + RuntimeCall::Balances(BalancesCall::transfer_keep_alive) + where value < SMALL_TRANSFER_LIMIT, + RuntimeCall::Balances(BalancesCall::transfer_allow_death) + where value < SMALL_TRANSFER_LIMIT, + RuntimeCall::SubtensorModule(SubtensorCall::transfer_stake) + where alpha_amount < SMALL_ALPHA_TRANSFER_LIMIT, +]); + +// `SudoUncheckedSetCode`: a single sudo call, only when it wraps +// `System::set_code`. +call_filter_group!(SudoSetCodeCalls, [ + RuntimeCall::Sudo(SudoCall::sudo_unchecked_weight) + where nested(call) == RuntimeCall::System(SystemCall::set_code), +]); + +// Full inventory of every runtime call, used only by the coverage test that +// checks it against `RuntimeCall` metadata. Nested in three blocks so the +// flattened tuple stays within the `CallFilterMetadata` tuple-impl arity; +// `call_infos()` recurses regardless. +// Infrastructure pallets granted wholesale to the broad proxies, excluding +// `SudoCalls` (which `NonCritical` denies). Shared by the proxy policy in +// `mod.rs` and by the `WholesalePalletCalls` inventory below so the list lives +// in one place. +pub(super) type InfraCommonCalls = ( + SystemCalls, + TimestampCalls, + GrandpaCalls, + UtilityCalls, + MultisigCalls, + PreimageCalls, + SchedulerCalls, + ProxyCalls, + CommitmentsCalls, + SafeModeCalls, + EthereumCalls, + EvmCalls, + BaseFeeCalls, + DrandCalls, + CrowdloanCalls, + SwapCalls, + ContractsCalls, + MevShieldCalls, + LimitOrdersCalls, +); + +#[cfg(test)] +pub(super) type AllCalls = ( + WholesalePalletCalls, + SubtensorSplitCalls, + AdminUtilsSplitCalls, +); + +// Pallets every granting proxy grants in full. +#[cfg(test)] +type WholesalePalletCalls = (InfraCommonCalls, SudoCalls); + +// Balances + pallet-subtensor, split by proxy membership. +#[cfg(test)] +type SubtensorSplitCalls = ( + BalanceTransferCalls, + BalanceMaintenanceCalls, + StakeManagementCalls, + StakeTransferCalls, + PowRegistrationCalls, + BurnedRegistrationCalls, + RootRegistrationCalls, + FaucetCalls, + HotkeySwapCalls, + ColdkeySwapCalls, + CriticalNetworkCalls, + ChildKeyCalls, + RootClaimCalls, + RootClaimTypeCalls, + SubnetIdentityCalls, + SubnetActivationCalls, + SubtensorCommonCalls, +); + +// admin-utils, split for the Owner and SubnetLeaseBeneficiary proxies. +#[cfg(test)] +type AdminUtilsSplitCalls = (SubnetManagementCalls, RootConfigCalls, OwnerKeyCalls); + +#[cfg(test)] +mod tests { + use super::*; + use crate::RuntimeCall; + use alloc::{collections::BTreeSet, format, string::String, vec::Vec}; + use frame_support::traits::GetCallMetadata; + use subtensor_runtime_common::{CallFilterMetadata, CallInfo}; + + #[test] + fn all_call_groups_cover_runtime_call_metadata() { + let groups_call_infos = AllCalls::call_infos(); + let groups_call_names = call_names_from_group_infos(&groups_call_infos); + let runtime_call_names = call_names_from_runtime_metadata(); + let duplicate_group_calls = duplicate_call_names(&groups_call_infos); + + let missing_from_groups = runtime_call_names + .difference(&groups_call_names) + .cloned() + .collect::>(); + let extra_in_groups = groups_call_names + .difference(&runtime_call_names) + .cloned() + .collect::>(); + + assert!( + missing_from_groups.is_empty() + && extra_in_groups.is_empty() + && duplicate_group_calls.is_empty(), + "AllCalls inventory does not match RuntimeCall metadata.\n\ + call_infos: {}\n\ + runtime calls: {}\n\ + missing from AllCalls ({}):\n{}\n\ + extra in AllCalls ({}):\n{}\n\ + duplicates in AllCalls ({}):\n{}", + groups_call_names.len(), + runtime_call_names.len(), + missing_from_groups.len(), + format_call_list(&missing_from_groups), + extra_in_groups.len(), + format_call_list(&extra_in_groups), + duplicate_group_calls.len(), + format_call_list(&duplicate_group_calls), + ); + } + + fn call_names_from_group_infos(call_infos: &[CallInfo]) -> BTreeSet { + call_infos.iter().map(format_call_info).collect() + } + + fn call_names_from_runtime_metadata() -> BTreeSet { + RuntimeCall::get_module_names() + .iter() + .flat_map(|module| { + RuntimeCall::get_call_names(module) + .iter() + .map(move |call| format!("{}::{}", module, call)) + }) + .collect() + } + + fn duplicate_call_names(call_infos: &[CallInfo]) -> Vec { + let mut seen = BTreeSet::new(); + let mut duplicates = Vec::new(); + + for call_info in call_infos { + let call_name = format_call_info(call_info); + if !seen.insert(call_name.clone()) { + duplicates.push(call_name); + } + } + + duplicates + } + + fn format_call_info(call_info: &CallInfo) -> String { + format!( + "{}::{}", + String::from_utf8_lossy(&call_info.pallet_name), + String::from_utf8_lossy(&call_info.call_name) + ) + } + + fn format_call_list(calls: &[String]) -> String { + if calls.is_empty() { + return " ".into(); + } + + calls + .iter() + .map(|call| format!(" {}", call)) + .collect::>() + .join("\n") + } +} diff --git a/runtime/src/proxy_filters/mod.rs b/runtime/src/proxy_filters/mod.rs new file mode 100644 index 0000000000..df1d936066 --- /dev/null +++ b/runtime/src/proxy_filters/mod.rs @@ -0,0 +1,654 @@ +mod call_groups; + +use alloc::{format, vec::Vec}; + +use call_groups::*; +use frame_support::traits::{Contains, InstanceFilter}; +use subtensor_runtime_common::{ + CallFilterMetadata, FilterMode, ProxyFilterInfo, ProxyType, ProxyTypeInfo, +}; + +use crate::RuntimeCall; + +// ============================================================================ +// Per-proxy allow-lists +// +// Each proxy type's permission set is an *additive* union of whole call groups +// from `call_groups`. A call a proxy does not list is denied. `Any` allows +// everything; the deprecated proxies allow nothing. +// +// `Contains` for a tuple is logical OR (any member matches), so these aliases +// read as "allow if the call is in any of these groups". +// ============================================================================ + +/// All admin-utils configuration. Every broad proxy historically allowed every +/// admin call; the root-only (`RootConfigCalls`) and owner-key (`OwnerKeyCalls`) +/// calls are gated by the dispatch's own origin check, so granting them to a +/// signed proxy is inert. +type AdminAll = (SubnetManagementCalls, RootConfigCalls, OwnerKeyCalls); + +/// `Transfer`: liquid value movement. +type TransferAllowed = (BalanceTransferCalls, StakeTransferCalls); + +/// `Staking`: stake position management plus root-claim mode selection. +type StakingAllowed = (StakeManagementCalls, RootClaimTypeCalls); + +/// `Registration`: acquire a slot (POW or by burn). +type RegistrationAllowed = (PowRegistrationCalls, BurnedRegistrationCalls); + +/// `Owner`: run a subnet you own — subnet identity plus the owner-settable +/// admin config. Excludes root-only admin (it can't pass `ensure_root`) and +/// owner-key rotation. +type OwnerAllowed = (SubnetIdentityCalls, SubnetManagementCalls); + +/// `SubnetLeaseBeneficiary`: operate a leased subnet (activation, identity, and +/// the owner-settable subnet management config). +type SubnetLeaseAllowed = ( + SubnetActivationCalls, + SubnetIdentityCalls, + SubnetManagementCalls, +); + +/// `NonTransfer`: everything except liquid value movement and coldkey swaps. +type NonTransferAllowed = ( + InfraCommonCalls, + AdminAll, + SudoCalls, + StakeManagementCalls, + PowRegistrationCalls, + BurnedRegistrationCalls, + FaucetCalls, + RootRegistrationCalls, + HotkeySwapCalls, + CriticalNetworkCalls, + ChildKeyCalls, + RootClaimCalls, + RootClaimTypeCalls, + SubnetIdentityCalls, + SubnetActivationCalls, + SubtensorCommonCalls, +); + +/// `NonFungible`: nothing that moves TAO/alpha and no key swaps. +type NonFungibleAllowed = ( + InfraCommonCalls, + AdminAll, + SudoCalls, + PowRegistrationCalls, + FaucetCalls, + CriticalNetworkCalls, + ChildKeyCalls, + RootClaimCalls, + RootClaimTypeCalls, + SubnetIdentityCalls, + SubnetActivationCalls, + SubtensorCommonCalls, +); + +/// `NonCritical`: day-to-day operations including value movement, but no sudo, +/// network dissolution, root/burned registration, or coldkey swaps. +type NonCriticalAllowed = ( + InfraCommonCalls, + AdminAll, + BalanceTransferCalls, + BalanceMaintenanceCalls, + StakeManagementCalls, + StakeTransferCalls, + PowRegistrationCalls, + FaucetCalls, + HotkeySwapCalls, + ChildKeyCalls, + RootClaimCalls, + RootClaimTypeCalls, + SubnetIdentityCalls, + SubnetActivationCalls, + SubtensorCommonCalls, +); + +pub(crate) fn proxy_type_filter(proxy_type: &ProxyType, call: &RuntimeCall) -> bool { + match proxy_type { + ProxyType::Any => true, + ProxyType::Owner => OwnerAllowed::contains(call), + ProxyType::NonCritical => NonCriticalAllowed::contains(call), + ProxyType::NonTransfer => NonTransferAllowed::contains(call), + ProxyType::NonFungible => NonFungibleAllowed::contains(call), + ProxyType::Staking => StakingAllowed::contains(call), + ProxyType::Registration => RegistrationAllowed::contains(call), + ProxyType::Transfer => TransferAllowed::contains(call), + ProxyType::SmallTransfer => SmallTransferCalls::contains(call), + ProxyType::ChildKeys => ChildKeyCalls::contains(call), + ProxyType::SwapHotkey => HotkeySwapCalls::contains(call), + ProxyType::SubnetLeaseBeneficiary => SubnetLeaseAllowed::contains(call), + ProxyType::RootClaim => RootClaimCalls::contains(call), + ProxyType::SudoUncheckedSetCode => SudoSetCodeCalls::contains(call), + ProxyType::Triumvirate + | ProxyType::Senate + | ProxyType::Governance + | ProxyType::RootWeights => false, + } +} + +impl InstanceFilter for ProxyType { + fn filter(&self, call: &RuntimeCall) -> bool { + proxy_type_filter(self, call) + } + + fn is_superset(&self, other: &Self) -> bool { + match (self, other) { + (x, y) if x == y => true, + (ProxyType::Any, _) => true, + (_, ProxyType::Any) => false, + // Keep this positive list explicit. A future proxy type that can + // move value must not become addable through `NonTransfer` by + // default. + ( + ProxyType::NonTransfer, + ProxyType::Owner + | ProxyType::Senate + | ProxyType::NonFungible + | ProxyType::Triumvirate + | ProxyType::Governance + | ProxyType::Staking + | ProxyType::Registration + | ProxyType::RootWeights + | ProxyType::ChildKeys + | ProxyType::SudoUncheckedSetCode + | ProxyType::SwapHotkey + | ProxyType::SubnetLeaseBeneficiary + | ProxyType::RootClaim, + ) => true, + (ProxyType::Transfer, ProxyType::SmallTransfer) => true, + _ => false, + } + } +} + +// ============================================================================ +// Runtime API metadata +// +// The client-facing allowlist view is derived from the same call groups the +// filter uses, so the two cannot drift. +// ============================================================================ + +/// The filter mode (allow-all or an explicit allowlist) for one proxy type. +fn proxy_filter_mode(proxy_type: ProxyType) -> FilterMode { + match proxy_type { + ProxyType::Any => FilterMode::AllowAll, + ProxyType::Owner => FilterMode::Allow(OwnerAllowed::call_infos()), + ProxyType::NonCritical => FilterMode::Allow(NonCriticalAllowed::call_infos()), + ProxyType::NonTransfer => FilterMode::Allow(NonTransferAllowed::call_infos()), + ProxyType::NonFungible => FilterMode::Allow(NonFungibleAllowed::call_infos()), + ProxyType::Staking => FilterMode::Allow(StakingAllowed::call_infos()), + ProxyType::Registration => FilterMode::Allow(RegistrationAllowed::call_infos()), + ProxyType::Transfer => FilterMode::Allow(TransferAllowed::call_infos()), + ProxyType::SmallTransfer => FilterMode::Allow(SmallTransferCalls::call_infos()), + ProxyType::ChildKeys => FilterMode::Allow(ChildKeyCalls::call_infos()), + ProxyType::SwapHotkey => FilterMode::Allow(HotkeySwapCalls::call_infos()), + ProxyType::SubnetLeaseBeneficiary => FilterMode::Allow(SubnetLeaseAllowed::call_infos()), + ProxyType::RootClaim => FilterMode::Allow(RootClaimCalls::call_infos()), + ProxyType::SudoUncheckedSetCode => FilterMode::Allow(SudoSetCodeCalls::call_infos()), + ProxyType::Triumvirate + | ProxyType::Senate + | ProxyType::Governance + | ProxyType::RootWeights => FilterMode::Allow(Vec::new()), + } +} + +/// Every proxy type with its on-chain index and deprecation flag. +pub fn get_all_proxy_type_infos() -> Vec { + (0u8..=u8::MAX) + .filter_map(|index| { + ProxyType::try_from(index) + .ok() + .map(|proxy_type| ProxyTypeInfo { + name: format!("{:?}", proxy_type).into_bytes(), + index, + deprecated: proxy_type.is_deprecated(), + }) + }) + .collect() +} + +/// Filter metadata for the requested proxy types (all of them when `None`). +pub fn get_proxy_filters(proxy_types: Option>) -> Vec { + (0u8..=u8::MAX) + .filter_map(|index| { + ProxyType::try_from(index) + .ok() + .map(|proxy_type| (index, proxy_type)) + }) + .filter(|(index, _)| { + proxy_types + .as_ref() + .is_none_or(|selected| selected.contains(index)) + }) + .map(|(index, proxy_type)| ProxyFilterInfo { + proxy_type: index, + name: format!("{:?}", proxy_type).into_bytes(), + deprecated: proxy_type.is_deprecated(), + filter_mode: proxy_filter_mode(proxy_type), + }) + .collect() +} + +#[cfg(test)] +mod tests { + #![allow(clippy::unwrap_used)] + + use super::*; + use alloc::{ + collections::BTreeSet, + string::{String, ToString}, + vec, + }; + use frame_support::traits::GetCallMetadata; + use subtensor_runtime_common::CallInfo; + + fn call_name(info: &CallInfo) -> String { + format!( + "{}::{}", + String::from_utf8_lossy(&info.pallet_name), + String::from_utf8_lossy(&info.call_name) + ) + } + + /// All `pallet::call` names in the runtime, straight from `RuntimeCall` + /// metadata. + fn all_runtime_calls() -> BTreeSet { + RuntimeCall::get_module_names() + .iter() + .flat_map(|module| { + RuntimeCall::get_call_names(module) + .iter() + .map(move |call| format!("{}::{}", module, call)) + }) + .collect() + } + + fn group_calls() -> BTreeSet { + G::call_infos().iter().map(call_name).collect() + } + + /// The set of calls a proxy type allows, taken from its metadata view. + fn allowed_calls(proxy_type: ProxyType) -> BTreeSet { + match proxy_filter_mode(proxy_type) { + FilterMode::AllowAll => all_runtime_calls(), + FilterMode::Allow(infos) => infos.iter().map(call_name).collect(), + } + } + + fn expected(calls: &[&str]) -> BTreeSet { + calls.iter().map(|c| c.to_string()).collect() + } + + fn all_proxy_types() -> Vec { + (0u8..=u8::MAX) + .filter_map(|index| ProxyType::try_from(index).ok()) + .collect() + } + + #[test] + fn any_allows_everything_and_deprecated_allow_nothing() { + assert_eq!(allowed_calls(ProxyType::Any), all_runtime_calls()); + for deprecated in [ + ProxyType::Triumvirate, + ProxyType::Senate, + ProxyType::Governance, + ProxyType::RootWeights, + ] { + assert!(allowed_calls(deprecated).is_empty()); + } + } + + // Broad proxies are specified subtractively here (all calls minus a few + // denied groups) and checked against the additive composition in the filter. + // Because the inventory groups partition every runtime call, the two must + // agree exactly; a missing or extra group in the filter shows up as a diff. + #[test] + fn non_transfer_is_everything_but_transfers_and_coldkey_swaps() { + let denied = &(&group_calls::() + | &group_calls::()) + | &(&group_calls::() | &group_calls::()); + assert_eq!( + allowed_calls(ProxyType::NonTransfer), + &all_runtime_calls() - &denied + ); + } + + #[test] + fn non_fungible_is_everything_but_value_movement_and_key_swaps() { + let denied = &(&(&group_calls::() + | &group_calls::()) + | &(&group_calls::() | &group_calls::())) + | &(&(&group_calls::() + | &group_calls::()) + | &(&group_calls::() | &group_calls::())); + assert_eq!( + allowed_calls(ProxyType::NonFungible), + &all_runtime_calls() - &denied + ); + } + + #[test] + fn non_critical_is_everything_but_sudo_and_critical_ops() { + let denied = &(&(&group_calls::() | &group_calls::()) + | &(&group_calls::() | &group_calls::())) + | &group_calls::(); + assert_eq!( + allowed_calls(ProxyType::NonCritical), + &all_runtime_calls() - &denied + ); + } + + #[test] + fn superset_relations_match_allowed_call_sets() { + let proxy_types = all_proxy_types(); + let mut violations = Vec::new(); + + for parent in &proxy_types { + let parent_calls = allowed_calls(*parent); + for child in &proxy_types { + if !parent.is_superset(child) { + continue; + } + + let child_calls = allowed_calls(*child); + let missing = child_calls + .difference(&parent_calls) + .cloned() + .collect::>(); + + if !missing.is_empty() { + violations.push(format!( + "{:?}.is_superset({:?}) is missing:\n{}", + parent, + child, + missing + .iter() + .map(|call| format!(" {}", call)) + .collect::>() + .join("\n") + )); + } + } + } + + assert!( + violations.is_empty(), + "is_superset claims proxy permissions that the parent filter does not allow:\n{}", + violations.join("\n\n") + ); + } + + #[test] + fn non_transfer_superset_is_explicit_allowlist() { + let actual = all_proxy_types() + .into_iter() + .filter(|proxy_type| ProxyType::NonTransfer.is_superset(proxy_type)) + .collect::>(); + let expected = [ + ProxyType::Owner, + ProxyType::NonTransfer, + ProxyType::Senate, + ProxyType::NonFungible, + ProxyType::Triumvirate, + ProxyType::Governance, + ProxyType::Staking, + ProxyType::Registration, + ProxyType::RootWeights, + ProxyType::ChildKeys, + ProxyType::SudoUncheckedSetCode, + ProxyType::SwapHotkey, + ProxyType::SubnetLeaseBeneficiary, + ProxyType::RootClaim, + ] + .into_iter() + .collect::>(); + + assert_eq!(actual, expected); + } + + #[test] + fn owner_allows_only_owner_settable_config() { + let owner = allowed_calls(ProxyType::Owner); + // Owner-settable subnet params + subnet identity. + assert!(owner.contains("AdminUtils::sudo_set_serving_rate_limit")); + assert!(owner.contains("AdminUtils::sudo_set_max_difficulty")); + assert!(owner.contains("SubtensorModule::set_subnet_identity")); + // Root-only admin is not owner-settable (gated by `ensure_root`). + assert!(!owner.contains("AdminUtils::sudo_set_tempo")); + assert!(!owner.contains("AdminUtils::sudo_set_kappa")); + assert!(!owner.contains("AdminUtils::sudo_set_total_issuance")); + assert!(!owner.contains("AdminUtils::swap_authorities")); + // Never owner-key rotation. + assert!(!owner.contains("AdminUtils::sudo_set_sn_owner_hotkey")); + // Exactly subnet identity plus the owner-settable management config. + let expected = + &group_calls::() | &group_calls::(); + assert_eq!(owner, expected); + } + + #[test] + fn subnet_lease_boundaries() { + let lease = allowed_calls(ProxyType::SubnetLeaseBeneficiary); + // Can activate and tune the subnet's owner-settable params... + assert!(lease.contains("SubtensorModule::start_call")); + assert!(lease.contains("SubtensorModule::set_subnet_identity")); + assert!(lease.contains("AdminUtils::sudo_set_serving_rate_limit")); + // ...but not root-only params, owner keys, authorities, or lease teardown. + assert!(!lease.contains("AdminUtils::sudo_set_kappa")); + assert!(!lease.contains("AdminUtils::sudo_set_total_issuance")); + assert!(!lease.contains("AdminUtils::sudo_set_sn_owner_hotkey")); + assert!(!lease.contains("AdminUtils::swap_authorities")); + assert!(!lease.contains("SubtensorModule::terminate_lease")); + } + + #[test] + fn narrow_proxies_have_exact_allow_lists() { + assert_eq!( + allowed_calls(ProxyType::Transfer), + expected(&[ + "Balances::transfer_keep_alive", + "Balances::transfer_allow_death", + "Balances::transfer_all", + "SubtensorModule::transfer_stake", + ]) + ); + assert_eq!( + allowed_calls(ProxyType::SmallTransfer), + expected(&[ + "Balances::transfer_keep_alive", + "Balances::transfer_allow_death", + "SubtensorModule::transfer_stake", + ]) + ); + assert_eq!( + allowed_calls(ProxyType::Staking), + expected(&[ + "SubtensorModule::add_stake", + "SubtensorModule::add_stake_limit", + "SubtensorModule::remove_stake", + "SubtensorModule::remove_stake_limit", + "SubtensorModule::remove_stake_full_limit", + "SubtensorModule::unstake_all", + "SubtensorModule::unstake_all_alpha", + "SubtensorModule::move_stake", + "SubtensorModule::swap_stake", + "SubtensorModule::swap_stake_limit", + "SubtensorModule::set_root_claim_type", + ]) + ); + assert_eq!( + allowed_calls(ProxyType::Registration), + expected(&[ + "SubtensorModule::register", + "SubtensorModule::register_limit", + "SubtensorModule::burned_register", + ]) + ); + assert_eq!( + allowed_calls(ProxyType::ChildKeys), + expected(&[ + "SubtensorModule::set_children", + "SubtensorModule::set_childkey_take", + ]) + ); + assert_eq!( + allowed_calls(ProxyType::SwapHotkey), + expected(&[ + "SubtensorModule::swap_hotkey", + "SubtensorModule::swap_hotkey_v2", + ]) + ); + assert_eq!( + allowed_calls(ProxyType::RootClaim), + expected(&["SubtensorModule::claim_root"]) + ); + assert_eq!( + allowed_calls(ProxyType::SudoUncheckedSetCode), + expected(&["Sudo::sudo_unchecked_weight"]) + ); + } + + // The newer calls that leaked through `main`'s denylists must stay denied + // for every broad proxy. + #[test] + fn tightened_denylist_leaks_stay_denied() { + for proxy_type in [ + ProxyType::NonTransfer, + ProxyType::NonFungible, + ProxyType::NonCritical, + ] { + let allowed = allowed_calls(proxy_type); + assert!(!allowed.contains("SubtensorModule::reset_coldkey_swap")); + assert!(!allowed.contains("SubtensorModule::swap_coldkey")); + assert!(!allowed.contains("SubtensorModule::schedule_swap_coldkey")); + } + // `root_dissolve_network` leaked into NonCritical specifically. + assert!( + !allowed_calls(ProxyType::NonCritical) + .contains("SubtensorModule::root_dissolve_network") + ); + } + + // The SmallTransfer / SudoUncheckedSetCode metadata must carry their + // amount / nested-call constraints. + #[test] + fn conditional_proxies_expose_constraints() { + use subtensor_runtime_common::CallConstraint; + + let small = match proxy_filter_mode(ProxyType::SmallTransfer) { + FilterMode::Allow(infos) => infos, + FilterMode::AllowAll => vec![], + }; + assert!( + small + .iter() + .all(|info| matches!(info.constraint, Some(CallConstraint::ParamLessThan { .. }))) + ); + + let set_code = match proxy_filter_mode(ProxyType::SudoUncheckedSetCode) { + FilterMode::Allow(infos) => infos, + FilterMode::AllowAll => vec![], + }; + assert!(set_code.iter().any(|info| matches!( + &info.constraint, + Some(CallConstraint::NestedCallMustBe { pallet_name, call_name, .. }) + if pallet_name == b"System" && call_name == b"set_code" + ))); + } + + // The name-based golden tests above don't exercise the amount / nested-call + // predicates, so check them directly through the filter. + #[test] + fn small_transfer_enforces_amount_limits() { + use frame_system::Call as SystemCall; + use pallet_balances::Call as BalancesCall; + use pallet_subtensor::Call as SubtensorCall; + use subtensor_runtime_common::{ + AccountId, AlphaBalance, NetUid, SMALL_ALPHA_TRANSFER_LIMIT, SMALL_TRANSFER_LIMIT, + TaoBalance, + }; + + let dest = AccountId::new([2u8; 32]); + + let balance_transfer = |value: TaoBalance| { + RuntimeCall::Balances(BalancesCall::transfer_allow_death { + dest: dest.clone().into(), + value, + }) + }; + let stake_transfer = |alpha_amount: AlphaBalance| { + RuntimeCall::SubtensorModule(SubtensorCall::transfer_stake { + destination_coldkey: dest.clone(), + hotkey: dest.clone(), + origin_netuid: NetUid::from(1), + destination_netuid: NetUid::from(1), + alpha_amount, + }) + }; + + // Strictly-below the limit is allowed; at the limit is denied. + assert!(proxy_type_filter( + &ProxyType::SmallTransfer, + &balance_transfer(TaoBalance::from(1)) + )); + assert!(!proxy_type_filter( + &ProxyType::SmallTransfer, + &balance_transfer(SMALL_TRANSFER_LIMIT) + )); + assert!(proxy_type_filter( + &ProxyType::SmallTransfer, + &stake_transfer(AlphaBalance::from(1)) + )); + assert!(!proxy_type_filter( + &ProxyType::SmallTransfer, + &stake_transfer(SMALL_ALPHA_TRANSFER_LIMIT) + )); + + // A non-transfer call is never a small transfer. + let remark = RuntimeCall::System(SystemCall::remark { remark: vec![] }); + assert!(!proxy_type_filter(&ProxyType::SmallTransfer, &remark)); + + // `Transfer` is unconditional: the at-limit amount still passes. + assert!(proxy_type_filter( + &ProxyType::Transfer, + &balance_transfer(SMALL_TRANSFER_LIMIT) + )); + } + + #[test] + fn sudo_unchecked_set_code_only_matches_set_code() { + use alloc::boxed::Box; + use frame_support::weights::Weight; + use frame_system::Call as SystemCall; + use pallet_sudo::Call as SudoCall; + + let unchecked = |inner: RuntimeCall| { + RuntimeCall::Sudo(SudoCall::sudo_unchecked_weight { + call: Box::new(inner), + weight: Weight::zero(), + }) + }; + let set_code = RuntimeCall::System(SystemCall::set_code { code: vec![] }); + let remark = RuntimeCall::System(SystemCall::remark { remark: vec![] }); + + // Allowed only when wrapping `System::set_code`. + assert!(proxy_type_filter( + &ProxyType::SudoUncheckedSetCode, + &unchecked(set_code.clone()) + )); + assert!(!proxy_type_filter( + &ProxyType::SudoUncheckedSetCode, + &unchecked(remark) + )); + // `Sudo::sudo` (checked) never matches, even wrapping set_code. + let checked = RuntimeCall::Sudo(SudoCall::sudo { + call: Box::new(set_code), + }); + assert!(!proxy_type_filter( + &ProxyType::SudoUncheckedSetCode, + &checked + )); + } +} diff --git a/runtime/src/transaction_payment_wrapper.rs b/runtime/src/transaction_payment_wrapper.rs index b16773daf9..67c032ebad 100644 --- a/runtime/src/transaction_payment_wrapper.rs +++ b/runtime/src/transaction_payment_wrapper.rs @@ -12,7 +12,7 @@ use sp_runtime::DispatchResult; use sp_runtime::traits::{ AsSystemOriginSigner, DispatchInfoOf, DispatchOriginOf, Dispatchable, Implication, PostDispatchInfoOf, StaticLookup, TransactionExtension, TransactionExtensionMetadata, - ValidateResult, + ValidateResult, Zero, }; use sp_runtime::transaction_validity::{ TransactionPriority, TransactionSource, TransactionValidity, TransactionValidityError, @@ -28,6 +28,13 @@ type RuntimeOriginOf = ::RuntimeOrigin; type AccountIdOf = ::AccountId; type LookupOf = ::Lookup; +/// Runtime-supplied policy: which calls have their fee charged to the signing +/// hotkey's coldkey. Keeping the concrete list in the runtime (see `fee_filters`) +/// lets this generic extension stay free of a hardcoded allow-list. +pub trait ColdkeyFeeCallFilter { + fn charges_coldkey(call: &Call) -> bool; +} + #[freeze_struct("f003cde1f9da4a90")] #[derive(Encode, Decode, DecodeWithMemTracking, Clone, Eq, PartialEq, TypeInfo)] #[scale_info(skip_type_params(T))] @@ -57,9 +64,11 @@ where } } -impl ChargeTransactionPaymentWrapper +impl + ChargeTransactionPaymentWrapper where RuntimeCallOf: IsSubType> + IsSubType>, + T: ColdkeyFeeCallFilter>, RuntimeOriginOf: AsSystemOriginSigner> + Clone, { /// Extract (real, delegate, inner_call) from a `proxy` call. @@ -182,14 +191,35 @@ where common_real } + + /// Determine the coldkey that should pay the fee when a hotkey is the origin. + /// + /// Returns `Some(coldkey)` only when the call is runtime-marked as coldkey-paid + /// (via [`ColdkeyFeeCallFilter`]) and the signer (hotkey) has an owner. The coldkey + /// covers the protocol fee only; the signer-chosen tip is dropped by the caller + /// (see `validate`) so a hotkey cannot spend coldkey funds through the tip. Returns + /// `None` otherwise, so the signer pays. + fn extract_coldkey_fee_payer( + call: &RuntimeCallOf, + origin: &RuntimeOriginOf, + ) -> Option> { + if !T::charges_coldkey(call) { + return None; + } + + let signer = origin.as_system_origin_signer()?; + pallet_subtensor::Pallet::::maybe_coldkey_for_hotkey(signer) + } } -impl +impl TransactionExtension> for ChargeTransactionPaymentWrapper where RuntimeCallOf: Dispatchable + IsSubType> + IsSubType>, + T: ColdkeyFeeCallFilter>, + BalanceOf: Zero + Send + Sync, RuntimeOriginOf: AsSystemOriginSigner> + Clone + From>>, @@ -226,23 +256,35 @@ where base.saturated_into::() }; - // If a real account opted in to pay fees, create a synthetic origin for fee validation. - // Otherwise, the signer pays as usual. - let fee_origin = if let Some(real) = Self::extract_real_fee_payer(call, &origin) { - frame_system::RawOrigin::Signed(real).into() + // Resolve the fee payer. A proxy `RealPaysFee` opt-in takes precedence; otherwise an + // owned hotkey's coldkey pays for allow-listed calls. In that case the coldkey covers + // the protocol fee only — the signer-chosen tip is dropped, since a tip does not buy + // priority in this wrapper (priority is overridden above) and billing it to the coldkey + // would let a hotkey drain coldkey funds. Other payers keep the original tip. + let (fee_origin, tip) = if let Some(real) = Self::extract_real_fee_payer(call, &origin) { + ( + frame_system::RawOrigin::Signed(real).into(), + self.inner.tip(), + ) + } else if let Some(coldkey) = Self::extract_coldkey_fee_payer(call, &origin) { + ( + frame_system::RawOrigin::Signed(coldkey).into(), + Zero::zero(), + ) } else { - origin.clone() + (origin.clone(), self.inner.tip()) }; - let (mut valid_transaction, val, _fee_origin) = self.inner.validate( - fee_origin, - call, - info, - len, - self_implicit, - inherited_implication, - source, - )?; + let (mut valid_transaction, val, _fee_origin) = ChargeTransactionPayment::::from(tip) + .validate( + fee_origin, + call, + info, + len, + self_implicit, + inherited_implication, + source, + )?; valid_transaction.priority = overridden_priority; @@ -311,3 +353,605 @@ where ChargeTransactionPayment::::bare_post_dispatch(info, post_info, len, result) } } + +#[cfg(test)] +#[allow(clippy::unwrap_used)] +mod tests { + use super::ChargeTransactionPaymentWrapper; + use crate::{ + BuildStorage, NORMAL_DISPATCH_BASE_PRIORITY, OPERATIONAL_DISPATCH_PRIORITY, Proxy, Runtime, + RuntimeCall, RuntimeGenesisConfig, RuntimeOrigin, System, SystemCall, + }; + use frame_support::{ + assert_ok, + dispatch::{DispatchClass, DispatchInfo, GetDispatchInfo, Pays}, + }; + use pallet_subtensor_proxy as pallet_proxy; + use pallet_subtensor_utility as pallet_utility; + use pallet_transaction_payment::Val; + use sp_runtime::Saturating; + use sp_runtime::traits::{DispatchTransaction, TransactionExtension, TxBaseImplication}; + use sp_runtime::transaction_validity::{ + TransactionSource, TransactionValidityError, ValidTransaction, + }; + use subtensor_runtime_common::{AccountId, NetUid, ProxyType, TaoBalance}; + + const SIGNER: [u8; 32] = [1_u8; 32]; + const REAL_A: [u8; 32] = [2_u8; 32]; + const REAL_B: [u8; 32] = [3_u8; 32]; + const OTHER: [u8; 32] = [4_u8; 32]; + const BALANCE: TaoBalance = TaoBalance::new(1_000_000_000_000_u64); + + fn new_test_ext() -> sp_io::TestExternalities { + sp_tracing::try_init_simple(); + let mut ext: sp_io::TestExternalities = RuntimeGenesisConfig { + balances: pallet_balances::GenesisConfig { + balances: vec![ + (AccountId::from(SIGNER), BALANCE), + (AccountId::from(REAL_A), BALANCE), + (AccountId::from(REAL_B), BALANCE), + (AccountId::from(OTHER), BALANCE), + ], + dev_accounts: None, + }, + ..Default::default() + } + .build_storage() + .unwrap() + .into(); + ext.execute_with(|| System::set_block_number(1)); + ext + } + + fn signer() -> AccountId { + AccountId::from(SIGNER) + } + fn real_a() -> AccountId { + AccountId::from(REAL_A) + } + fn real_b() -> AccountId { + AccountId::from(REAL_B) + } + fn other() -> AccountId { + AccountId::from(OTHER) + } + + // -- Call builders -- + + fn call_remark() -> RuntimeCall { + RuntimeCall::System(SystemCall::remark { + remark: vec![1, 2, 3], + }) + } + + fn call_set_weights() -> RuntimeCall { + RuntimeCall::SubtensorModule(pallet_subtensor::Call::set_weights { + netuid: NetUid::from(1), + dests: vec![0], + weights: vec![1], + version_key: 0, + }) + } + + fn call_commit_weights() -> RuntimeCall { + RuntimeCall::SubtensorModule(pallet_subtensor::Call::commit_weights { + netuid: NetUid::from(1), + commit_hash: sp_core::H256::zero(), + }) + } + + fn proxy_call(real: AccountId, inner: RuntimeCall) -> RuntimeCall { + RuntimeCall::Proxy(pallet_proxy::Call::proxy { + real: real.into(), + force_proxy_type: None, + call: Box::new(inner), + }) + } + + fn proxy_announced_call( + delegate: AccountId, + real: AccountId, + inner: RuntimeCall, + ) -> RuntimeCall { + RuntimeCall::Proxy(pallet_proxy::Call::proxy_announced { + delegate: delegate.into(), + real: real.into(), + force_proxy_type: None, + call: Box::new(inner), + }) + } + + fn batch_call(calls: Vec) -> RuntimeCall { + RuntimeCall::Utility(pallet_utility::Call::batch { calls }) + } + + fn batch_all_call(calls: Vec) -> RuntimeCall { + RuntimeCall::Utility(pallet_utility::Call::batch_all { calls }) + } + + fn force_batch_call(calls: Vec) -> RuntimeCall { + RuntimeCall::Utility(pallet_utility::Call::force_batch { calls }) + } + + // -- Setup helpers -- + + fn add_proxy(real: &AccountId, delegate: &AccountId) { + assert_ok!(Proxy::add_proxy( + RuntimeOrigin::signed(real.clone()), + delegate.clone().into(), + ProxyType::Any, + 0, + )); + } + + fn enable_real_pays_fee(real: &AccountId, delegate: &AccountId) { + assert_ok!(Proxy::set_real_pays_fee( + RuntimeOrigin::signed(real.clone()), + delegate.clone().into(), + true, + )); + } + + // -- Validate helpers -- + + fn validate_call( + origin: RuntimeOrigin, + call: &RuntimeCall, + ) -> Result<(ValidTransaction, Val), TransactionValidityError> { + validate_call_with_info(origin, call, &call.get_dispatch_info()) + } + + fn validate_call_with_info( + origin: RuntimeOrigin, + call: &RuntimeCall, + info: &DispatchInfo, + ) -> Result<(ValidTransaction, Val), TransactionValidityError> { + let ext = ChargeTransactionPaymentWrapper::::new(TaoBalance::new(0)); + let (valid_tx, val, _origin) = ext.validate( + origin, + call, + info, + 100, + (), + &TxBaseImplication(()), + TransactionSource::External, + )?; + Ok((valid_tx, val)) + } + + /// Extract the fee payer from the validate result. + fn fee_payer(val: &Val) -> AccountId { + match val { + Val::Charge { who, .. } => who.clone(), + _ => panic!("expected Val::Charge"), + } + } + + // ============================================================ + // Case 0: Non-proxy calls + // ============================================================ + + #[test] + fn non_proxy_call_charges_signer() { + new_test_ext().execute_with(|| { + let call = call_remark(); + let (_valid_tx, val) = validate_call(RuntimeOrigin::signed(signer()), &call).unwrap(); + assert_eq!(fee_payer(&val), signer()); + }); + } + + // ============================================================ + // Case 1: Simple proxy (1 level) + // ============================================================ + + #[test] + fn simple_proxy_charges_real_when_opted_in() { + new_test_ext().execute_with(|| { + add_proxy(&real_a(), &signer()); + enable_real_pays_fee(&real_a(), &signer()); + + let call = proxy_call(real_a(), call_remark()); + let (_valid_tx, val) = validate_call(RuntimeOrigin::signed(signer()), &call).unwrap(); + assert_eq!(fee_payer(&val), real_a()); + }); + } + + #[test] + fn simple_proxy_charges_signer_when_not_opted_in() { + new_test_ext().execute_with(|| { + add_proxy(&real_a(), &signer()); + // No enable_real_pays_fee + + let call = proxy_call(real_a(), call_remark()); + let (_valid_tx, val) = validate_call(RuntimeOrigin::signed(signer()), &call).unwrap(); + assert_eq!(fee_payer(&val), signer()); + }); + } + + #[test] + fn proxy_announced_always_charges_signer() { + new_test_ext().execute_with(|| { + add_proxy(&real_a(), &real_b()); + enable_real_pays_fee(&real_a(), &real_b()); + + // Fee propagation intentionally ignores proxy_announced; signer always pays. + let call = proxy_announced_call(real_b(), real_a(), call_remark()); + let (_valid_tx, val) = validate_call(RuntimeOrigin::signed(signer()), &call).unwrap(); + assert_eq!(fee_payer(&val), signer()); + }); + } + + // ============================================================ + // Case 2: Nested proxy (2 levels) + // ============================================================ + + #[test] + fn nested_proxy_charges_inner_real_when_both_opted_in() { + new_test_ext().execute_with(|| { + // Chain: signer → real_b → real_a + add_proxy(&real_b(), &signer()); + enable_real_pays_fee(&real_b(), &signer()); + add_proxy(&real_a(), &real_b()); + enable_real_pays_fee(&real_a(), &real_b()); + + let call = proxy_call(real_b(), proxy_call(real_a(), call_remark())); + let (_valid_tx, val) = validate_call(RuntimeOrigin::signed(signer()), &call).unwrap(); + assert_eq!(fee_payer(&val), real_a()); + }); + } + + #[test] + fn nested_proxy_charges_outer_real_when_only_outer_opted_in() { + new_test_ext().execute_with(|| { + add_proxy(&real_b(), &signer()); + enable_real_pays_fee(&real_b(), &signer()); + add_proxy(&real_a(), &real_b()); + // No enable_real_pays_fee for A→B + + let call = proxy_call(real_b(), proxy_call(real_a(), call_remark())); + let (_valid_tx, val) = validate_call(RuntimeOrigin::signed(signer()), &call).unwrap(); + assert_eq!(fee_payer(&val), real_b()); + }); + } + + #[test] + fn nested_proxy_charges_signer_when_neither_opted_in() { + new_test_ext().execute_with(|| { + add_proxy(&real_b(), &signer()); + add_proxy(&real_a(), &real_b()); + // No enable_real_pays_fee at all + + let call = proxy_call(real_b(), proxy_call(real_a(), call_remark())); + let (_valid_tx, val) = validate_call(RuntimeOrigin::signed(signer()), &call).unwrap(); + assert_eq!(fee_payer(&val), signer()); + }); + } + + #[test] + fn nested_proxy_charges_signer_when_only_inner_opted_in() { + new_test_ext().execute_with(|| { + add_proxy(&real_b(), &signer()); + // No enable_real_pays_fee for B→signer + add_proxy(&real_a(), &real_b()); + enable_real_pays_fee(&real_a(), &real_b()); + + let call = proxy_call(real_b(), proxy_call(real_a(), call_remark())); + let (_valid_tx, val) = validate_call(RuntimeOrigin::signed(signer()), &call).unwrap(); + // Outer RealPaysFee not set → signer pays (inner opt-in is irrelevant) + assert_eq!(fee_payer(&val), signer()); + }); + } + + // ============================================================ + // Case 3: Batch of proxy calls + // ============================================================ + + #[test] + fn batch_charges_inner_real_when_all_opted_in() { + new_test_ext().execute_with(|| { + add_proxy(&real_b(), &signer()); + enable_real_pays_fee(&real_b(), &signer()); + add_proxy(&real_a(), &real_b()); + enable_real_pays_fee(&real_a(), &real_b()); + + let batch = batch_call(vec![ + proxy_call(real_a(), call_remark()), + proxy_call(real_a(), call_remark()), + ]); + let call = proxy_call(real_b(), batch); + let (_valid_tx, val) = validate_call(RuntimeOrigin::signed(signer()), &call).unwrap(); + assert_eq!(fee_payer(&val), real_a()); + }); + } + + #[test] + fn batch_all_charges_inner_real_when_all_opted_in() { + new_test_ext().execute_with(|| { + add_proxy(&real_b(), &signer()); + enable_real_pays_fee(&real_b(), &signer()); + add_proxy(&real_a(), &real_b()); + enable_real_pays_fee(&real_a(), &real_b()); + + let batch = batch_all_call(vec![ + proxy_call(real_a(), call_remark()), + proxy_call(real_a(), call_remark()), + ]); + let call = proxy_call(real_b(), batch); + let (_valid_tx, val) = validate_call(RuntimeOrigin::signed(signer()), &call).unwrap(); + assert_eq!(fee_payer(&val), real_a()); + }); + } + + #[test] + fn force_batch_charges_inner_real_when_all_opted_in() { + new_test_ext().execute_with(|| { + add_proxy(&real_b(), &signer()); + enable_real_pays_fee(&real_b(), &signer()); + add_proxy(&real_a(), &real_b()); + enable_real_pays_fee(&real_a(), &real_b()); + + let batch = force_batch_call(vec![ + proxy_call(real_a(), call_remark()), + proxy_call(real_a(), call_remark()), + ]); + let call = proxy_call(real_b(), batch); + let (_valid_tx, val) = validate_call(RuntimeOrigin::signed(signer()), &call).unwrap(); + assert_eq!(fee_payer(&val), real_a()); + }); + } + + #[test] + fn batch_charges_outer_real_when_only_outer_opted_in() { + new_test_ext().execute_with(|| { + add_proxy(&real_b(), &signer()); + enable_real_pays_fee(&real_b(), &signer()); + add_proxy(&real_a(), &real_b()); + // No enable_real_pays_fee for A→B + + let batch = batch_call(vec![ + proxy_call(real_a(), call_remark()), + proxy_call(real_a(), call_remark()), + ]); + let call = proxy_call(real_b(), batch); + let (_valid_tx, val) = validate_call(RuntimeOrigin::signed(signer()), &call).unwrap(); + assert_eq!(fee_payer(&val), real_b()); + }); + } + + #[test] + fn batch_charges_outer_real_when_mixed_inner_reals() { + new_test_ext().execute_with(|| { + add_proxy(&real_b(), &signer()); + enable_real_pays_fee(&real_b(), &signer()); + add_proxy(&real_a(), &real_b()); + enable_real_pays_fee(&real_a(), &real_b()); + add_proxy(&other(), &real_b()); + enable_real_pays_fee(&other(), &real_b()); + + // Different inner reals → can't push deeper + let batch = batch_call(vec![ + proxy_call(real_a(), call_remark()), + proxy_call(other(), call_remark()), + ]); + let call = proxy_call(real_b(), batch); + let (_valid_tx, val) = validate_call(RuntimeOrigin::signed(signer()), &call).unwrap(); + assert_eq!(fee_payer(&val), real_b()); + }); + } + + #[test] + fn batch_charges_outer_real_when_non_proxy_in_batch() { + new_test_ext().execute_with(|| { + add_proxy(&real_b(), &signer()); + enable_real_pays_fee(&real_b(), &signer()); + + // Batch contains a non-proxy call → extract_proxy_parts fails + let batch = batch_call(vec![proxy_call(real_a(), call_remark()), call_remark()]); + let call = proxy_call(real_b(), batch); + let (_valid_tx, val) = validate_call(RuntimeOrigin::signed(signer()), &call).unwrap(); + assert_eq!(fee_payer(&val), real_b()); + }); + } + + #[test] + fn batch_charges_outer_real_when_empty() { + new_test_ext().execute_with(|| { + add_proxy(&real_b(), &signer()); + enable_real_pays_fee(&real_b(), &signer()); + + let batch = batch_call(vec![]); + let call = proxy_call(real_b(), batch); + let (_valid_tx, val) = validate_call(RuntimeOrigin::signed(signer()), &call).unwrap(); + assert_eq!(fee_payer(&val), real_b()); + }); + } + + #[test] + fn batch_charges_outer_real_when_inner_real_not_opted_in() { + new_test_ext().execute_with(|| { + add_proxy(&real_b(), &signer()); + enable_real_pays_fee(&real_b(), &signer()); + add_proxy(&real_a(), &real_b()); + // real_a has NOT opted in to pay for real_b + + // Even with same real in all batch items, if RealPaysFee not set → outer_real pays + let batch = batch_call(vec![ + proxy_call(real_a(), call_remark()), + proxy_call(real_a(), call_remark()), + ]); + let call = proxy_call(real_b(), batch); + let (_valid_tx, val) = validate_call(RuntimeOrigin::signed(signer()), &call).unwrap(); + assert_eq!(fee_payer(&val), real_b()); + }); + } + + // ============================================================ + // Priority override + // ============================================================ + + #[test] + fn priority_override_normal_dispatch() { + new_test_ext().execute_with(|| { + let call = call_remark(); + let (valid_tx, _val) = validate_call(RuntimeOrigin::signed(signer()), &call).unwrap(); + assert_eq!(valid_tx.priority, NORMAL_DISPATCH_BASE_PRIORITY); + }); + } + + #[test] + fn priority_override_operational_dispatch() { + new_test_ext().execute_with(|| { + let call = call_remark(); + let mut info = call.get_dispatch_info(); + info.class = DispatchClass::Operational; + + let (valid_tx, _val) = + validate_call_with_info(RuntimeOrigin::signed(signer()), &call, &info).unwrap(); + assert_eq!(valid_tx.priority, OPERATIONAL_DISPATCH_PRIORITY); + }); + } + + #[test] + fn priority_override_mandatory_dispatch() { + new_test_ext().execute_with(|| { + let call = call_remark(); + let mut info = call.get_dispatch_info(); + info.class = DispatchClass::Mandatory; + + let (valid_tx, _val) = + validate_call_with_info(RuntimeOrigin::signed(signer()), &call, &info).unwrap(); + // Mandatory uses the same base as Normal + assert_eq!(valid_tx.priority, NORMAL_DISPATCH_BASE_PRIORITY); + }); + } + + #[test] + fn priority_override_applies_with_real_pays_fee() { + new_test_ext().execute_with(|| { + add_proxy(&real_a(), &signer()); + enable_real_pays_fee(&real_a(), &signer()); + + let call = proxy_call(real_a(), call_remark()); + let (valid_tx, _val) = validate_call(RuntimeOrigin::signed(signer()), &call).unwrap(); + // Priority override should still apply when real pays fee + assert_eq!(valid_tx.priority, NORMAL_DISPATCH_BASE_PRIORITY); + }); + } + + // ============================================================ + // Coldkey pays the fee when a hotkey is the origin + // ============================================================ + + #[test] + fn hotkey_with_owner_charges_coldkey() { + new_test_ext().execute_with(|| { + let hotkey = signer(); + let coldkey = other(); + + pallet_subtensor::Owner::::insert(hotkey.clone(), coldkey.clone()); + + let call = call_set_weights(); + let (_valid_tx, val) = validate_call(RuntimeOrigin::signed(hotkey), &call).unwrap(); + + assert_eq!(fee_payer(&val), coldkey); + }); + } + + // Guards against the gate only recognizing `set_weights` rather than the whole group. + #[test] + fn other_group_member_charges_coldkey() { + new_test_ext().execute_with(|| { + let hotkey = signer(); + let coldkey = other(); + + pallet_subtensor::Owner::::insert(hotkey.clone(), coldkey.clone()); + + let call = call_commit_weights(); + let (_valid_tx, val) = validate_call(RuntimeOrigin::signed(hotkey), &call).unwrap(); + + assert_eq!(fee_payer(&val), coldkey); + }); + } + + #[test] + fn hotkey_without_owner_charges_signer() { + new_test_ext().execute_with(|| { + let hotkey = signer(); + + let call = call_set_weights(); + let (_valid_tx, val) = + validate_call(RuntimeOrigin::signed(hotkey.clone()), &call).unwrap(); + + assert_eq!(fee_payer(&val), hotkey); + }); + } + + // Full lifecycle (validate → prepare → post_dispatch) via `DispatchTransaction::test_run`, + // which validate-only tests cannot reach: a `Pays::Yes` allow-listed call debits the coldkey + // and leaves the hotkey untouched, and a signer-chosen tip is excluded from the debit. + #[test] + fn full_lifecycle_debits_coldkey_excluding_tip() { + new_test_ext().execute_with(|| { + let hotkey = signer(); + let coldkey = other(); + + pallet_subtensor::Owner::::insert(hotkey.clone(), coldkey.clone()); + + let call = call_set_weights(); + // Force a fee-bearing call; the real `set_weights` is `Pays::No` in this PR. + let info = DispatchInfo { + pays_fee: Pays::Yes, + ..call.get_dispatch_info() + }; + + let hotkey_before = pallet_balances::Pallet::::free_balance(&hotkey); + let coldkey_before = pallet_balances::Pallet::::free_balance(&coldkey); + + // Sign with a large tip; it must not reach the coldkey. + let ext = ChargeTransactionPaymentWrapper::::new(TaoBalance::new(1_000_000)); + assert_ok!(ext.test_run( + RuntimeOrigin::signed(hotkey.clone()), + &call, + &info, + 0, + 0, + |_origin| Ok(Default::default()), + )); + + let tipless_fee = pallet_transaction_payment::Pallet::::compute_fee( + 0, + &info, + TaoBalance::new(0), + ); + let coldkey_after = pallet_balances::Pallet::::free_balance(&coldkey); + + assert_eq!( + pallet_balances::Pallet::::free_balance(&hotkey), + hotkey_before + ); + assert!(coldkey_after < coldkey_before, "coldkey should be debited"); + assert_eq!( + coldkey_before.saturating_sub(coldkey_after), + tipless_fee, + "coldkey pays the tipless fee, not the tip" + ); + }); + } + + // Owner is set, yet a call outside the group is never redirected: the signer pays. + #[test] + fn ineligible_call_charges_signer() { + new_test_ext().execute_with(|| { + let hotkey = signer(); + let coldkey = other(); + + pallet_subtensor::Owner::::insert(hotkey.clone(), coldkey); + + let call = call_remark(); + let (_valid_tx, val) = + validate_call(RuntimeOrigin::signed(hotkey.clone()), &call).unwrap(); + + assert_eq!(fee_payer(&val), hotkey); + }); + } +} diff --git a/runtime/tests/ghsa_repro.rs b/runtime/tests/ghsa_repro.rs index aa8651ba75..f153fc83ec 100644 --- a/runtime/tests/ghsa_repro.rs +++ b/runtime/tests/ghsa_repro.rs @@ -73,12 +73,6 @@ fn set_sn_owner_hotkey_c67() -> RuntimeCall { hotkey: acct(), }) } -fn set_subnet_owner_hotkey_c64() -> RuntimeCall { - RuntimeCall::AdminUtils(pallet_admin_utils::Call::sudo_set_subnet_owner_hotkey { - netuid: Default::default(), - hotkey: acct(), - }) -} /// GHSA-2026-001 — NonTransfer and NonFungible proxies (the two "cannot move my funds" /// types) ALLOW the new coldkey-swap lifecycle, so a restricted delegate can take over @@ -149,17 +143,10 @@ fn ghsa_2026_002_nonfungible_allows_swap_hotkey_v2_gap() { } /// GHSA-2026-003 — the Owner proxy excepts sudo_set_sn_owner_hotkey (call 67) but the -/// duplicate alias sudo_set_subnet_owner_hotkey (call 64) is allowed by the AdminUtils::* -/// wildcard, bypassing the carve-out. #[test] fn ghsa_2026_003_owner_proxy_set_owner_hotkey_alias_bypass() { assert!( !ProxyType::Owner.filter(&set_sn_owner_hotkey_c67()), "precondition: Owner correctly excepts sudo_set_sn_owner_hotkey (call 67)" ); - assert!( - !ProxyType::Owner.filter(&set_subnet_owner_hotkey_c64()), - "regression (GHSA-2026-003 fixed): Owner must DENY the alias sudo_set_subnet_owner_hotkey (call 64), \ - which calls the same do_set_sn_owner_hotkey backend" - ); } diff --git a/runtime/tests/pallet_proxy.rs b/runtime/tests/pallet_proxy.rs deleted file mode 100644 index 481c17b53d..0000000000 --- a/runtime/tests/pallet_proxy.rs +++ /dev/null @@ -1,715 +0,0 @@ -#![allow(clippy::unwrap_used)] - -use frame_support::{assert_ok, traits::InstanceFilter}; -use node_subtensor_runtime::{ - BalancesCall, BuildStorage, Proxy, Runtime, RuntimeCall, RuntimeEvent, RuntimeGenesisConfig, - RuntimeOrigin, SubtensorModule, System, SystemCall, get_all_proxy_filters, - get_all_proxy_type_infos, -}; -use pallet_subtensor_proxy as pallet_proxy; -use subtensor_runtime_common::{ - AccountId, CallCondition, FilterMode, NetUid, ProxyType, SMALL_ALPHA_TRANSFER_LIMIT, - SMALL_TRANSFER_LIMIT, TaoBalance, -}; - -const ACCOUNT: [u8; 32] = [1_u8; 32]; -const DELEGATE: [u8; 32] = [2_u8; 32]; -const OTHER_ACCOUNT: [u8; 32] = [3_u8; 32]; - -type SystemError = frame_system::Error; - -pub fn new_test_ext() -> sp_io::TestExternalities { - sp_tracing::try_init_simple(); - let amount = TaoBalance::from(1_000_000_000_000_u64); - let mut ext: sp_io::TestExternalities = RuntimeGenesisConfig { - balances: pallet_balances::GenesisConfig { - balances: vec![ - (AccountId::from(ACCOUNT), amount), - (AccountId::from(DELEGATE), amount), - (AccountId::from(OTHER_ACCOUNT), amount), - ], - dev_accounts: None, - }, - ..Default::default() - } - .build_storage() - .unwrap() - .into(); - ext.execute_with(|| System::set_block_number(1)); - ext -} - -// transfer call -fn call_transfer() -> RuntimeCall { - let value = TaoBalance::from(100); - RuntimeCall::Balances(BalancesCall::transfer_allow_death { - dest: AccountId::from(OTHER_ACCOUNT).into(), - value, - }) -} - -// remark call -fn call_remark() -> RuntimeCall { - let remark = vec![1, 2, 3]; - RuntimeCall::System(SystemCall::remark { remark }) -} - -// owner call -fn call_owner_util() -> RuntimeCall { - let netuid = NetUid::from(1); - let serving_rate_limit = 2; - RuntimeCall::AdminUtils(pallet_admin_utils::Call::sudo_set_serving_rate_limit { - netuid, - serving_rate_limit, - }) -} - -// sn owner hotkey call -fn call_sn_owner_hotkey() -> RuntimeCall { - let netuid = NetUid::from(1); - RuntimeCall::AdminUtils(pallet_admin_utils::Call::sudo_set_sn_owner_hotkey { - netuid, - hotkey: AccountId::from(ACCOUNT).into(), - }) -} - -// set subnet identity call -fn call_set_subnet_identity() -> RuntimeCall { - let netuid = NetUid::from(1); - RuntimeCall::SubtensorModule(pallet_subtensor::Call::set_subnet_identity { - netuid, - subnet_name: vec![], - github_repo: vec![], - subnet_contact: vec![], - subnet_url: vec![], - discord: vec![], - description: vec![], - logo_url: vec![], - additional: vec![], - }) -} - -// update symbol call -fn call_update_symbol() -> RuntimeCall { - let netuid = NetUid::from(1); - RuntimeCall::SubtensorModule(pallet_subtensor::Call::update_symbol { - netuid, - symbol: vec![], - }) -} - -// critical call for Subtensor -fn call_root_register() -> RuntimeCall { - RuntimeCall::SubtensorModule(pallet_subtensor::Call::root_register { - hotkey: AccountId::from(ACCOUNT), - }) -} - -// staking call -fn call_add_stake() -> RuntimeCall { - let netuid = NetUid::from(1); - let amount_staked = 100; - RuntimeCall::SubtensorModule(pallet_subtensor::Call::add_stake { - hotkey: AccountId::from(DELEGATE), - netuid, - amount_staked: amount_staked.into(), - }) -} - -// register call, account as hotkey, delegate as coldkey -fn call_register() -> RuntimeCall { - let block_number: u64 = 1; - let netuid = NetUid::from(2); - - // lower diff first - SubtensorModule::set_difficulty(netuid, 100); - - let (nonce, work): (u64, Vec) = SubtensorModule::create_work_for_block_number( - netuid, - block_number, - 0, - &AccountId::from(ACCOUNT), - ); - - RuntimeCall::SubtensorModule(pallet_subtensor::Call::register { - netuid, - block_number, - nonce, - work: work.clone(), - hotkey: AccountId::from(ACCOUNT), - coldkey: AccountId::from(DELEGATE), - }) -} - -fn verify_call_with_proxy_type(proxy_type: &ProxyType, call: &RuntimeCall) { - assert_ok!(Proxy::proxy( - RuntimeOrigin::signed(AccountId::from(DELEGATE)), - AccountId::from(ACCOUNT).into(), - None, - Box::new(call.clone()), - )); - - let filtered_event: RuntimeEvent = pallet_proxy::Event::ProxyExecuted { - result: Err(SystemError::CallFiltered.into()), - } - .into(); - - // check if the filter works by checking the last event - // filtered if the last event is SystemError::CallFiltered - // not filtered if the last event is proxy executed done or any error from proxy call - if proxy_type.filter(call) { - let last_event = System::events().last().unwrap().event.clone(); - assert_ne!(last_event, filtered_event); - } else { - System::assert_last_event(filtered_event); - } -} - -#[test] -fn test_proxy_pallet() { - let proxy_types = [ - ProxyType::Any, - ProxyType::Owner, - ProxyType::NonCritical, - ProxyType::NonTransfer, - ProxyType::NonFungible, - ProxyType::Staking, - ProxyType::Registration, - ]; - - let calls = [ - call_transfer, - call_remark, - call_owner_util, - call_root_register, - call_add_stake, - call_register, - ]; - - for call in calls.iter() { - for proxy_type in proxy_types.iter() { - new_test_ext().execute_with(|| { - assert_ok!(Proxy::add_proxy( - RuntimeOrigin::signed(AccountId::from(ACCOUNT)), - AccountId::from(DELEGATE).into(), - *proxy_type, - 0 - )); - - verify_call_with_proxy_type(proxy_type, &call()); - }); - } - } -} - -#[test] -fn test_non_transfer_cannot_transfer() { - new_test_ext().execute_with(|| { - assert_ok!(Proxy::add_proxy( - RuntimeOrigin::signed(AccountId::from(ACCOUNT)), - AccountId::from(DELEGATE).into(), - ProxyType::NonTransfer, - 0 - )); - - let call = call_transfer(); - assert_ok!(Proxy::proxy( - RuntimeOrigin::signed(AccountId::from(DELEGATE)), - AccountId::from(ACCOUNT).into(), - None, - Box::new(call.clone()), - )); - - System::assert_last_event( - pallet_proxy::Event::ProxyExecuted { - result: Err(SystemError::CallFiltered.into()), - } - .into(), - ); - }); -} - -#[test] -fn test_owner_type_cannot_set_sn_owner_hotkey() { - new_test_ext().execute_with(|| { - assert_ok!(Proxy::add_proxy( - RuntimeOrigin::signed(AccountId::from(ACCOUNT)), - AccountId::from(DELEGATE).into(), - ProxyType::Owner, - 0 - )); - - let call = call_sn_owner_hotkey(); - assert_ok!(Proxy::proxy( - RuntimeOrigin::signed(AccountId::from(DELEGATE)), - AccountId::from(ACCOUNT).into(), - None, - Box::new(call.clone()), - )); - - System::assert_last_event( - pallet_proxy::Event::ProxyExecuted { - result: Err(SystemError::CallFiltered.into()), - } - .into(), - ); - }); -} - -#[test] -fn test_owner_type_can_set_subnet_identity_and_update_symbol() { - new_test_ext().execute_with(|| { - assert_ok!(Proxy::add_proxy( - RuntimeOrigin::signed(AccountId::from(ACCOUNT)), - AccountId::from(DELEGATE).into(), - ProxyType::Owner, - 0 - )); - - verify_call_with_proxy_type(&ProxyType::Owner, &call_set_subnet_identity()); - verify_call_with_proxy_type(&ProxyType::Owner, &call_update_symbol()); - }); -} - -// --- ProxyFilter RuntimeAPI sync tests --- - -fn call_small_transfer() -> RuntimeCall { - let value = TaoBalance::from(100u64); - RuntimeCall::Balances(BalancesCall::transfer_allow_death { - dest: AccountId::new([2u8; 32]).into(), - value, - }) -} - -fn call_large_transfer() -> RuntimeCall { - let value = TaoBalance::from(1_000_000_000u64); - RuntimeCall::Balances(BalancesCall::transfer_allow_death { - dest: AccountId::new([2u8; 32]).into(), - value, - }) -} - -fn call_sudo_remark() -> RuntimeCall { - RuntimeCall::Sudo(pallet_sudo::Call::sudo { - call: Box::new(RuntimeCall::System(SystemCall::remark { - remark: vec![1, 2, 3], - })), - }) -} - -#[test] -fn proxy_filter_api_behavior_matches_instance_filter() { - new_test_ext().execute_with(|| { - // Part 1: Ground truth — verify InstanceFilter::filter() returns expected results - // based on reading the InstanceFilter source code - - // Any allows everything - assert!(ProxyType::Any.filter(&call_transfer())); - assert!(ProxyType::Any.filter(&call_remark())); - assert!(ProxyType::Any.filter(&call_add_stake())); - - // NonTransfer denies Balances and specific transfer calls - assert!(!ProxyType::NonTransfer.filter(&call_transfer())); - assert!(ProxyType::NonTransfer.filter(&call_remark())); - assert!(ProxyType::NonTransfer.filter(&call_add_stake())); - - // NonFungible denies Balances and staking-related calls - assert!(!ProxyType::NonFungible.filter(&call_transfer())); - assert!(!ProxyType::NonFungible.filter(&call_add_stake())); - assert!(ProxyType::NonFungible.filter(&call_remark())); - - // Transfer allows only specific transfer calls - assert!(ProxyType::Transfer.filter(&call_transfer())); - assert!(!ProxyType::Transfer.filter(&call_remark())); - assert!(!ProxyType::Transfer.filter(&call_add_stake())); - - // SmallTransfer allows transfers below limit - assert!(ProxyType::SmallTransfer.filter(&call_small_transfer())); - assert!(!ProxyType::SmallTransfer.filter(&call_large_transfer())); - assert!(!ProxyType::SmallTransfer.filter(&call_remark())); - - // Owner allows AdminUtils and specific SubtensorModule calls - assert!(ProxyType::Owner.filter(&call_owner_util())); - assert!(ProxyType::Owner.filter(&call_set_subnet_identity())); - assert!(ProxyType::Owner.filter(&call_update_symbol())); - assert!(!ProxyType::Owner.filter(&call_sn_owner_hotkey())); - assert!(!ProxyType::Owner.filter(&call_remark())); - - // NonCritical denies critical calls - assert!(!ProxyType::NonCritical.filter(&call_root_register())); - assert!(ProxyType::NonCritical.filter(&call_remark())); - assert!(ProxyType::NonCritical.filter(&call_transfer())); - assert!(!ProxyType::NonCritical.filter(&call_sudo_remark())); - - // Staking allows staking calls only - assert!(ProxyType::Staking.filter(&call_add_stake())); - assert!(!ProxyType::Staking.filter(&call_transfer())); - assert!(!ProxyType::Staking.filter(&call_remark())); - - // Registration allows registration calls only - assert!(ProxyType::Registration.filter(&call_register())); - assert!(!ProxyType::Registration.filter(&call_remark())); - - // Deprecated types deny everything - assert!(!ProxyType::Triumvirate.filter(&call_remark())); - assert!(!ProxyType::Senate.filter(&call_remark())); - assert!(!ProxyType::Governance.filter(&call_remark())); - assert!(!ProxyType::RootWeights.filter(&call_remark())); - }); -} - -#[test] -fn proxy_filter_api_structural_validation() { - new_test_ext().execute_with(|| { - let type_infos = get_all_proxy_type_infos(); - let filters = get_all_proxy_filters(); - - // Part 2: Structural validation of API output - - // Verify total count equals number of ProxyType variants (18) - assert_eq!(type_infos.len(), 18); - assert_eq!(filters.len(), 18); - - // Verify ProxyTypeInfo correctness - let any_info = type_infos.iter().find(|t| t.index == 0).unwrap(); - assert_eq!(any_info.name, b"Any"); - assert!(!any_info.deprecated); - - let triumvirate_info = type_infos.iter().find(|t| t.index == 6).unwrap(); - assert_eq!(triumvirate_info.name, b"Triumvirate"); - assert!(triumvirate_info.deprecated); - - let senate_info = type_infos.iter().find(|t| t.index == 4).unwrap(); - assert_eq!(senate_info.name, b"Senate"); - assert!(senate_info.deprecated); - - // Verify deprecated ProxyTypes have DenyAll filter mode - for filter in &filters { - let pt = ProxyType::try_from(filter.proxy_type).unwrap(); - if pt.is_deprecated() { - assert_eq!( - filter.filter_mode, - FilterMode::DenyAll, - "Deprecated ProxyType {:?} should have DenyAll filter mode", - pt - ); - assert!(filter.calls.is_empty()); - } - } - - // Verify Any has AllowAll - let any_filter = filters.iter().find(|f| f.proxy_type == 0).unwrap(); - assert_eq!(any_filter.filter_mode, FilterMode::AllowAll); - assert!(any_filter.calls.is_empty()); - - // Verify NonTransfer has Deny mode with Balances wildcard - let non_transfer = filters.iter().find(|f| f.proxy_type == 3).unwrap(); - assert_eq!(non_transfer.filter_mode, FilterMode::Deny); - assert!( - non_transfer - .calls - .iter() - .any(|c| c.call_name.is_none() && c.pallet_name == b"Balances") - ); - - // Verify Owner has Allow mode with exceptions - let owner = filters.iter().find(|f| f.proxy_type == 1).unwrap(); - assert_eq!(owner.filter_mode, FilterMode::Allow); - assert!( - owner - .calls - .iter() - .any(|c| c.call_name.is_none() && c.pallet_name == b"AdminUtils") - ); - assert!(!owner.exceptions.is_empty()); - assert!( - owner - .exceptions - .iter() - .any(|c| c.call_name.as_deref() == Some(b"sudo_set_sn_owner_hotkey".as_slice())) - ); - - // Verify SmallTransfer has conditions with correct limits - let small_transfer = filters.iter().find(|f| f.proxy_type == 11).unwrap(); - assert_eq!(small_transfer.filter_mode, FilterMode::Allow); - let has_tao_limit = small_transfer.calls.iter().any(|c| { - matches!( - &c.condition, - Some(CallCondition::ParamLessThan { limit, .. }) - if *limit == Into::::into(SMALL_TRANSFER_LIMIT) as u128 - ) - }); - assert!( - has_tao_limit, - "SmallTransfer should have TAO limit condition" - ); - - let has_alpha_limit = small_transfer.calls.iter().any(|c| { - matches!( - &c.condition, - Some(CallCondition::ParamLessThan { param_name, limit, .. }) - if param_name == b"alpha_amount" - && *limit == Into::::into(SMALL_ALPHA_TRANSFER_LIMIT) as u128 - ) - }); - assert!( - has_alpha_limit, - "SmallTransfer should have Alpha limit condition" - ); - - // Verify NonCritical has Deny mode with Sudo wildcard - let non_critical = filters.iter().find(|f| f.proxy_type == 2).unwrap(); - assert_eq!(non_critical.filter_mode, FilterMode::Deny); - assert!( - non_critical - .calls - .iter() - .any(|c| c.call_name.is_none() && c.pallet_name == b"Sudo") - ); - - // Verify SudoUncheckedSetCode has NestedCallMustBe condition - let sudo_set_code = filters.iter().find(|f| f.proxy_type == 14).unwrap(); - assert_eq!(sudo_set_code.filter_mode, FilterMode::Allow); - let has_nested_condition = sudo_set_code.calls.iter().any(|c| { - matches!( - &c.condition, - Some(CallCondition::NestedCallMustBe { - pallet_name, - call_name, - }) if pallet_name == b"System" && call_name == b"set_code" - ) - }); - assert!( - has_nested_condition, - "SudoUncheckedSetCode should have NestedCallMustBe condition" - ); - - // Verify pallet indices are correct (from construct_runtime!) - // Balances = 5, SubtensorModule = 7, Sudo = 12, AdminUtils = 19 - let balances_wildcard = non_transfer - .calls - .iter() - .find(|c| c.call_name.is_none() && c.pallet_name == b"Balances") - .unwrap(); - assert_eq!(balances_wildcard.pallet_index, 5); - - let sudo_wildcard = non_critical - .calls - .iter() - .find(|c| c.call_name.is_none() && c.pallet_name == b"Sudo") - .unwrap(); - assert_eq!(sudo_wildcard.pallet_index, 12); - - let admin_wildcard = owner - .calls - .iter() - .find(|c| c.call_name.is_none() && c.pallet_name == b"AdminUtils") - .unwrap(); - assert_eq!(admin_wildcard.pallet_index, 19); - }); -} - -#[test] -#[allow(clippy::indexing_slicing)] -fn proxy_filter_api_cross_check_filter_behavior() { - new_test_ext().execute_with(|| { - let filters = get_all_proxy_filters(); - - // Part 3: For each non-wildcard, non-conditional call in the API output, - // verify that InstanceFilter::filter() agrees with the declared filter_mode - - // Build a set of test calls indexed by (pallet_index, call_index) - let test_calls: Vec<(u8, u8, RuntimeCall)> = vec![ - // Balances calls - { - let call = RuntimeCall::Balances(BalancesCall::transfer_allow_death { - dest: AccountId::new([0u8; 32]).into(), - value: Default::default(), - }); - let encoded = codec::Encode::encode(&call); - (encoded[0], encoded[1], call) - }, - // SubtensorModule calls - { - let call = RuntimeCall::SubtensorModule(pallet_subtensor::Call::add_stake { - hotkey: AccountId::new([0u8; 32]), - netuid: Default::default(), - amount_staked: Default::default(), - }); - let encoded = codec::Encode::encode(&call); - (encoded[0], encoded[1], call) - }, - { - let call = RuntimeCall::SubtensorModule(pallet_subtensor::Call::remove_stake { - hotkey: AccountId::new([0u8; 32]), - netuid: Default::default(), - amount_unstaked: Default::default(), - }); - let encoded = codec::Encode::encode(&call); - (encoded[0], encoded[1], call) - }, - { - let call = RuntimeCall::SubtensorModule(pallet_subtensor::Call::burned_register { - netuid: Default::default(), - hotkey: AccountId::new([0u8; 32]), - }); - let encoded = codec::Encode::encode(&call); - (encoded[0], encoded[1], call) - }, - { - let call = RuntimeCall::SubtensorModule(pallet_subtensor::Call::root_register { - hotkey: AccountId::new([0u8; 32]), - }); - let encoded = codec::Encode::encode(&call); - (encoded[0], encoded[1], call) - }, - { - let call = RuntimeCall::SubtensorModule(pallet_subtensor::Call::swap_hotkey { - hotkey: AccountId::new([0u8; 32]), - new_hotkey: AccountId::new([0u8; 32]), - netuid: Default::default(), - }); - let encoded = codec::Encode::encode(&call); - (encoded[0], encoded[1], call) - }, - { - let call = RuntimeCall::SubtensorModule(pallet_subtensor::Call::set_children { - hotkey: AccountId::new([0u8; 32]), - netuid: Default::default(), - children: Default::default(), - }); - let encoded = codec::Encode::encode(&call); - (encoded[0], encoded[1], call) - }, - // AdminUtils calls - { - let call = RuntimeCall::AdminUtils( - pallet_admin_utils::Call::sudo_set_serving_rate_limit { - netuid: Default::default(), - serving_rate_limit: Default::default(), - }, - ); - let encoded = codec::Encode::encode(&call); - (encoded[0], encoded[1], call) - }, - { - let call = - RuntimeCall::AdminUtils(pallet_admin_utils::Call::sudo_set_sn_owner_hotkey { - netuid: Default::default(), - hotkey: AccountId::new([0u8; 32]), - }); - let encoded = codec::Encode::encode(&call); - (encoded[0], encoded[1], call) - }, - ]; - - for filter_info in &filters { - let proxy_type = ProxyType::try_from(filter_info.proxy_type).unwrap(); - - match filter_info.filter_mode { - FilterMode::AllowAll => { - for (_, _, call) in &test_calls { - assert!( - proxy_type.filter(call), - "AllowAll ProxyType {:?} should allow all calls", - proxy_type - ); - } - } - FilterMode::DenyAll => { - for (_, _, call) in &test_calls { - assert!( - !proxy_type.filter(call), - "DenyAll ProxyType {:?} should deny all calls", - proxy_type - ); - } - } - FilterMode::Allow => { - for call_info in &filter_info.calls { - if call_info.call_name.is_none() || call_info.condition.is_some() { - continue; - } - if let Some((_, _, call)) = test_calls.iter().find(|(pi, ci, _)| { - *pi == call_info.pallet_index && Some(*ci) == call_info.call_index - }) { - assert!( - proxy_type.filter(call), - "Allow-mode ProxyType {:?} should allow call {:?}", - proxy_type, - call_info - .call_name - .as_ref() - .map(|n| core::str::from_utf8(n).unwrap_or("?")) - .unwrap_or("*") - ); - } - } - // Verify exceptions are denied - for exc_info in &filter_info.exceptions { - if let Some((_, _, call)) = test_calls.iter().find(|(pi, ci, _)| { - *pi == exc_info.pallet_index && Some(*ci) == exc_info.call_index - }) { - assert!( - !proxy_type.filter(call), - "ProxyType {:?} should deny exception {:?}", - proxy_type, - exc_info - .call_name - .as_ref() - .map(|n| core::str::from_utf8(n).unwrap_or("?")) - .unwrap_or("*") - ); - } - } - } - FilterMode::Deny => { - for call_info in &filter_info.calls { - if call_info.call_name.is_none() || call_info.condition.is_some() { - continue; - } - if let Some((_, _, call)) = test_calls.iter().find(|(pi, ci, _)| { - *pi == call_info.pallet_index && Some(*ci) == call_info.call_index - }) { - assert!( - !proxy_type.filter(call), - "Deny-mode ProxyType {:?} should deny call {:?}", - proxy_type, - call_info - .call_name - .as_ref() - .map(|n| core::str::from_utf8(n).unwrap_or("?")) - .unwrap_or("*") - ); - } - } - } - } - } - }); -} - -#[test] -fn proxy_filter_api_deprecated_consistency() { - new_test_ext().execute_with(|| { - let type_infos = get_all_proxy_type_infos(); - - for pt_info in &type_infos { - let pt = ProxyType::try_from(pt_info.index).unwrap(); - if pt_info.deprecated { - assert!( - pt.is_deprecated(), - "ProxyTypeInfo reports {:?} as deprecated but is_deprecated() disagrees", - pt - ); - assert!( - !pt.filter(&call_remark()), - "Deprecated ProxyType {:?} should deny all calls", - pt - ); - assert!(!pt.filter(&call_transfer())); - assert!(!pt.filter(&call_add_stake())); - } - } - }); -} diff --git a/runtime/tests/transaction_payment_wrapper.rs b/runtime/tests/transaction_payment_wrapper.rs deleted file mode 100644 index bbc9798a3e..0000000000 --- a/runtime/tests/transaction_payment_wrapper.rs +++ /dev/null @@ -1,461 +0,0 @@ -#![allow(clippy::unwrap_used)] - -use frame_support::{ - assert_ok, - dispatch::{DispatchClass, DispatchInfo, GetDispatchInfo}, -}; -use node_subtensor_runtime::{ - BuildStorage, NORMAL_DISPATCH_BASE_PRIORITY, OPERATIONAL_DISPATCH_PRIORITY, Proxy, Runtime, - RuntimeCall, RuntimeGenesisConfig, RuntimeOrigin, System, SystemCall, - transaction_payment_wrapper::ChargeTransactionPaymentWrapper, -}; -use pallet_subtensor_proxy as pallet_proxy; -use pallet_subtensor_utility as pallet_utility; -use pallet_transaction_payment::Val; -use sp_runtime::traits::{TransactionExtension, TxBaseImplication}; -use sp_runtime::transaction_validity::{ - TransactionSource, TransactionValidityError, ValidTransaction, -}; -use subtensor_runtime_common::{AccountId, ProxyType, TaoBalance}; - -const SIGNER: [u8; 32] = [1_u8; 32]; -const REAL_A: [u8; 32] = [2_u8; 32]; -const REAL_B: [u8; 32] = [3_u8; 32]; -const OTHER: [u8; 32] = [4_u8; 32]; -const BALANCE: TaoBalance = TaoBalance::new(1_000_000_000_000_u64); - -fn new_test_ext() -> sp_io::TestExternalities { - sp_tracing::try_init_simple(); - let mut ext: sp_io::TestExternalities = RuntimeGenesisConfig { - balances: pallet_balances::GenesisConfig { - balances: vec![ - (AccountId::from(SIGNER), BALANCE), - (AccountId::from(REAL_A), BALANCE), - (AccountId::from(REAL_B), BALANCE), - (AccountId::from(OTHER), BALANCE), - ], - dev_accounts: None, - }, - ..Default::default() - } - .build_storage() - .unwrap() - .into(); - ext.execute_with(|| System::set_block_number(1)); - ext -} - -fn signer() -> AccountId { - AccountId::from(SIGNER) -} -fn real_a() -> AccountId { - AccountId::from(REAL_A) -} -fn real_b() -> AccountId { - AccountId::from(REAL_B) -} -fn other() -> AccountId { - AccountId::from(OTHER) -} - -// -- Call builders -- - -fn call_remark() -> RuntimeCall { - RuntimeCall::System(SystemCall::remark { - remark: vec![1, 2, 3], - }) -} - -fn proxy_call(real: AccountId, inner: RuntimeCall) -> RuntimeCall { - RuntimeCall::Proxy(pallet_proxy::Call::proxy { - real: real.into(), - force_proxy_type: None, - call: Box::new(inner), - }) -} - -fn proxy_announced_call(delegate: AccountId, real: AccountId, inner: RuntimeCall) -> RuntimeCall { - RuntimeCall::Proxy(pallet_proxy::Call::proxy_announced { - delegate: delegate.into(), - real: real.into(), - force_proxy_type: None, - call: Box::new(inner), - }) -} - -fn batch_call(calls: Vec) -> RuntimeCall { - RuntimeCall::Utility(pallet_utility::Call::batch { calls }) -} - -fn batch_all_call(calls: Vec) -> RuntimeCall { - RuntimeCall::Utility(pallet_utility::Call::batch_all { calls }) -} - -fn force_batch_call(calls: Vec) -> RuntimeCall { - RuntimeCall::Utility(pallet_utility::Call::force_batch { calls }) -} - -// -- Setup helpers -- - -fn add_proxy(real: &AccountId, delegate: &AccountId) { - assert_ok!(Proxy::add_proxy( - RuntimeOrigin::signed(real.clone()), - delegate.clone().into(), - ProxyType::Any, - 0, - )); -} - -fn enable_real_pays_fee(real: &AccountId, delegate: &AccountId) { - assert_ok!(Proxy::set_real_pays_fee( - RuntimeOrigin::signed(real.clone()), - delegate.clone().into(), - true, - )); -} - -// -- Validate helpers -- - -fn validate_call( - origin: RuntimeOrigin, - call: &RuntimeCall, -) -> Result<(ValidTransaction, Val), TransactionValidityError> { - validate_call_with_info(origin, call, &call.get_dispatch_info()) -} - -fn validate_call_with_info( - origin: RuntimeOrigin, - call: &RuntimeCall, - info: &DispatchInfo, -) -> Result<(ValidTransaction, Val), TransactionValidityError> { - let ext = ChargeTransactionPaymentWrapper::::new(TaoBalance::new(0)); - let (valid_tx, val, _origin) = ext.validate( - origin, - call, - info, - 100, - (), - &TxBaseImplication(()), - TransactionSource::External, - )?; - Ok((valid_tx, val)) -} - -/// Extract the fee payer from the validate result. -fn fee_payer(val: &Val) -> AccountId { - match val { - Val::Charge { who, .. } => who.clone(), - _ => panic!("expected Val::Charge"), - } -} - -// ============================================================ -// Case 0: Non-proxy calls -// ============================================================ - -#[test] -fn non_proxy_call_charges_signer() { - new_test_ext().execute_with(|| { - let call = call_remark(); - let (_valid_tx, val) = validate_call(RuntimeOrigin::signed(signer()), &call).unwrap(); - assert_eq!(fee_payer(&val), signer()); - }); -} - -// ============================================================ -// Case 1: Simple proxy (1 level) -// ============================================================ - -#[test] -fn simple_proxy_charges_real_when_opted_in() { - new_test_ext().execute_with(|| { - add_proxy(&real_a(), &signer()); - enable_real_pays_fee(&real_a(), &signer()); - - let call = proxy_call(real_a(), call_remark()); - let (_valid_tx, val) = validate_call(RuntimeOrigin::signed(signer()), &call).unwrap(); - assert_eq!(fee_payer(&val), real_a()); - }); -} - -#[test] -fn simple_proxy_charges_signer_when_not_opted_in() { - new_test_ext().execute_with(|| { - add_proxy(&real_a(), &signer()); - // No enable_real_pays_fee - - let call = proxy_call(real_a(), call_remark()); - let (_valid_tx, val) = validate_call(RuntimeOrigin::signed(signer()), &call).unwrap(); - assert_eq!(fee_payer(&val), signer()); - }); -} - -#[test] -fn proxy_announced_always_charges_signer() { - new_test_ext().execute_with(|| { - add_proxy(&real_a(), &real_b()); - enable_real_pays_fee(&real_a(), &real_b()); - - // Fee propagation intentionally ignores proxy_announced; signer always pays. - let call = proxy_announced_call(real_b(), real_a(), call_remark()); - let (_valid_tx, val) = validate_call(RuntimeOrigin::signed(signer()), &call).unwrap(); - assert_eq!(fee_payer(&val), signer()); - }); -} - -// ============================================================ -// Case 2: Nested proxy (2 levels) -// ============================================================ - -#[test] -fn nested_proxy_charges_inner_real_when_both_opted_in() { - new_test_ext().execute_with(|| { - // Chain: signer → real_b → real_a - add_proxy(&real_b(), &signer()); - enable_real_pays_fee(&real_b(), &signer()); - add_proxy(&real_a(), &real_b()); - enable_real_pays_fee(&real_a(), &real_b()); - - let call = proxy_call(real_b(), proxy_call(real_a(), call_remark())); - let (_valid_tx, val) = validate_call(RuntimeOrigin::signed(signer()), &call).unwrap(); - assert_eq!(fee_payer(&val), real_a()); - }); -} - -#[test] -fn nested_proxy_charges_outer_real_when_only_outer_opted_in() { - new_test_ext().execute_with(|| { - add_proxy(&real_b(), &signer()); - enable_real_pays_fee(&real_b(), &signer()); - add_proxy(&real_a(), &real_b()); - // No enable_real_pays_fee for A→B - - let call = proxy_call(real_b(), proxy_call(real_a(), call_remark())); - let (_valid_tx, val) = validate_call(RuntimeOrigin::signed(signer()), &call).unwrap(); - assert_eq!(fee_payer(&val), real_b()); - }); -} - -#[test] -fn nested_proxy_charges_signer_when_neither_opted_in() { - new_test_ext().execute_with(|| { - add_proxy(&real_b(), &signer()); - add_proxy(&real_a(), &real_b()); - // No enable_real_pays_fee at all - - let call = proxy_call(real_b(), proxy_call(real_a(), call_remark())); - let (_valid_tx, val) = validate_call(RuntimeOrigin::signed(signer()), &call).unwrap(); - assert_eq!(fee_payer(&val), signer()); - }); -} - -#[test] -fn nested_proxy_charges_signer_when_only_inner_opted_in() { - new_test_ext().execute_with(|| { - add_proxy(&real_b(), &signer()); - // No enable_real_pays_fee for B→signer - add_proxy(&real_a(), &real_b()); - enable_real_pays_fee(&real_a(), &real_b()); - - let call = proxy_call(real_b(), proxy_call(real_a(), call_remark())); - let (_valid_tx, val) = validate_call(RuntimeOrigin::signed(signer()), &call).unwrap(); - // Outer RealPaysFee not set → signer pays (inner opt-in is irrelevant) - assert_eq!(fee_payer(&val), signer()); - }); -} - -// ============================================================ -// Case 3: Batch of proxy calls -// ============================================================ - -#[test] -fn batch_charges_inner_real_when_all_opted_in() { - new_test_ext().execute_with(|| { - add_proxy(&real_b(), &signer()); - enable_real_pays_fee(&real_b(), &signer()); - add_proxy(&real_a(), &real_b()); - enable_real_pays_fee(&real_a(), &real_b()); - - let batch = batch_call(vec![ - proxy_call(real_a(), call_remark()), - proxy_call(real_a(), call_remark()), - ]); - let call = proxy_call(real_b(), batch); - let (_valid_tx, val) = validate_call(RuntimeOrigin::signed(signer()), &call).unwrap(); - assert_eq!(fee_payer(&val), real_a()); - }); -} - -#[test] -fn batch_all_charges_inner_real_when_all_opted_in() { - new_test_ext().execute_with(|| { - add_proxy(&real_b(), &signer()); - enable_real_pays_fee(&real_b(), &signer()); - add_proxy(&real_a(), &real_b()); - enable_real_pays_fee(&real_a(), &real_b()); - - let batch = batch_all_call(vec![ - proxy_call(real_a(), call_remark()), - proxy_call(real_a(), call_remark()), - ]); - let call = proxy_call(real_b(), batch); - let (_valid_tx, val) = validate_call(RuntimeOrigin::signed(signer()), &call).unwrap(); - assert_eq!(fee_payer(&val), real_a()); - }); -} - -#[test] -fn force_batch_charges_inner_real_when_all_opted_in() { - new_test_ext().execute_with(|| { - add_proxy(&real_b(), &signer()); - enable_real_pays_fee(&real_b(), &signer()); - add_proxy(&real_a(), &real_b()); - enable_real_pays_fee(&real_a(), &real_b()); - - let batch = force_batch_call(vec![ - proxy_call(real_a(), call_remark()), - proxy_call(real_a(), call_remark()), - ]); - let call = proxy_call(real_b(), batch); - let (_valid_tx, val) = validate_call(RuntimeOrigin::signed(signer()), &call).unwrap(); - assert_eq!(fee_payer(&val), real_a()); - }); -} - -#[test] -fn batch_charges_outer_real_when_only_outer_opted_in() { - new_test_ext().execute_with(|| { - add_proxy(&real_b(), &signer()); - enable_real_pays_fee(&real_b(), &signer()); - add_proxy(&real_a(), &real_b()); - // No enable_real_pays_fee for A→B - - let batch = batch_call(vec![ - proxy_call(real_a(), call_remark()), - proxy_call(real_a(), call_remark()), - ]); - let call = proxy_call(real_b(), batch); - let (_valid_tx, val) = validate_call(RuntimeOrigin::signed(signer()), &call).unwrap(); - assert_eq!(fee_payer(&val), real_b()); - }); -} - -#[test] -fn batch_charges_outer_real_when_mixed_inner_reals() { - new_test_ext().execute_with(|| { - add_proxy(&real_b(), &signer()); - enable_real_pays_fee(&real_b(), &signer()); - add_proxy(&real_a(), &real_b()); - enable_real_pays_fee(&real_a(), &real_b()); - add_proxy(&other(), &real_b()); - enable_real_pays_fee(&other(), &real_b()); - - // Different inner reals → can't push deeper - let batch = batch_call(vec![ - proxy_call(real_a(), call_remark()), - proxy_call(other(), call_remark()), - ]); - let call = proxy_call(real_b(), batch); - let (_valid_tx, val) = validate_call(RuntimeOrigin::signed(signer()), &call).unwrap(); - assert_eq!(fee_payer(&val), real_b()); - }); -} - -#[test] -fn batch_charges_outer_real_when_non_proxy_in_batch() { - new_test_ext().execute_with(|| { - add_proxy(&real_b(), &signer()); - enable_real_pays_fee(&real_b(), &signer()); - - // Batch contains a non-proxy call → extract_proxy_parts fails - let batch = batch_call(vec![proxy_call(real_a(), call_remark()), call_remark()]); - let call = proxy_call(real_b(), batch); - let (_valid_tx, val) = validate_call(RuntimeOrigin::signed(signer()), &call).unwrap(); - assert_eq!(fee_payer(&val), real_b()); - }); -} - -#[test] -fn batch_charges_outer_real_when_empty() { - new_test_ext().execute_with(|| { - add_proxy(&real_b(), &signer()); - enable_real_pays_fee(&real_b(), &signer()); - - let batch = batch_call(vec![]); - let call = proxy_call(real_b(), batch); - let (_valid_tx, val) = validate_call(RuntimeOrigin::signed(signer()), &call).unwrap(); - assert_eq!(fee_payer(&val), real_b()); - }); -} - -#[test] -fn batch_charges_outer_real_when_inner_real_not_opted_in() { - new_test_ext().execute_with(|| { - add_proxy(&real_b(), &signer()); - enable_real_pays_fee(&real_b(), &signer()); - add_proxy(&real_a(), &real_b()); - // real_a has NOT opted in to pay for real_b - - // Even with same real in all batch items, if RealPaysFee not set → outer_real pays - let batch = batch_call(vec![ - proxy_call(real_a(), call_remark()), - proxy_call(real_a(), call_remark()), - ]); - let call = proxy_call(real_b(), batch); - let (_valid_tx, val) = validate_call(RuntimeOrigin::signed(signer()), &call).unwrap(); - assert_eq!(fee_payer(&val), real_b()); - }); -} - -// ============================================================ -// Priority override -// ============================================================ - -#[test] -fn priority_override_normal_dispatch() { - new_test_ext().execute_with(|| { - let call = call_remark(); - let (valid_tx, _val) = validate_call(RuntimeOrigin::signed(signer()), &call).unwrap(); - assert_eq!(valid_tx.priority, NORMAL_DISPATCH_BASE_PRIORITY); - }); -} - -#[test] -fn priority_override_operational_dispatch() { - new_test_ext().execute_with(|| { - let call = call_remark(); - let mut info = call.get_dispatch_info(); - info.class = DispatchClass::Operational; - - let (valid_tx, _val) = - validate_call_with_info(RuntimeOrigin::signed(signer()), &call, &info).unwrap(); - assert_eq!(valid_tx.priority, OPERATIONAL_DISPATCH_PRIORITY); - }); -} - -#[test] -fn priority_override_mandatory_dispatch() { - new_test_ext().execute_with(|| { - let call = call_remark(); - let mut info = call.get_dispatch_info(); - info.class = DispatchClass::Mandatory; - - let (valid_tx, _val) = - validate_call_with_info(RuntimeOrigin::signed(signer()), &call, &info).unwrap(); - // Mandatory uses the same base as Normal - assert_eq!(valid_tx.priority, NORMAL_DISPATCH_BASE_PRIORITY); - }); -} - -#[test] -fn priority_override_applies_with_real_pays_fee() { - new_test_ext().execute_with(|| { - add_proxy(&real_a(), &signer()); - enable_real_pays_fee(&real_a(), &signer()); - - let call = proxy_call(real_a(), call_remark()); - let (valid_tx, _val) = validate_call(RuntimeOrigin::signed(signer()), &call).unwrap(); - // Priority override should still apply when real pays fee - assert_eq!(valid_tx.priority, NORMAL_DISPATCH_BASE_PRIORITY); - }); -} diff --git a/scripts/install_rust.sh b/scripts/install_rust.sh index 753aa3245b..8f8964f839 100755 --- a/scripts/install_rust.sh +++ b/scripts/install_rust.sh @@ -14,7 +14,7 @@ if [[ "$(uname)" == "Darwin" ]]; then if ! which brew >/dev/null 2>&1; then /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install.sh)" fi - + brew update brew install openssl cmake llvm elif [[ "$(uname)" == "Linux" ]]; then diff --git a/scripts/release_notes.rs b/scripts/release_notes.rs index 220f1ad4bb..a3f9c40955 100755 --- a/scripts/release_notes.rs +++ b/scripts/release_notes.rs @@ -85,6 +85,11 @@ fn main() { .filter(|s| { !s.is_empty() && s.starts_with("Merge pull request #") + && !s.ends_with("from RaoFoundation/devnet-ready") + && !s.ends_with("from RaoFoundation/testnet-ready") + && !s.ends_with("from RaoFoundation/devnet") + && !s.ends_with("from RaoFoundation/testnet") + // Merges predating the opentensor -> RaoFoundation org move && !s.ends_with("from opentensor/devnet-ready") && !s.ends_with("from opentensor/testnet-ready") && !s.ends_with("from opentensor/devnet") diff --git a/scripts/update-deps-to-path.sh b/scripts/update-deps-to-path.sh index a1eab9b99c..f567fed51b 100755 --- a/scripts/update-deps-to-path.sh +++ b/scripts/update-deps-to-path.sh @@ -26,10 +26,10 @@ typeset -A PKG_PATHS for SOURCE_PATH in "$@"; do SOURCE_PATH="$(cd "$SOURCE_PATH" && pwd)" echo "Scanning $SOURCE_PATH for packages..." >&2 - + for cargo_toml in $(find "$SOURCE_PATH" -name "Cargo.toml" -type f 2>/dev/null); do pkg_name=$(yq -p toml -o yaml '.package.name // ""' "$cargo_toml" 2>/dev/null | tr -d '"') - + if [[ -n "$pkg_name" && "$pkg_name" != "null" ]]; then pkg_dir="$(dirname "$cargo_toml")" PKG_PATHS[$pkg_name]="$pkg_dir" @@ -49,19 +49,19 @@ while IFS= read -r line; do if [[ "$line" =~ ^([a-zA-Z0-9_-]+|\"[^\"]+\")\ *=\ *\{.*git\ *=\ *\" ]]; then # Extract package name (handle both quoted and unquoted) dep_name=$(echo "$line" | sed -E 's/^"?([a-zA-Z0-9_-]+)"? *=.*/\1/') - + # Check for package alias if [[ "$line" =~ package\ *=\ *\"([^\"]+)\" ]]; then lookup_name="${match[1]}" else lookup_name="$dep_name" fi - + # Check if we have this package if [[ -n "${PKG_PATHS[$lookup_name]}" ]]; then pkg_path="${PKG_PATHS[$lookup_name]}" echo " $dep_name -> $pkg_path" >&2 - + # Extract features/default-features/package if present extras="" if [[ "$line" =~ default-features\ *=\ *false ]]; then @@ -73,7 +73,7 @@ while IFS= read -r line; do if [[ "$line" =~ features\ *=\ *\[([^\]]*)\] ]]; then extras="$extras, features = [${match[1]}]" fi - + # Output new line with just path echo "${dep_name} = { path = \"${pkg_path}\"${extras} }" else diff --git a/support/macros/Cargo.toml b/support/macros/Cargo.toml index 4ba891a6ea..41013fed79 100644 --- a/support/macros/Cargo.toml +++ b/support/macros/Cargo.toml @@ -5,7 +5,7 @@ edition.workspace = true license = "MIT" description = "support macros for Subtensor" -repository = "https://github.com/opentensor/subtensor" +repository = "https://github.com/RaoFoundation/subtensor" homepage = "https://bittensor.com/" [lib] @@ -16,6 +16,7 @@ syn = { workspace = true, features = [ "full", "visit-mut", "visit", + "clone-impls", "extra-traits", "parsing", "printing", diff --git a/support/macros/src/call_filter_group.rs b/support/macros/src/call_filter_group.rs new file mode 100644 index 0000000000..37bbf994cc --- /dev/null +++ b/support/macros/src/call_filter_group.rs @@ -0,0 +1,398 @@ +use proc_macro2::TokenStream as TokenStream2; +use quote::quote; +use syn::{ + Error, Expr, Ident, Path, Result, Token, bracketed, parenthesized, + parse::{Parse, ParseStream}, + punctuated::Punctuated, +}; + +/// Parsed input for one call filter group. +pub struct CallFilterGroupInput { + group: Ident, + rules: Punctuated, +} + +/// One allowed call, optionally guarded by a simple condition. +struct CallRule { + target: RuntimeCallRef, + condition: Option, +} + +/// Reference to `RuntimeCall::Variant(pallet::Call::method)`. +#[derive(Clone)] +struct RuntimeCallRef { + variant: Ident, + call_enum: Path, + call: Ident, +} + +/// Constraints supported by the generated filter and metadata. +enum CallConstraint { + ParamLessThan { + field: Ident, + limit: Expr, + }, + NestedCallMustBe { + field: Ident, + target: RuntimeCallRef, + }, +} + +impl Parse for CallFilterGroupInput { + fn parse(input: ParseStream) -> Result { + let group = input.parse()?; + input.parse::()?; + + let content; + bracketed!(content in input); + let rules = content.parse_terminated(CallRule::parse, Token![,])?; + + Ok(Self { group, rules }) + } +} + +impl Parse for CallRule { + fn parse(input: ParseStream) -> Result { + let target = input.parse()?; + let condition = if input.peek(Token![where]) { + input.parse::()?; + Some(input.parse()?) + } else { + None + }; + + Ok(Self { target, condition }) + } +} + +impl Parse for RuntimeCallRef { + fn parse(input: ParseStream) -> Result { + let runtime_call = input.parse::()?; + if runtime_call != "RuntimeCall" { + return Err(Error::new_spanned( + runtime_call, + "expected RuntimeCall::Variant(call_enum::Call::method)", + )); + } + + input.parse::()?; + let variant = input.parse()?; + + let content; + parenthesized!(content in input); + let call_path = content.parse()?; + if !content.is_empty() { + return Err(content.error("expected a single call path")); + } + + let (call_enum, call) = split_call_path(call_path)?; + Ok(Self { + variant, + call_enum, + call, + }) + } +} + +impl Parse for CallConstraint { + fn parse(input: ParseStream) -> Result { + let field = input.parse::()?; + + if field == "nested" { + let content; + parenthesized!(content in input); + let nested_field = content.parse()?; + if !content.is_empty() { + return Err(content.error("expected a single nested call field")); + } + + input.parse::()?; + let target = input.parse()?; + + return Ok(Self::NestedCallMustBe { + field: nested_field, + target, + }); + } + + input.parse::()?; + let limit = input.parse()?; + Ok(Self::ParamLessThan { field, limit }) + } +} + +/// Generate both impls for a filter group: +/// - `Contains` for execution-time filtering +/// - `CallFilterMetadata` for runtime API discovery +pub fn generate(input: CallFilterGroupInput) -> Result { + let group = input.group; + let contains_rules = input.rules.iter().map(generate_contains_rule); + let call_infos = input.rules.iter().map(generate_call_info); + + Ok(quote! { + pub(super) struct #group; + + impl frame_support::traits::Contains for #group { + fn contains(call: &crate::RuntimeCall) -> bool { + false #( || #contains_rules )* + } + } + + impl subtensor_runtime_common::CallFilterMetadata for #group { + fn call_infos() -> ::alloc::vec::Vec { + ::alloc::vec![#(#call_infos),*] + } + } + }) +} + +/// Build the executable predicate for one allowed call. +fn generate_contains_rule(rule: &CallRule) -> TokenStream2 { + match &rule.condition { + None => { + let pattern = call_pattern(&rule.target, None); + quote! { matches!(call, #pattern) } + } + Some(CallConstraint::ParamLessThan { field, limit }) => { + let pattern = call_pattern(&rule.target, Some(field)); + quote! { + match call { + #pattern => *#field < #limit, + _ => false, + } + } + } + Some(CallConstraint::NestedCallMustBe { field, target }) => { + let source_pattern = call_pattern(&rule.target, Some(field)); + let target_pattern = call_pattern(target, None); + quote! { + match call { + #source_pattern => matches!(#field.as_ref(), #target_pattern), + _ => false, + } + } + } + } +} + +/// Build the metadata entry for one allowed call. +fn generate_call_info(rule: &CallRule) -> TokenStream2 { + let base = call_info_expr(&rule.target); + + match &rule.condition { + None => base, + Some(CallConstraint::ParamLessThan { field, limit }) => quote! {{ + let mut info = #base; + info.constraint = Some(subtensor_runtime_common::CallConstraint::ParamLessThan { + param_name: stringify!(#field).as_bytes().to_vec(), + limit: Into::::into(#limit) as u128, + }); + info + }}, + Some(CallConstraint::NestedCallMustBe { field, target }) => { + let nested = call_info_expr(target); + quote! {{ + let mut info = #base; + let nested = #nested; + info.constraint = Some(subtensor_runtime_common::CallConstraint::NestedCallMustBe { + param_name: stringify!(#field).as_bytes().to_vec(), + pallet_name: nested.pallet_name, + call_name: nested.call_name, + }); + info + }} + } + } +} + +/// Convert a call reference into a `RuntimeCall` match pattern. +fn call_pattern(target: &RuntimeCallRef, field: Option<&Ident>) -> TokenStream2 { + let variant = &target.variant; + let call_enum = &target.call_enum; + let call = &target.call; + + match field { + Some(field) => quote! { + crate::RuntimeCall::#variant(#call_enum::#call { #field, .. }) + }, + None => quote! { + crate::RuntimeCall::#variant(#call_enum::#call { .. }) + }, + } +} + +/// Convert a call reference into a runtime lookup for pallet/call indexes. +fn call_info_expr(target: &RuntimeCallRef) -> TokenStream2 { + let variant = &target.variant; + let call_enum = &target.call_enum; + let call = &target.call; + + quote! { + subtensor_runtime_common::call_info_by_name::< + crate::#variant, + #call_enum, + >(stringify!(#call)) + } +} + +/// Split `pallet::Call::method` into `pallet::Call` and `method`. +fn split_call_path(call_path: Path) -> Result<(Path, Ident)> { + if call_path.segments.len() < 2 { + return Err(Error::new_spanned( + call_path, + "expected a call path like pallet_name::Call::method", + )); + } + + let mut call_enum_segments = Punctuated::new(); + for segment in call_path + .segments + .iter() + .take(call_path.segments.len().saturating_sub(1)) + { + call_enum_segments.push((*segment).clone()); + } + + #[allow(clippy::expect_used)] + let call = call_path + .segments + .last() + .expect("length checked above") + .ident + .clone(); + + Ok(( + Path { + leading_colon: call_path.leading_colon, + segments: call_enum_segments, + }, + call, + )) +} + +#[cfg(test)] +mod tests { + #![allow(clippy::unwrap_used, clippy::indexing_slicing)] + + use quote::quote; + + use super::*; + + fn generate_tokens(input: TokenStream2) -> String { + let input = syn::parse2::(input).unwrap(); + generate(input).unwrap().to_string() + } + + #[test] + fn parses_group_name_and_call_targets() { + let input = syn::parse2::(quote! { + StakingOperations, [ + RuntimeCall::SubtensorModule(pallet_subtensor::Call::add_stake), + RuntimeCall::SubtensorModule(pallet_subtensor::Call::remove_stake), + ] + }) + .unwrap(); + + assert_eq!(input.group, "StakingOperations"); + assert_eq!(input.rules.len(), 2); + assert_eq!(input.rules[0].target.variant, "SubtensorModule"); + assert_eq!(input.rules[0].target.call, "add_stake"); + assert!(input.rules[0].condition.is_none()); + } + + #[test] + fn generated_group_implements_contains_and_shared_metadata() { + let generated = generate_tokens(quote! { + StakingOperations, [ + RuntimeCall::SubtensorModule(pallet_subtensor::Call::add_stake), + ] + }); + + assert!(generated.contains( + "impl frame_support :: traits :: Contains < crate :: RuntimeCall > for StakingOperations" + )); + assert!( + generated.contains( + "impl subtensor_runtime_common :: CallFilterMetadata for StakingOperations" + ) + ); + assert!(generated.contains( + "matches ! (call , crate :: RuntimeCall :: SubtensorModule (pallet_subtensor :: Call :: add_stake" + )); + assert!(generated.contains("subtensor_runtime_common :: call_info_by_name")); + assert!(generated.contains("crate :: SubtensorModule")); + assert!(generated.contains("pallet_subtensor :: Call < crate :: Runtime >")); + assert!(generated.contains("stringify ! (add_stake)")); + } + + #[test] + fn generated_group_supports_param_less_than_condition() { + let generated = generate_tokens(quote! { + SmallTransfers, [ + RuntimeCall::Balances(pallet_balances::Call::transfer_allow_death) + where value < SMALL_TRANSFER_LIMIT, + ] + }); + + assert!(generated.contains("* value < SMALL_TRANSFER_LIMIT")); + assert!(generated.contains("subtensor_runtime_common :: CallConstraint :: ParamLessThan")); + assert!(generated.contains("param_name : stringify ! (value)")); + assert!( + generated.contains("limit : Into :: < u64 > :: into (SMALL_TRANSFER_LIMIT) as u128") + ); + } + + #[test] + fn generated_group_supports_nested_call_condition() { + let generated = generate_tokens(quote! { + SudoUncheckedSetCodeOperation, [ + RuntimeCall::Sudo(pallet_sudo::Call::sudo_unchecked_weight) + where nested(call) == RuntimeCall::System(frame_system::Call::set_code), + ] + }); + + assert!(generated.contains( + "matches ! (call . as_ref () , crate :: RuntimeCall :: System (frame_system :: Call :: set_code" + )); + assert!( + generated.contains("subtensor_runtime_common :: CallConstraint :: NestedCallMustBe") + ); + assert!(generated.contains("param_name : stringify ! (call)")); + assert!(generated.contains("pallet_name : nested . pallet_name")); + assert!(generated.contains("call_name : nested . call_name")); + } + + #[test] + fn rejects_non_runtime_call_target() { + let err = match syn::parse2::(quote! { + BadGroup, [ + Call::SubtensorModule(pallet_subtensor::Call::add_stake), + ] + }) { + Ok(_) => panic!("invalid runtime call target should fail to parse"), + Err(err) => err, + }; + + assert!( + err.to_string() + .contains("expected RuntimeCall::Variant(call_enum::Call::method)") + ); + } + + #[test] + fn rejects_call_paths_without_call_name() { + let err = match syn::parse2::(quote! { + BadGroup, [ + RuntimeCall::SubtensorModule(add_stake), + ] + }) { + Ok(_) => panic!("call path without call enum should fail to parse"), + Err(err) => err, + }; + + assert!( + err.to_string() + .contains("expected a call path like pallet_name::Call::method") + ); + } +} diff --git a/support/macros/src/lib.rs b/support/macros/src/lib.rs index 2e19a9fbce..0580e1fd62 100644 --- a/support/macros/src/lib.rs +++ b/support/macros/src/lib.rs @@ -3,12 +3,10 @@ use proc_macro2::TokenStream as TokenStream2; use quote::ToTokens; use syn::{Error, ItemStruct, LitStr, Result, parse2, visit_mut::visit_item_struct_mut}; +mod call_filter_group; mod visitor; use visitor::*; -mod proxy_filter; -use proxy_filter::ProxyFilterInput; - /// Freezes the layout of a struct to the current hash of its fields, ensuring that future /// changes require updating the hash. /// @@ -30,6 +28,18 @@ pub fn freeze_struct(attr: TokenStream, tokens: TokenStream) -> TokenStream { } } +/// Defines a reusable runtime call filter group and derives its metadata from the same allowlist. +/// +/// Example: `call_filter_group!(GroupName, [RuntimeCall::Foobar(pallet_foobar::Call::some_call)]);` +#[proc_macro] +pub fn call_filter_group(input: TokenStream) -> TokenStream { + let parsed = syn::parse_macro_input!(input as call_filter_group::CallFilterGroupInput); + match call_filter_group::generate(parsed) { + Ok(tokens) => tokens.into(), + Err(err) => err.to_compile_error().into(), + } +} + fn freeze_struct_impl( attr: impl Into, tokens: impl Into, @@ -72,15 +82,3 @@ fn freeze_struct_impl( } Ok(item) } - -/// Defines proxy filter rules as a single source of truth, generating both the -/// `proxy_type_filter()` function (used by `InstanceFilter::filter()`) and the -/// `get_all_proxy_filters()` function (used by the Runtime API). -/// -/// This ensures the on-chain filtering logic and the off-chain API metadata -/// can never drift apart. -#[proc_macro] -pub fn define_proxy_filters(input: TokenStream) -> TokenStream { - let parsed = syn::parse_macro_input!(input as ProxyFilterInput); - parsed.generate().into() -} diff --git a/support/macros/src/proxy_filter.rs b/support/macros/src/proxy_filter.rs deleted file mode 100644 index dc03db780d..0000000000 --- a/support/macros/src/proxy_filter.rs +++ /dev/null @@ -1,540 +0,0 @@ -//! Proc-macro implementation for `define_proxy_filters!`. -//! -//! This module provides parsing and code generation for a declarative DSL that -//! defines proxy filter rules as a single source of truth. From one definition, -//! it generates: -//! - `proxy_type_filter()` — the runtime filtering function used by `InstanceFilter::filter()` -//! - `get_all_proxy_filters()` — the Runtime API data function returning filter metadata -use proc_macro2::TokenStream as TokenStream2; -use quote::quote; -use syn::{ - Expr, Ident, Result, Token, - parse::{Parse, ParseStream}, -}; - -// ============================================================================ -// DSL AST types -// ============================================================================ - -/// Top-level input: pallets { ... } followed by filter rules -pub struct ProxyFilterInput { - pub pallets: Vec, - pub rules: Vec, -} - -/// Maps a short pallet name to its RuntimeCall variant and module path -/// e.g. `Balances => (Balances, pallet_balances)` -pub struct PalletDef { - pub name: Ident, - pub runtime_variant: Ident, - pub module: Ident, -} - -/// A single filter rule for one ProxyType variant -pub struct FilterRule { - pub proxy_type: Ident, - pub kind: FilterKind, -} - -pub enum FilterKind { - AllowAll, - DenyAll, - Allow { - calls: Vec, - exceptions: Vec, - }, - Deny { - calls: Vec, - }, - AllowConditional { - calls: Vec, - }, - AllowNested { - calls: Vec, - }, -} - -/// Reference to a specific call or wildcard pallet -pub enum CallRef { - Wildcard(Ident), - Specific(Ident, Ident), -} - -/// Conditional call: `Pallet::call where (field) < LIMIT` -pub struct ConditionalCallRef { - pub pallet: Ident, - pub call: Ident, - pub field: Ident, - pub limit: Expr, -} - -/// Nested call: `Pallet::call where nested(field) == TargetPallet::target_call` -pub struct NestedCallRef { - pub pallet: Ident, - pub call: Ident, - pub field: Ident, - pub target_pallet: Ident, - pub target_call: Ident, -} - -// ============================================================================ -// Parsing -// ============================================================================ - -mod kw { - syn::custom_keyword!(pallets); - syn::custom_keyword!(allow_all); - syn::custom_keyword!(deny_all); - syn::custom_keyword!(allow); - syn::custom_keyword!(deny); - syn::custom_keyword!(allow_conditional); - syn::custom_keyword!(allow_nested); - syn::custom_keyword!(except); - syn::custom_keyword!(nested); -} - -impl Parse for ProxyFilterInput { - fn parse(input: ParseStream) -> Result { - input.parse::()?; - let pallet_content; - syn::braced!(pallet_content in input); - let mut pallets = Vec::new(); - while !pallet_content.is_empty() { - pallets.push(pallet_content.parse::()?); - if pallet_content.peek(Token![,]) { - pallet_content.parse::()?; - } - } - - let mut rules = Vec::new(); - while !input.is_empty() { - rules.push(input.parse::()?); - } - - Ok(ProxyFilterInput { pallets, rules }) - } -} - -impl Parse for PalletDef { - fn parse(input: ParseStream) -> Result { - let name: Ident = input.parse()?; - input.parse::]>()?; - let content; - syn::parenthesized!(content in input); - let runtime_variant: Ident = content.parse()?; - content.parse::()?; - let module: Ident = content.parse()?; - Ok(PalletDef { - name, - runtime_variant, - module, - }) - } -} - -impl Parse for FilterRule { - fn parse(input: ParseStream) -> Result { - let proxy_type: Ident = input.parse()?; - input.parse::]>()?; - - let kind = if input.peek(kw::allow_all) { - input.parse::()?; - input.parse::()?; - FilterKind::AllowAll - } else if input.peek(kw::deny_all) { - input.parse::()?; - input.parse::()?; - FilterKind::DenyAll - } else if input.peek(kw::allow_conditional) { - input.parse::()?; - let content; - syn::braced!(content in input); - let calls = parse_conditional_calls(&content)?; - FilterKind::AllowConditional { calls } - } else if input.peek(kw::allow_nested) { - input.parse::()?; - let content; - syn::braced!(content in input); - let calls = parse_nested_calls(&content)?; - FilterKind::AllowNested { calls } - } else if input.peek(kw::allow) { - input.parse::()?; - let content; - syn::braced!(content in input); - let calls = parse_call_refs(&content)?; - - let exceptions = if input.peek(kw::except) { - input.parse::()?; - let exc_content; - syn::braced!(exc_content in input); - parse_call_refs(&exc_content)? - } else { - Vec::new() - }; - - FilterKind::Allow { calls, exceptions } - } else if input.peek(kw::deny) { - input.parse::()?; - let content; - syn::braced!(content in input); - let calls = parse_call_refs(&content)?; - FilterKind::Deny { calls } - } else { - return Err(input.error( - "expected allow_all, deny_all, allow, deny, allow_conditional, or allow_nested", - )); - }; - - // Consume optional trailing semicolon after braced rules - if input.peek(Token![;]) { - input.parse::()?; - } - - Ok(FilterRule { proxy_type, kind }) - } -} - -fn parse_call_refs(input: ParseStream) -> Result> { - let mut calls = Vec::new(); - while !input.is_empty() { - let pallet: Ident = input.parse()?; - input.parse::()?; - if input.peek(Token![*]) { - input.parse::()?; - calls.push(CallRef::Wildcard(pallet)); - } else { - let call: Ident = input.parse()?; - calls.push(CallRef::Specific(pallet, call)); - } - if input.peek(Token![,]) { - input.parse::()?; - } - } - Ok(calls) -} - -fn parse_conditional_calls(input: ParseStream) -> Result> { - let mut calls = Vec::new(); - while !input.is_empty() { - let pallet: Ident = input.parse()?; - input.parse::()?; - let call: Ident = input.parse()?; - // parse: where (field) < LIMIT - input.parse::()?; - let field_content; - syn::parenthesized!(field_content in input); - let field: Ident = field_content.parse()?; - input.parse::()?; - let limit: Expr = input.parse()?; - calls.push(ConditionalCallRef { - pallet, - call, - field, - limit, - }); - if input.peek(Token![,]) { - input.parse::()?; - } - } - Ok(calls) -} - -fn parse_nested_calls(input: ParseStream) -> Result> { - let mut calls = Vec::new(); - while !input.is_empty() { - let pallet: Ident = input.parse()?; - input.parse::()?; - let call: Ident = input.parse()?; - // parse: where nested(field) == TargetPallet::target_call - input.parse::()?; - input.parse::()?; - let field_content; - syn::parenthesized!(field_content in input); - let field: Ident = field_content.parse()?; - input.parse::()?; - let target_pallet: Ident = input.parse()?; - input.parse::()?; - let target_call: Ident = input.parse()?; - calls.push(NestedCallRef { - pallet, - call, - field, - target_pallet, - target_call, - }); - if input.peek(Token![,]) { - input.parse::()?; - } - } - Ok(calls) -} - -// ============================================================================ -// Code generation -// ============================================================================ - -impl ProxyFilterInput { - pub fn generate(self) -> TokenStream2 { - let filter_fn = self.generate_filter_fn(); - let data_fn = self.generate_data_fn(); - - quote! { - #filter_fn - #data_fn - } - } - - fn find_pallet(&self, name: &Ident) -> &PalletDef { - self.pallets - .iter() - .find(|p| p.name == *name) - .unwrap_or_else(|| panic!("Pallet '{}' not found in pallets block", name)) - } - - // ======================================================================== - // filter function generation - // ======================================================================== - - fn generate_filter_fn(&self) -> TokenStream2 { - let arms: Vec = self.rules.iter().map(|rule| { - let pt = &rule.proxy_type; - match &rule.kind { - FilterKind::AllowAll => quote! { - ProxyType::#pt => true, - }, - FilterKind::DenyAll => quote! { - ProxyType::#pt => false, - }, - FilterKind::Allow { calls, exceptions } => { - let patterns = self.call_refs_to_patterns(calls); - if exceptions.is_empty() { - quote! { - ProxyType::#pt => matches!(c, #(#patterns)|*), - } - } else { - let exc_patterns = self.call_refs_to_patterns(exceptions); - quote! { - ProxyType::#pt => { - matches!(c, #(#patterns)|*) && !matches!(c, #(#exc_patterns)|*) - } - } - } - } - FilterKind::Deny { calls } => { - let patterns = self.call_refs_to_patterns(calls); - quote! { - ProxyType::#pt => !matches!(c, #(#patterns)|*), - } - } - FilterKind::AllowConditional { calls } => { - let arms = calls.iter().map(|cond| { - let pallet_def = self.find_pallet(&cond.pallet); - let variant = &pallet_def.runtime_variant; - let module = &pallet_def.module; - let call_name = &cond.call; - let field = &cond.field; - let limit = &cond.limit; - quote! { - RuntimeCall::#variant(#module::Call::#call_name { #field, .. }) => { - *#field < #limit - } - } - }); - quote! { - ProxyType::#pt => match c { - #(#arms)* - _ => false, - }, - } - } - FilterKind::AllowNested { calls } => { - let arms = calls.iter().map(|nested| { - let pallet_def = self.find_pallet(&nested.pallet); - let variant = &pallet_def.runtime_variant; - let module = &pallet_def.module; - let call_name = &nested.call; - let field = &nested.field; - let target_pallet_def = self.find_pallet(&nested.target_pallet); - let target_variant = &target_pallet_def.runtime_variant; - let target_module = &target_pallet_def.module; - let target_call_name = &nested.target_call; - quote! { - RuntimeCall::#variant(#module::Call::#call_name { #field, .. }) => { - let inner_call: RuntimeCall = *#field.clone(); - matches!( - inner_call, - RuntimeCall::#target_variant(#target_module::Call::#target_call_name { .. }) - ) - } - } - }); - quote! { - ProxyType::#pt => match c { - #(#arms)* - _ => false, - }, - } - } - } - }).collect(); - - quote! { - fn proxy_type_filter(pt: &ProxyType, c: &RuntimeCall) -> bool { - match pt { - #(#arms)* - } - } - } - } - - fn call_refs_to_patterns(&self, calls: &[CallRef]) -> Vec { - calls - .iter() - .map(|call_ref| match call_ref { - CallRef::Wildcard(pallet) => { - let pallet_def = self.find_pallet(pallet); - let variant = &pallet_def.runtime_variant; - quote! { RuntimeCall::#variant(..) } - } - CallRef::Specific(pallet, call) => { - let pallet_def = self.find_pallet(pallet); - let variant = &pallet_def.runtime_variant; - let module = &pallet_def.module; - quote! { RuntimeCall::#variant(#module::Call::#call { .. }) } - } - }) - .collect() - } - - // ======================================================================== - // data function generation - // ======================================================================== - - fn generate_data_fn(&self) -> TokenStream2 { - let entries: Vec = self - .rules - .iter() - .map(|rule| { - let pt = &rule.proxy_type; - let (mode, calls_expr, exceptions_expr) = match &rule.kind { - FilterKind::AllowAll => ( - quote! { FilterMode::AllowAll }, - quote! { Vec::new() }, - quote! { Vec::new() }, - ), - FilterKind::DenyAll => ( - quote! { FilterMode::DenyAll }, - quote! { Vec::new() }, - quote! { Vec::new() }, - ), - FilterKind::Allow { calls, exceptions } => ( - quote! { FilterMode::Allow }, - self.call_refs_to_data(calls), - self.call_refs_to_data(exceptions), - ), - FilterKind::Deny { calls } => ( - quote! { FilterMode::Deny }, - self.call_refs_to_data(calls), - quote! { Vec::new() }, - ), - FilterKind::AllowConditional { calls } => { - let data = self.conditional_calls_to_data(calls); - (quote! { FilterMode::Allow }, data, quote! { Vec::new() }) - } - FilterKind::AllowNested { calls } => { - let data = self.nested_calls_to_data(calls); - (quote! { FilterMode::Allow }, data, quote! { Vec::new() }) - } - }; - - quote! { - ProxyFilterInfo { - proxy_type: ProxyType::#pt.into(), - name: alloc::format!("{:?}", ProxyType::#pt).into_bytes(), - deprecated: ProxyType::#pt.is_deprecated(), - filter_mode: #mode, - calls: #calls_expr, - exceptions: #exceptions_expr, - } - } - }) - .collect(); - - quote! { - pub fn get_all_proxy_filters() -> Vec { - vec![ - #(#entries),* - ] - } - } - } - - fn call_refs_to_data(&self, calls: &[CallRef]) -> TokenStream2 { - let items: Vec = calls - .iter() - .map(|call_ref| match call_ref { - CallRef::Wildcard(pallet) => { - let pallet_def = self.find_pallet(pallet); - let runtime_variant = &pallet_def.runtime_variant; - quote! { pallet_wildcard::<#runtime_variant>() } - } - CallRef::Specific(pallet, call) => { - let pallet_def = self.find_pallet(pallet); - let runtime_variant = &pallet_def.runtime_variant; - let module = &pallet_def.module; - let call_str = call.to_string(); - quote! { - call_info_by_name::<#runtime_variant, #module::Call>(#call_str) - } - } - }) - .collect(); - quote! { vec![#(#items),*] } - } - - fn conditional_calls_to_data(&self, calls: &[ConditionalCallRef]) -> TokenStream2 { - let items: Vec = calls - .iter() - .map(|cond| { - let pallet_def = self.find_pallet(&cond.pallet); - let runtime_variant = &pallet_def.runtime_variant; - let module = &pallet_def.module; - let call_str = cond.call.to_string(); - let field_str = cond.field.to_string(); - let limit = &cond.limit; - quote! { - call_info_by_name_conditional::<#runtime_variant, #module::Call>( - #call_str, - CallCondition::ParamLessThan { - param_name: #field_str.as_bytes().to_vec(), - limit: Into::::into(#limit) as u128, - }, - ) - } - }) - .collect(); - quote! { vec![#(#items),*] } - } - - fn nested_calls_to_data(&self, calls: &[NestedCallRef]) -> TokenStream2 { - let items: Vec = calls.iter().map(|nested| { - let pallet_def = self.find_pallet(&nested.pallet); - let runtime_variant = &pallet_def.runtime_variant; - let module = &pallet_def.module; - let call_str = nested.call.to_string(); - let target_pallet_def = self.find_pallet(&nested.target_pallet); - let target_runtime_variant = &target_pallet_def.runtime_variant; - let target_call_str = nested.target_call.to_string(); - quote! { - call_info_by_name_conditional::<#runtime_variant, #module::Call>( - #call_str, - CallCondition::NestedCallMustBe { - pallet_name: <#target_runtime_variant as PalletInfoAccess>::name().as_bytes().to_vec(), - call_name: #target_call_str.as_bytes().to_vec(), - }, - ) - } - }).collect(); - quote! { vec![#(#items),*] } - } -} diff --git a/support/procedural-fork/src/runtime/mod.rs b/support/procedural-fork/src/runtime/mod.rs index a96b21cd19..13bc1ea4ec 100644 --- a/support/procedural-fork/src/runtime/mod.rs +++ b/support/procedural-fork/src/runtime/mod.rs @@ -44,9 +44,9 @@ //! ```ignore //! +----------+ //! | Implicit | -//! +----------+ -//! | -//! v +//! +----------+ +//! | +//! v //! +----------+ //! | Explicit | //! +----------+ @@ -101,7 +101,7 @@ //! //! #[runtime::pallet_index(0)] //! pub type System = frame_system; -//! +//! //! #[runtime::pallet_index(1)] //! pub type Balances = pallet_balances; //! } diff --git a/support/tools/Cargo.toml b/support/tools/Cargo.toml index 065b3532d1..4d31dd130c 100644 --- a/support/tools/Cargo.toml +++ b/support/tools/Cargo.toml @@ -5,7 +5,7 @@ edition.workspace = true license = "MIT" description = "support tools for Subtensor" -repository = "https://github.com/opentensor/subtensor" +repository = "https://github.com/RaoFoundation/subtensor" homepage = "https://bittensor.com" [[bin]] diff --git a/support/weight-tools/Cargo.toml b/support/weight-tools/Cargo.toml index 9e68d2d205..e571d9ff91 100644 --- a/support/weight-tools/Cargo.toml +++ b/support/weight-tools/Cargo.toml @@ -5,7 +5,7 @@ edition.workspace = true license = "MIT" description = "Lightweight tools for benchmark weight management" -repository = "https://github.com/opentensor/subtensor" +repository = "https://github.com/RaoFoundation/subtensor" homepage = "https://bittensor.com" [[bin]] diff --git a/ts-tests/ink/bittensor.json b/ts-tests/ink/bittensor.json index 1a543547bc..7ab8775834 100644 --- a/ts-tests/ink/bittensor.json +++ b/ts-tests/ink/bittensor.json @@ -1,6 +1,6 @@ { "source": { - "hash": "0x69edf9f009ca08b785fad0aacb75a1d8ddad6d231848c8ca12b75f0bfc943b86", + "hash": "0xf5ded81165b29a8cdd592d22291ba8275aa9898162d916d6f3afdfe8bd7e5243", "language": "ink! 5.1.1", "compiler": "rustc 1.89.0", "build_info": { @@ -76,19 +76,19 @@ "displayName": [ "BlockNumber" ], - "type": 23 + "type": 34 }, "chainExtension": { "displayName": [ "ChainExtension" ], - "type": 24 + "type": 35 }, "hash": { "displayName": [ "Hash" ], - "type": 22 + "type": 33 }, "maxEventTopics": 4, "staticBufferSize": 16384, @@ -1563,6 +1563,98 @@ "type": 17 }, "selector": "0x59392a8e" + }, + { + "args": [ + { + "label": "netuid", + "type": { + "displayName": [ + "u16" + ], + "type": 6 + } + } + ], + "default": false, + "docs": [], + "label": "get_subnet_registration_state", + "mutates": false, + "payable": false, + "returnType": { + "displayName": [ + "ink", + "MessageResult" + ], + "type": 22 + }, + "selector": "0x2244b542" + }, + { + "args": [ + { + "label": "coldkey", + "type": { + "displayName": [], + "type": 4 + } + }, + { + "label": "netuid", + "type": { + "displayName": [ + "u16" + ], + "type": 6 + } + } + ], + "default": false, + "docs": [], + "label": "get_coldkey_lock", + "mutates": false, + "payable": false, + "returnType": { + "displayName": [ + "ink", + "MessageResult" + ], + "type": 25 + }, + "selector": "0x29513d8d" + }, + { + "args": [ + { + "label": "coldkey", + "type": { + "displayName": [], + "type": 4 + } + }, + { + "label": "netuid", + "type": { + "displayName": [ + "u16" + ], + "type": 6 + } + } + ], + "default": false, + "docs": [], + "label": "get_stake_availability", + "mutates": false, + "payable": false, + "returnType": { + "displayName": [ + "ink", + "MessageResult" + ], + "type": 30 + }, + "selector": "0xdb1d3b2c" } ] }, @@ -2153,6 +2245,394 @@ }, { "id": 22, + "type": { + "def": { + "variant": { + "variants": [ + { + "fields": [ + { + "type": 23 + } + ], + "index": 0, + "name": "Ok" + }, + { + "fields": [ + { + "type": 3 + } + ], + "index": 1, + "name": "Err" + } + ] + } + }, + "params": [ + { + "name": "T", + "type": 23 + }, + { + "name": "E", + "type": 3 + } + ], + "path": [ + "Result" + ] + } + }, + { + "id": 23, + "type": { + "def": { + "variant": { + "variants": [ + { + "fields": [ + { + "type": 24 + } + ], + "index": 0, + "name": "Ok" + }, + { + "fields": [ + { + "type": 16 + } + ], + "index": 1, + "name": "Err" + } + ] + } + }, + "params": [ + { + "name": "T", + "type": 24 + }, + { + "name": "E", + "type": 16 + } + ], + "path": [ + "Result" + ] + } + }, + { + "id": 24, + "type": { + "def": { + "composite": { + "fields": [ + { + "name": "netuid", + "type": 6, + "typeName": "u16" + }, + { + "name": "exists", + "type": 15, + "typeName": "bool" + }, + { + "name": "registered_subnet_counter", + "type": 14, + "typeName": "u64" + } + ] + } + }, + "path": [ + "bittensor", + "SubnetRegistrationState" + ] + } + }, + { + "id": 25, + "type": { + "def": { + "variant": { + "variants": [ + { + "fields": [ + { + "type": 26 + } + ], + "index": 0, + "name": "Ok" + }, + { + "fields": [ + { + "type": 3 + } + ], + "index": 1, + "name": "Err" + } + ] + } + }, + "params": [ + { + "name": "T", + "type": 26 + }, + { + "name": "E", + "type": 3 + } + ], + "path": [ + "Result" + ] + } + }, + { + "id": 26, + "type": { + "def": { + "variant": { + "variants": [ + { + "fields": [ + { + "type": 27 + } + ], + "index": 0, + "name": "Ok" + }, + { + "fields": [ + { + "type": 16 + } + ], + "index": 1, + "name": "Err" + } + ] + } + }, + "params": [ + { + "name": "T", + "type": 27 + }, + { + "name": "E", + "type": 16 + } + ], + "path": [ + "Result" + ] + } + }, + { + "id": 27, + "type": { + "def": { + "variant": { + "variants": [ + { + "index": 0, + "name": "None" + }, + { + "fields": [ + { + "type": 28 + } + ], + "index": 1, + "name": "Some" + } + ] + } + }, + "params": [ + { + "name": "T", + "type": 28 + } + ], + "path": [ + "Option" + ] + } + }, + { + "id": 28, + "type": { + "def": { + "composite": { + "fields": [ + { + "name": "locked_mass", + "type": 14, + "typeName": "u64" + }, + { + "name": "conviction_bits", + "type": 29, + "typeName": "u128" + }, + { + "name": "last_update", + "type": 14, + "typeName": "u64" + } + ] + } + }, + "path": [ + "bittensor", + "ColdkeyLock" + ] + } + }, + { + "id": 29, + "type": { + "def": { + "primitive": "u128" + } + } + }, + { + "id": 30, + "type": { + "def": { + "variant": { + "variants": [ + { + "fields": [ + { + "type": 31 + } + ], + "index": 0, + "name": "Ok" + }, + { + "fields": [ + { + "type": 3 + } + ], + "index": 1, + "name": "Err" + } + ] + } + }, + "params": [ + { + "name": "T", + "type": 31 + }, + { + "name": "E", + "type": 3 + } + ], + "path": [ + "Result" + ] + } + }, + { + "id": 31, + "type": { + "def": { + "variant": { + "variants": [ + { + "fields": [ + { + "type": 32 + } + ], + "index": 0, + "name": "Ok" + }, + { + "fields": [ + { + "type": 16 + } + ], + "index": 1, + "name": "Err" + } + ] + } + }, + "params": [ + { + "name": "T", + "type": 32 + }, + { + "name": "E", + "type": 16 + } + ], + "path": [ + "Result" + ] + } + }, + { + "id": 32, + "type": { + "def": { + "composite": { + "fields": [ + { + "name": "netuid", + "type": 6, + "typeName": "u16" + }, + { + "name": "total", + "type": 14, + "typeName": "u64" + }, + { + "name": "locked", + "type": 14, + "typeName": "u64" + }, + { + "name": "available", + "type": 14, + "typeName": "u64" + } + ] + } + }, + "path": [ + "bittensor", + "StakeAvailability" + ] + } + }, + { + "id": 33, "type": { "def": { "composite": { @@ -2172,7 +2652,7 @@ } }, { - "id": 23, + "id": 34, "type": { "def": { "primitive": "u32" @@ -2180,7 +2660,7 @@ } }, { - "id": 24, + "id": 35, "type": { "def": { "variant": {} diff --git a/ts-tests/ink/bittensor.wasm b/ts-tests/ink/bittensor.wasm index 5d54ecf3d6..89e8b81c05 100644 Binary files a/ts-tests/ink/bittensor.wasm and b/ts-tests/ink/bittensor.wasm differ diff --git a/ts-tests/suites/zombienet_evm/00-evm-substrate-transfer.test.ts b/ts-tests/suites/zombienet_evm/00-evm-substrate-transfer.test.ts index 93e245d276..a21b61c777 100644 --- a/ts-tests/suites/zombienet_evm/00-evm-substrate-transfer.test.ts +++ b/ts-tests/suites/zombienet_evm/00-evm-substrate-transfer.test.ts @@ -224,7 +224,7 @@ describeSuite({ authorization_list: [], }); - await waitForTransactionWithRetry(api, tx, signer, "evm_call", 5); + await waitForTransactionWithRetry(api, tx, signer, "evm_call"); const receiverBalanceAfterCall = await getEthBalance(provider, ethWallet.address); expect(receiverBalanceAfterCall).toEqual(receiverBalance + raoToEth(tao(1))); @@ -242,6 +242,7 @@ describeSuite({ ); const contract = await contractFactory.deploy(); await contract.waitForDeployment(); + await waitForFinalizedBlocks(api, 1); const contractAddress = contract.target.toString(); const code = await provider.getCode(contractAddress); @@ -254,6 +255,7 @@ describeSuite({ }; const fundReceipt = await (await ethWallet.sendTransaction(ethTransfer)).wait(); expect(fundReceipt?.status).toEqual(1); + await waitForFinalizedBlocks(api, 1); const contractBalance = await getEthBalance(provider, contractAddress); const callerBalance = await getEthBalance(provider, ethWallet.address); diff --git a/ts-tests/suites/zombienet_evm/01-contract-deploy-call.test.ts b/ts-tests/suites/zombienet_evm/01-contract-deploy-call.test.ts index f8976bc674..9c02a31727 100644 --- a/ts-tests/suites/zombienet_evm/01-contract-deploy-call.test.ts +++ b/ts-tests/suites/zombienet_evm/01-contract-deploy-call.test.ts @@ -204,6 +204,7 @@ describeSuite({ expect(contract.target).toBeDefined(); await expectDeployedContract(provider, contract.target.toString()); + await waitForFinalizedBlocks(api, 1); }, }); @@ -223,6 +224,7 @@ describeSuite({ expect(contract.target).toBeDefined(); await expectDeployedContract(provider, contract.target.toString()); + await waitForFinalizedBlocks(api, 1); }, }); diff --git a/ts-tests/suites/zombienet_evm/03-wasm-contract.test.ts b/ts-tests/suites/zombienet_evm/03-wasm-contract.test.ts index e7e567f87d..a1c0154403 100644 --- a/ts-tests/suites/zombienet_evm/03-wasm-contract.test.ts +++ b/ts-tests/suites/zombienet_evm/03-wasm-contract.test.ts @@ -1213,5 +1213,142 @@ describeSuite({ expect(alphaOutAfter > alphaOutBefore).toBeTruthy(); }, }); + + it({ + id: "T36", + title: "Can get subnet registration state", + test: async () => { + const queryMessage = inkClient.message("get_subnet_registration_state"); + + const data = queryMessage.encode({ + netuid: netuid, + }); + + const response = await api.apis.ContractsApi.call( + convertPublicKeyToSs58(hotkey.publicKey), + contractAddress, + BigInt(0), + undefined, + undefined, + Binary.fromBytes(data.asBytes()) + ); + + expect(response.result.success).toBeTruthy(); + const result = queryMessage.decode(response.result.value).value.value; + if ( + typeof result === "object" && + "netuid" in result && + "exists" in result && + "registered_subnet_counter" in result + ) { + expect(result.netuid).toEqual(netuid); + expect(result.registered_subnet_counter).toBeGreaterThanOrEqual(BigInt(0)); + expect(result.exists).toEqual(true); + } else { + throw new Error("result is not an object"); + } + }, + }); + + it({ + id: "T37", + title: "Can get coldkey lock", + test: async () => { + const queryMessage = inkClient.message("get_coldkey_lock"); + const queryArgs = { + coldkey: Binary.fromBytes(coldkey.publicKey), + netuid: netuid, + }; + + async function queryColdkeyLock() { + const data = queryMessage.encode(queryArgs); + const response = await api.apis.ContractsApi.call( + convertPublicKeyToSs58(hotkey.publicKey), + contractAddress, + BigInt(0), + undefined, + undefined, + Binary.fromBytes(data.asBytes()) + ); + expect(response.result.success).toBeTruthy(); + return queryMessage.decode(response.result.value).value.value as + | { + locked_mass: bigint; + conviction_bits: bigint; + last_update: bigint; + } + | undefined; + } + + let lock = await queryColdkeyLock(); + if (!lock) { + await addStakeViaContract(false); + + const lockAmount = tao(1); + const lockTx = api.tx.SubtensorModule.lock_stake({ + hotkey: convertPublicKeyToSs58(hotkey.publicKey), + netuid: netuid, + amount: lockAmount, + }); + await waitForTransactionWithRetry(api, lockTx, coldkey, "lock_stake"); + + lock = await queryColdkeyLock(); + } + + expect(lock).toBeDefined(); + + if ( + typeof lock === "object" && + "locked_mass" in lock && + "conviction_bits" in lock && + "last_update" in lock + ) { + expect(lock.locked_mass).toBeGreaterThanOrEqual(BigInt(0)); + expect(lock.conviction_bits).toBeGreaterThanOrEqual(BigInt(0)); + expect(lock.last_update).toBeGreaterThanOrEqual(BigInt(0)); + } else { + throw new Error("result is not an object"); + } + }, + }); + + it({ + id: "T38", + title: "Can get stake availability", + test: async () => { + const queryMessage = inkClient.message("get_stake_availability"); + + const data = queryMessage.encode({ + coldkey: Binary.fromBytes(coldkey.publicKey), + netuid: netuid, + }); + + const response = await api.apis.ContractsApi.call( + convertPublicKeyToSs58(hotkey.publicKey), + contractAddress, + BigInt(0), + undefined, + undefined, + Binary.fromBytes(data.asBytes()) + ); + + expect(response.result.success).toBeTruthy(); + const result = queryMessage.decode(response.result.value).value.value; + if ( + typeof result === "object" && + "netuid" in result && + "locked" in result && + "available" in result && + "total" in result + ) { + expect(result.netuid).toEqual(netuid); + expect(result.locked).toBeGreaterThanOrEqual(BigInt(0)); + expect(result.available).toBeGreaterThanOrEqual(BigInt(0)); + expect(result.total).toBeGreaterThanOrEqual(BigInt(0)); + } else { + throw new Error("result is not an object"); + } + }, + }); }, }); diff --git a/ts-tests/utils/dev-helpers.ts b/ts-tests/utils/dev-helpers.ts index bc7b07659a..c2f573fbf6 100644 --- a/ts-tests/utils/dev-helpers.ts +++ b/ts-tests/utils/dev-helpers.ts @@ -3,14 +3,15 @@ * Uses ApiPromise, not PAPI TypedApi — keep them separate. */ import type { ApiPromise } from "@polkadot/api"; +import { tao } from "./balance.ts"; import type { KeyringPair } from "@moonwall/util"; -import { SignedOrder } from "./index.js"; +import type { SignedOrder } from "./index.js"; export async function devForceSetBalance( polkadotJs: ApiPromise, context: any, address: string, - amount: bigint + amount: bigint = tao(1e10) ): Promise { await context.createBlock([ await polkadotJs.tx.sudo