diff --git a/.github/workflows/build-publish-images.yml b/.github/workflows/build-publish-images.yml
index 874b5d37469cde33f2b92f8cec0a4d61c57b7ffb..deb3b3df5ff2f6d0c13e038cb6291e958a9c879a 100644
--- a/.github/workflows/build-publish-images.yml
+++ b/.github/workflows/build-publish-images.yml
@@ -53,7 +53,7 @@ jobs:
       - name: pack artifacts
         run: |
           mkdir -p ./artifacts
-          VERSION="${{ needs.preflight.outputs.SOURCE_REF_NAME }}" # will be tag or branch name
+          VERSION="${{ needs.preflight.outputs.SOURCE_REF_SLUG }}" # will be tag or branch name
           mv ./target/testnet/polkadot ./artifacts/.
           mv ./target/testnet/polkadot-prepare-worker ./artifacts/.
           mv ./target/testnet/polkadot-execute-worker ./artifacts/.
@@ -62,7 +62,7 @@ jobs:
           sha256sum polkadot | tee polkadot.sha256
           shasum -c polkadot.sha256
           cd ../
-          EXTRATAG="${{ needs.preflight.outputs.SOURCE_REF_NAME }}-${COMMIT_SHA}"
+          EXTRATAG="${{ needs.preflight.outputs.SOURCE_REF_SLUG }}-${COMMIT_SHA}"
           echo "Polkadot version = ${VERSION} (EXTRATAG = ${EXTRATAG})"
           echo -n ${VERSION} > ./artifacts/VERSION
           echo -n ${EXTRATAG} > ./artifacts/EXTRATAG
@@ -77,7 +77,7 @@ jobs:
       - name: upload artifacts
         uses: actions/upload-artifact@v4
         with:
-          name: ${{ github.job }}-${{ needs.preflight.outputs.SOURCE_REF_NAME }}
+          name: ${{ github.job }}-${{ needs.preflight.outputs.SOURCE_REF_SLUG }}
           path: artifacts.tar
           retention-days: 1
 
@@ -103,7 +103,7 @@ jobs:
           mkdir -p ./artifacts
           mv ./target/release/polkadot-parachain ./artifacts/.
           echo "___The VERSION is either a tag name or the curent branch if triggered not by a tag___"
-          echo ${{ needs.preflight.outputs.SOURCE_REF_NAME }} | tee ./artifacts/VERSION
+          echo ${{ needs.preflight.outputs.SOURCE_REF_SLUG }} | tee ./artifacts/VERSION
 
       - name: tar
         run: tar -cvf artifacts.tar artifacts
@@ -111,7 +111,7 @@ jobs:
       - name: upload artifacts
         uses: actions/upload-artifact@v4
         with:
-          name: ${{ github.job }}-${{ needs.preflight.outputs.SOURCE_REF_NAME }}
+          name: ${{ github.job }}-${{ needs.preflight.outputs.SOURCE_REF_SLUG }}
           path: artifacts.tar
           retention-days: 1
 
@@ -147,7 +147,7 @@ jobs:
       - name: upload artifacts
         uses: actions/upload-artifact@v4
         with:
-          name: ${{ github.job }}-${{ needs.preflight.outputs.SOURCE_REF_NAME }}
+          name: ${{ github.job }}-${{ needs.preflight.outputs.SOURCE_REF_SLUG }}
           path: artifacts.tar
           retention-days: 1
 
@@ -172,8 +172,8 @@ jobs:
           mkdir -p ./artifacts
           mv ./target/testnet/adder-collator ./artifacts/.
           mv ./target/testnet/undying-collator ./artifacts/.
-          echo -n "${{ needs.preflight.outputs.SOURCE_REF_NAME }}" > ./artifacts/VERSION
-          echo -n "${{ needs.preflight.outputs.SOURCE_REF_NAME }}-${COMMIT_SHA}" > ./artifacts/EXTRATAG
+          echo -n "${{ needs.preflight.outputs.SOURCE_REF_SLUG }}" > ./artifacts/VERSION
+          echo -n "${{ needs.preflight.outputs.SOURCE_REF_SLUG }}-${COMMIT_SHA}" > ./artifacts/EXTRATAG
           echo "adder-collator version = $(cat ./artifacts/VERSION) (EXTRATAG = $(cat ./artifacts/EXTRATAG))"
           echo "undying-collator version = $(cat ./artifacts/VERSION) (EXTRATAG = $(cat ./artifacts/EXTRATAG))"
           cp -r ./docker/* ./artifacts
@@ -184,7 +184,7 @@ jobs:
       - name: upload artifacts
         uses: actions/upload-artifact@v4
         with:
-          name: ${{ github.job }}-${{ needs.preflight.outputs.SOURCE_REF_NAME }}
+          name: ${{ github.job }}-${{ needs.preflight.outputs.SOURCE_REF_SLUG }}
           path: artifacts.tar
           retention-days: 1
 
@@ -209,8 +209,8 @@ jobs:
           mv ./target/testnet/malus ./artifacts/.
           mv ./target/testnet/polkadot-execute-worker ./artifacts/.
           mv ./target/testnet/polkadot-prepare-worker ./artifacts/.
-          echo -n "${{ needs.preflight.outputs.SOURCE_REF_NAME }}" > ./artifacts/VERSION
-          echo -n "${{ needs.preflight.outputs.SOURCE_REF_NAME }}-${COMMIT_SHA}" > ./artifacts/EXTRATAG
+          echo -n "${{ needs.preflight.outputs.SOURCE_REF_SLUG }}" > ./artifacts/VERSION
+          echo -n "${{ needs.preflight.outputs.SOURCE_REF_SLUG }}-${COMMIT_SHA}" > ./artifacts/EXTRATAG
           echo "polkadot-test-malus = $(cat ./artifacts/VERSION) (EXTRATAG = $(cat ./artifacts/EXTRATAG))"
           cp -r ./docker/* ./artifacts
 
@@ -220,7 +220,7 @@ jobs:
       - name: upload artifacts
         uses: actions/upload-artifact@v4
         with:
-          name: ${{ github.job }}-${{ needs.preflight.outputs.SOURCE_REF_NAME }}
+          name: ${{ github.job }}-${{ needs.preflight.outputs.SOURCE_REF_SLUG }}
           path: artifacts.tar
           retention-days: 1
 
@@ -246,6 +246,7 @@ jobs:
           WASM_BUILD_NO_COLOR=1 forklift cargo build --locked --release -p staging-node-cli
           ls -la target/release/
       - name: pack artifacts
+        shell: bash
         run: |
           mv target/release/substrate-node ./artifacts/substrate/substrate
           echo -n "Substrate version = "
@@ -264,7 +265,7 @@ jobs:
       - name: upload artifacts
         uses: actions/upload-artifact@v4
         with:
-          name: ${{ github.job }}-${{ needs.preflight.outputs.SOURCE_REF_NAME }}
+          name: ${{ github.job }}-${{ needs.preflight.outputs.SOURCE_REF_SLUG }}
           path: artifacts.tar
           retention-days: 1
 
@@ -294,7 +295,7 @@ jobs:
       - name: upload artifacts
         uses: actions/upload-artifact@v4
         with:
-          name: ${{ github.job }}-${{ needs.preflight.outputs.SOURCE_REF_NAME }}
+          name: ${{ github.job }}-${{ needs.preflight.outputs.SOURCE_REF_SLUG }}
           path: artifacts.tar
           retention-days: 1
 
@@ -313,7 +314,7 @@ jobs:
 
       - uses: actions/download-artifact@v4.1.8
         with:
-          name: build-test-parachain-${{ needs.preflight.outputs.SOURCE_REF_NAME }}
+          name: build-test-parachain-${{ needs.preflight.outputs.SOURCE_REF_SLUG }}
 
       - name: tar
         run: tar -xvf artifacts.tar
@@ -337,7 +338,7 @@ jobs:
 
       - uses: actions/download-artifact@v4.1.8
         with:
-          name: build-linux-stable-${{ needs.preflight.outputs.SOURCE_REF_NAME }}
+          name: build-linux-stable-${{ needs.preflight.outputs.SOURCE_REF_SLUG }}
 
       - name: tar
         run: tar -xvf artifacts.tar
@@ -361,7 +362,7 @@ jobs:
 
       - uses: actions/download-artifact@v4.1.8
         with:
-          name: build-test-collators-${{ needs.preflight.outputs.SOURCE_REF_NAME }}
+          name: build-test-collators-${{ needs.preflight.outputs.SOURCE_REF_SLUG }}
 
       - name: tar
         run: tar -xvf artifacts.tar
@@ -385,7 +386,7 @@ jobs:
 
       - uses: actions/download-artifact@v4.1.8
         with:
-          name: build-malus-${{ needs.preflight.outputs.SOURCE_REF_NAME }}
+          name: build-malus-${{ needs.preflight.outputs.SOURCE_REF_SLUG }}
 
       - name: tar
         run: tar -xvf artifacts.tar
@@ -409,7 +410,7 @@ jobs:
 
       - uses: actions/download-artifact@v4.1.8
         with:
-          name: build-linux-substrate-${{ needs.preflight.outputs.SOURCE_REF_NAME }}
+          name: build-linux-substrate-${{ needs.preflight.outputs.SOURCE_REF_SLUG }}
 
       - name: tar
         run: tar -xvf artifacts.tar
@@ -441,7 +442,7 @@ jobs:
 
       - uses: actions/download-artifact@v4.1.8
         with:
-          name: build-linux-stable-${{ needs.preflight.outputs.SOURCE_REF_NAME }}
+          name: build-linux-stable-${{ needs.preflight.outputs.SOURCE_REF_SLUG }}
       - name: tar
         run: |
           tar -xvf artifacts.tar
@@ -449,7 +450,7 @@ jobs:
 
       - uses: actions/download-artifact@v4.1.8
         with:
-          name: build-linux-stable-cumulus-${{ needs.preflight.outputs.SOURCE_REF_NAME }}
+          name: build-linux-stable-cumulus-${{ needs.preflight.outputs.SOURCE_REF_SLUG }}
       - name: tar
         run: |
           tar -xvf artifacts.tar
@@ -457,7 +458,7 @@ jobs:
 
       - uses: actions/download-artifact@v4.1.8
         with:
-          name: prepare-bridges-zombienet-artifacts-${{ needs.preflight.outputs.SOURCE_REF_NAME }}
+          name: prepare-bridges-zombienet-artifacts-${{ needs.preflight.outputs.SOURCE_REF_SLUG }}
       - name: tar
         run: |
           tar -xvf artifacts.tar
@@ -482,7 +483,7 @@ jobs:
 
       - uses: actions/download-artifact@v4.1.8
         with:
-          name: build-linux-stable-cumulus-${{ needs.preflight.outputs.SOURCE_REF_NAME }}
+          name: build-linux-stable-cumulus-${{ needs.preflight.outputs.SOURCE_REF_SLUG }}
 
       - name: tar
         run: tar -xvf artifacts.tar
diff --git a/.github/workflows/check-semver.yml b/.github/workflows/check-semver.yml
index 0da3e54ef60b88420a0bb9762bc9fff2e88be4af..43c70d6abc78b0aca6fe0738617632a9b33b809a 100644
--- a/.github/workflows/check-semver.yml
+++ b/.github/workflows/check-semver.yml
@@ -81,7 +81,7 @@ jobs:
       - name: install parity-publish
         if: ${{ !contains(github.event.pull_request.labels.*.name, 'R0-silent') }}
         # Set the target dir to cache the build.
-        run: CARGO_TARGET_DIR=./target/ cargo install parity-publish@0.10.3 --locked -q
+        run: CARGO_TARGET_DIR=./target/ cargo install parity-publish@0.10.4 --locked -q
 
       - name: check semver
         if: ${{ !contains(github.event.pull_request.labels.*.name, 'R0-silent') }}
diff --git a/.github/workflows/command-inform.yml b/.github/workflows/command-inform.yml
index 97346395319362b0455bcbcfbb490fa23e6b3b07..3431eadf70608d2a7465f57718689f4acfe96952 100644
--- a/.github/workflows/command-inform.yml
+++ b/.github/workflows/command-inform.yml
@@ -8,7 +8,7 @@ jobs:
   comment:
     runs-on: ubuntu-latest
     # Temporary disable the bot until the new command bot works properly
-    if: github.event.issue.pull_request && startsWith(github.event.comment.body, 'bot ') && false # disabled for now, until tested
+    if: github.event.issue.pull_request && startsWith(github.event.comment.body, 'bot ')
     steps:
       - name: Inform that the new command exist
         uses: actions/github-script@v7
@@ -18,5 +18,5 @@ jobs:
               issue_number: context.issue.number,
               owner: context.repo.owner,
               repo: context.repo.repo,
-              body: 'We have migrated the command bot to GHA<br/><br/>Please, see the new usage instructions <a href="https://github.com/paritytech/polkadot-sdk/blob/master/docs/contributor/commands-readme.md">here</a>. Soon the old commands will be disabled.'
-            })
\ No newline at end of file
+              body: 'We have migrated the command bot to GHA<br/><br/>Please, see the new usage instructions <a href="https://github.com/paritytech/polkadot-sdk/blob/master/docs/contributor/commands-readme.md">here</a> or <a href="https://forum.parity.io/t/streamlining-weight-generation-and-more-the-new-cmd-bot/2411">here</a>. Soon the old commands will be disabled.'
+            })
diff --git a/.github/workflows/publish-check-compile.yml b/.github/workflows/publish-check-compile.yml
index ce1b2cb231d0bbfa9497559a1c0dbe8a2dedb783..f20909106a82098d79ab8e47b03bf5b4076b9380 100644
--- a/.github/workflows/publish-check-compile.yml
+++ b/.github/workflows/publish-check-compile.yml
@@ -26,12 +26,14 @@ jobs:
       - uses: actions/checkout@6d193bf28034eafb982f37bd894289fe649468fc # v4.1.7
 
       - name: Rust Cache
-        uses: Swatinem/rust-cache@82a92a6e8fbeee089604da2575dc567ae9ddeaab # v2.7.5
+        uses: Swatinem/rust-cache@f0deed1e0edfc6a9be95417288c0e1099b1eeec3 # v2.7.7
         with:
           cache-on-failure: true
 
       - name: install parity-publish
-        run: cargo install parity-publish@0.10.3 --locked -q
+        run: |
+          rustup override set 1.82.0
+          cargo install parity-publish@0.10.4 --locked -q
 
       - name: parity-publish update plan
         run: parity-publish --color always plan --skip-check --prdoc prdoc/
diff --git a/.github/workflows/publish-check-crates.yml b/.github/workflows/publish-check-crates.yml
index 3150cb9dd40508a214280f716ba161c5420bfc92..c1b13243ba193c7c141782dc263c577c5f0ec159 100644
--- a/.github/workflows/publish-check-crates.yml
+++ b/.github/workflows/publish-check-crates.yml
@@ -19,12 +19,12 @@ jobs:
       - uses: actions/checkout@6d193bf28034eafb982f37bd894289fe649468fc # v4.1.7
 
       - name: Rust Cache
-        uses: Swatinem/rust-cache@82a92a6e8fbeee089604da2575dc567ae9ddeaab # v2.7.5
+        uses: Swatinem/rust-cache@f0deed1e0edfc6a9be95417288c0e1099b1eeec3 # v2.7.7
         with:
           cache-on-failure: true
 
       - name: install parity-publish
-        run: cargo install parity-publish@0.10.3 --locked -q
+        run: cargo install parity-publish@0.10.4 --locked -q
 
       - name: parity-publish check
         run: parity-publish --color always check --allow-unpublished
diff --git a/.github/workflows/publish-claim-crates.yml b/.github/workflows/publish-claim-crates.yml
index a6efc8a5599e4171cd93c5bdb287d2b5a8514787..804baf9ff06cfc4c32c19fbe3a70be81c8f07b46 100644
--- a/.github/workflows/publish-claim-crates.yml
+++ b/.github/workflows/publish-claim-crates.yml
@@ -13,12 +13,12 @@ jobs:
       - uses: actions/checkout@6d193bf28034eafb982f37bd894289fe649468fc # v4.1.7
 
       - name: Rust Cache
-        uses: Swatinem/rust-cache@82a92a6e8fbeee089604da2575dc567ae9ddeaab # v2.7.5
+        uses: Swatinem/rust-cache@f0deed1e0edfc6a9be95417288c0e1099b1eeec3 # v2.7.7
         with:
           cache-on-failure: true
 
       - name: install parity-publish
-        run: cargo install parity-publish@0.10.3 --locked -q
+        run: cargo install parity-publish@0.10.4 --locked -q
 
       - name: parity-publish claim
         env:
diff --git a/.github/workflows/review-bot.yml b/.github/workflows/review-bot.yml
index 3dd5b1114813dbb2e151319293ade7ce44f4aae9..27c6162a0fc20cddbde29324a9626f3f1e8b7973 100644
--- a/.github/workflows/review-bot.yml
+++ b/.github/workflows/review-bot.yml
@@ -29,7 +29,7 @@ jobs:
         with:
           artifact-name: pr_number
       - name: "Evaluates PR reviews and assigns reviewers"
-        uses: paritytech/review-bot@v2.6.0
+        uses: paritytech/review-bot@v2.7.0
         with:
           repo-token: ${{ steps.app_token.outputs.token }}
           team-token: ${{ steps.app_token.outputs.token }}
diff --git a/.github/workflows/zombienet-reusable-preflight.yml b/.github/workflows/zombienet-reusable-preflight.yml
new file mode 100644
index 0000000000000000000000000000000000000000..8e938567d8118ad6d68eb91108281fd20e72b26f
--- /dev/null
+++ b/.github/workflows/zombienet-reusable-preflight.yml
@@ -0,0 +1,145 @@
+# Reusable workflow to set various useful variables
+# and to perform checks and generate conditions for other workflows.
+# Currently it checks if any Rust (build-related) file is changed
+# and if the current (caller) workflow file is changed.
+# Example:
+#
+# jobs:
+#   preflight:
+#     uses: ./.github/workflows/reusable-preflight.yml
+#   some-job:
+#     needs: changes
+#     if: ${{ needs.preflight.outputs.changes_rust }}
+#  .......
+
+name: Zombienet Preflight
+
+on:
+  workflow_call:
+    # Map the workflow outputs to job outputs
+    outputs:
+      changes_substrate:
+        value: ${{ jobs.preflight.outputs.changes_substrate }}
+
+      ZOMBIENET_IMAGE:
+        value: ${{ jobs.preflight.outputs.ZOMBIENET_IMAGE }}
+        description: "ZOMBIENET CI image"
+
+      ZOMBIENET_RUNNER:
+        value: ${{ jobs.preflight.outputs.ZOMBIENET_RUNNER }}
+        description: |
+          Main runner for zombienet tests.
+
+      DOCKER_IMAGES_VERSION:
+        value: ${{ jobs.preflight.outputs.DOCKER_IMAGES_VERSION }}
+        description: |
+          Version for temp docker images.
+
+      # Global vars (from global preflight)
+      SOURCE_REF_SLUG:
+        value: ${{ jobs.global_preflight.outputs.SOURCE_REF_SLUG }}
+
+      # Zombie vars
+      PUSHGATEWAY_URL:
+        value: ${{ jobs.preflight.outputs.PUSHGATEWAY_URL }}
+        description: "Gateway (url) to push metrics related to test."
+      DEBUG:
+        value: ${{ jobs.preflight.outputs.DEBUG }}
+        description: "Debug value to zombienet v1 tests."
+      ZOMBIE_PROVIDER:
+        value: ${{ jobs.preflight.outputs.ZOMBIE_PROVIDER }}
+        description: "Provider to use in zombienet-sdk tests."
+      RUST_LOG:
+        value: ${{ jobs.preflight.outputs.RUST_LOG }}
+        description: "Log value to use in zombinet-sdk tests."
+      RUN_IN_CI:
+        value: ${{ jobs.preflight.outputs.RUN_IN_CI }}
+        description: "Internal flag to make zombienet aware of the env."
+
+      KUBERNETES_CPU_REQUEST:
+        value: ${{ jobs.preflight.outputs.KUBERNETES_CPU_REQUEST }}
+        description: "Base cpu (request) for pod runner."
+
+      KUBERNETES_MEMORY_REQUEST:
+        value: ${{ jobs.preflight.outputs.KUBERNETES_MEMORY_REQUEST }}
+        description: "Base memory (request) for pod runner."
+
+jobs:
+  global_preflight:
+    uses: ./.github/workflows/reusable-preflight.yml
+
+  #
+  #
+  #
+  preflight:
+    runs-on: ubuntu-latest
+    outputs:
+      changes_substrate: ${{ steps.set_changes.outputs.substrate_any_changed || steps.set_changes.outputs.currentWorkflow_any_changed }}
+
+      ZOMBIENET_IMAGE: ${{ steps.set_vars.outputs.ZOMBIENET_IMAGE }}
+      ZOMBIENET_RUNNER: ${{ steps.set_vars.outputs.ZOMBIENET_RUNNER }}
+
+      DOCKER_IMAGES_VERSION: ${{ steps.set_images_version.outputs.ZOMBIENET_RUNNER }}
+
+      # common vars
+      PUSHGATEWAY_URL: ${{ steps.set_vars.outputs.PUSHGATEWAY_URL }}
+      DEBUG: ${{ steps.set_vars.outputs.DEBUG }}
+      ZOMBIE_PROVIDER: ${{ steps.set_vars.outputs.ZOMBIE_PROVIDER }}
+      RUST_LOG: ${{ steps.set_vars.outputs.RUST_LOG }}
+      RUN_IN_CI: ${{ steps.set_vars.outputs.RUN_IN_CI }}
+      KUBERNETES_CPU_REQUEST: ${{ steps.set_vars.outputs.KUBERNETES_CPU_REQUEST }}
+      KUBERNETES_MEMORY_REQUEST: ${{ steps.set_vars.outputs.KUBERNETES_MEMORY_REQUEST }}
+
+    steps:
+
+      - uses: actions/checkout@v4
+
+      #
+      # Set changes
+      #
+      - name: Current file
+        id: current_file
+        shell: bash
+        run: |
+          echo "currentWorkflowFile=$(echo ${{ github.workflow_ref }} | sed -nE "s/.*(\.github\/workflows\/[a-zA-Z0-9_-]*\.y[a]?ml)@refs.*/\1/p")" >> $GITHUB_OUTPUT
+          echo "currentActionDir=$(echo ${{ github.action_path }} | sed -nE "s/.*(\.github\/actions\/[a-zA-Z0-9_-]*)/\1/p")" >> $GITHUB_OUTPUT
+
+      - name: Set changes
+        id: set_changes
+        uses: tj-actions/changed-files@v45
+        with:
+          files_yaml: |
+            substrate:
+              - 'substrate/**/*'
+            currentWorkflow:
+              - '${{ steps.current_file.outputs.currentWorkflowFile }}'
+              - '.github/workflows/zombienet-reusable-preflight.yml'
+              - '.github/zombienet-env'
+
+
+      #
+      # Set environment vars (including runner/image)
+      #
+      - name: Set vars
+        id: set_vars
+        shell: bash
+        run: cat .github/env >> $GITHUB_OUTPUT
+
+
+      #
+      #
+      #
+      - name: Set docker images version
+        id: set_images_version
+        shell: bash
+        run: |
+          export BRANCH_NAME=${{ github.head_ref || github.ref_name }}
+          export DOCKER_IMAGES_VERSION=${BRANCH_NAME/\//-}
+          if [[ ${{ github.event_name }} == "merge_group" ]]; then export DOCKER_IMAGES_VERSION="${GITHUB_SHA::8}"; fi
+          echo "DOCKER_IMAGES_VERSION=${DOCKER_IMAGES_VERSION}" >> $GITHUB_OUTPUT
+
+      - name: log
+        shell: bash
+        run: |
+          echo "workflow file: ${{ steps.current_file.outputs.currentWorkflowFile }}"
+          echo "Modified: ${{ steps.set_changes.outputs.modified_keys }}"
\ No newline at end of file
diff --git a/.github/workflows/zombienet_substrate.yml b/.github/workflows/zombienet_substrate.yml
new file mode 100644
index 0000000000000000000000000000000000000000..823679d67d5c0adc204a2fc15360fdab561a4c17
--- /dev/null
+++ b/.github/workflows/zombienet_substrate.yml
@@ -0,0 +1,45 @@
+name: Zombienet Substrate
+
+on:
+  workflow_run:
+    workflows: [Build and push images]
+    types: [completed]
+  merge_group:
+  workflow_dispatch:
+concurrency:
+  group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
+  cancel-in-progress: true
+
+jobs:
+  preflight:
+    uses: ./.github/workflows/zombienet-reusable-preflight.yml
+
+  zombienet-substrate-0000-block-building:
+    needs: [preflight]
+    # only run if we have changes in ./substrate directory and the build workflow already finish with success status.
+    if: ${{ needs.preflight.outputs.changes_substrate && github.event.workflow_run.conclusion == 'success' }}
+    runs-on: ${{ needs.preflight.outputs.ZOMBIENET_RUNNER }}
+    timeout-minutes: 60
+    container:
+      image: ${{ needs.preflight.outputs.ZOMBIENET_IMAGE }}
+    env:
+      FF_DISABLE_UMASK_FOR_DOCKER_EXECUTOR: 1
+      LOCAL_DIR: "./substrate/zombienet"
+    steps:
+      - name: Checkout
+        uses: actions/checkout@v4
+
+      - uses: actions/download-artifact@v4.1.8
+        with:
+          name: build-linux-substrate-${{ needs.preflight.outputs.SOURCE_REF_NAME }}
+
+      - name: script
+        run: |
+          DEBUG=${{ needs.preflight.outputs.DEBUG }} zombie -p native ${LOCAL_DIR}/0000-block-building/block-building.zndsl
+
+      - name: upload logs
+        uses: actions/upload-artifact@v4
+        with:
+          name: zombienet-logs-scale-net
+          path: |
+            /tmp/zombie*/logs/*
diff --git a/.github/zombienet-env b/.github/zombienet-env
new file mode 100644
index 0000000000000000000000000000000000000000..e6da1a49c4bb38602bdc9d7a21623ed136dee61b
--- /dev/null
+++ b/.github/zombienet-env
@@ -0,0 +1,9 @@
+    ZOMBIENET_IMAGE="docker.io/paritytech/zombienet:v1.3.116"
+    ZOMBIE_RUNNER="zombienet-arc-runner"
+    PUSHGATEWAY_URL="http://zombienet-prometheus-pushgateway.managed-monitoring:9091/metrics/job/zombie-metrics"
+    DEBUG="zombie,zombie::network-node,zombie::kube::client::logs"
+    ZOMBIE_PROVIDER="k8s"
+    RUST_LOG="info,zombienet_orchestrator=debug"
+    RUN_IN_CI="1"
+    KUBERNETES_CPU_REQUEST="512m"
+    KUBERNETES_MEMORY_REQUEST="1Gi"
diff --git a/Cargo.lock b/Cargo.lock
index 37c65b1652517a0fd88b636786a027dbd96b411e..79cd837c6d4d0b7f334eff4a7a88d5d5763b765d 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -910,6 +910,7 @@ dependencies = [
  "cumulus-primitives-core 0.7.0",
  "emulated-integration-tests-common",
  "frame-support 28.0.0",
+ "pallet-asset-rewards",
  "parachains-common 7.0.0",
  "rococo-emulated-chain",
  "sp-core 28.0.0",
@@ -928,6 +929,7 @@ dependencies = [
  "emulated-integration-tests-common",
  "frame-support 28.0.0",
  "pallet-asset-conversion 10.0.0",
+ "pallet-asset-rewards",
  "pallet-assets 29.1.0",
  "pallet-balances 28.0.0",
  "pallet-message-queue 31.0.0",
@@ -978,6 +980,7 @@ dependencies = [
  "pallet-asset-conversion 10.0.0",
  "pallet-asset-conversion-ops 0.1.0",
  "pallet-asset-conversion-tx-payment 10.0.0",
+ "pallet-asset-rewards",
  "pallet-assets 29.1.0",
  "pallet-assets-freezer 0.1.0",
  "pallet-aura 27.0.0",
@@ -1063,6 +1066,7 @@ dependencies = [
  "frame-support 28.0.0",
  "frame-system 28.0.0",
  "pallet-asset-conversion 10.0.0",
+ "pallet-asset-rewards",
  "pallet-asset-tx-payment 28.0.0",
  "pallet-assets 29.1.0",
  "pallet-balances 28.0.0",
@@ -1114,6 +1118,7 @@ dependencies = [
  "pallet-asset-conversion 10.0.0",
  "pallet-asset-conversion-ops 0.1.0",
  "pallet-asset-conversion-tx-payment 10.0.0",
+ "pallet-asset-rewards",
  "pallet-assets 29.1.0",
  "pallet-assets-freezer 0.1.0",
  "pallet-aura 27.0.0",
@@ -10446,9 +10451,9 @@ dependencies = [
 
 [[package]]
 name = "litep2p"
-version = "0.8.4"
+version = "0.9.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "2b0fef34af8847e816003bf7fdeac5ea50b9a7a88441ac927a6166b5e812ab79"
+checksum = "6ca6ee50a125dc4fc4e9a3ae3640010796d1d07bc517a0ac715fdf0b24a0b6ac"
 dependencies = [
  "async-trait",
  "bs58",
@@ -12036,6 +12041,27 @@ dependencies = [
  "sp-runtime 39.0.2",
 ]
 
+[[package]]
+name = "pallet-asset-rewards"
+version = "0.1.0"
+dependencies = [
+ "frame-benchmarking 28.0.0",
+ "frame-support 28.0.0",
+ "frame-system 28.0.0",
+ "pallet-assets 29.1.0",
+ "pallet-assets-freezer 0.1.0",
+ "pallet-balances 28.0.0",
+ "parity-scale-codec",
+ "primitive-types 0.13.1",
+ "scale-info",
+ "sp-api 26.0.0",
+ "sp-arithmetic 23.0.0",
+ "sp-core 28.0.0",
+ "sp-io 30.0.0",
+ "sp-runtime 31.0.1",
+ "sp-std 14.0.0",
+]
+
 [[package]]
 name = "pallet-asset-tx-payment"
 version = "28.0.0"
@@ -13343,6 +13369,7 @@ dependencies = [
  "log",
  "pallet-bags-list 27.0.0",
  "pallet-balances 28.0.0",
+ "pallet-delegated-staking 1.0.0",
  "pallet-election-provider-multi-phase 27.0.0",
  "pallet-nomination-pools 25.0.0",
  "pallet-session 28.0.0",
@@ -14428,29 +14455,6 @@ dependencies = [
  "sp-tracing 16.0.0",
 ]
 
-[[package]]
-name = "pallet-nomination-pools-test-transfer-stake"
-version = "1.0.0"
-dependencies = [
- "frame-election-provider-support 28.0.0",
- "frame-support 28.0.0",
- "frame-system 28.0.0",
- "log",
- "pallet-bags-list 27.0.0",
- "pallet-balances 28.0.0",
- "pallet-nomination-pools 25.0.0",
- "pallet-staking 28.0.0",
- "pallet-staking-reward-curve",
- "pallet-timestamp 27.0.0",
- "parity-scale-codec",
- "scale-info",
- "sp-core 28.0.0",
- "sp-io 30.0.0",
- "sp-runtime 31.0.1",
- "sp-staking 26.0.0",
- "sp-tracing 16.0.0",
-]
-
 [[package]]
 name = "pallet-offences"
 version = "27.0.0"
@@ -14830,6 +14834,7 @@ dependencies = [
  "assert_matches",
  "derive_more 0.99.17",
  "environmental",
+ "ethabi-decode 2.0.0",
  "ethereum-types 0.15.1",
  "frame-benchmarking 28.0.0",
  "frame-support 28.0.0",
@@ -14848,7 +14853,7 @@ dependencies = [
  "pallet-utility 28.0.0",
  "parity-scale-codec",
  "paste",
- "polkavm 0.18.0",
+ "polkavm 0.19.0",
  "pretty_assertions",
  "rlp 0.6.1",
  "scale-info",
@@ -14937,7 +14942,7 @@ name = "pallet-revive-fixtures"
 version = "0.1.0"
 dependencies = [
  "anyhow",
- "polkavm-linker 0.18.0",
+ "polkavm-linker 0.19.0",
  "sp-core 28.0.0",
  "sp-io 30.0.0",
  "toml 0.8.19",
@@ -15052,7 +15057,7 @@ dependencies = [
  "pallet-revive-proc-macro 0.1.0",
  "parity-scale-codec",
  "paste",
- "polkavm-derive 0.18.0",
+ "polkavm-derive 0.19.0",
  "scale-info",
 ]
 
@@ -18710,6 +18715,7 @@ dependencies = [
  "pallet-asset-conversion-ops 0.1.0",
  "pallet-asset-conversion-tx-payment 10.0.0",
  "pallet-asset-rate 7.0.0",
+ "pallet-asset-rewards",
  "pallet-asset-tx-payment 28.0.0",
  "pallet-assets 29.1.0",
  "pallet-assets-freezer 0.1.0",
@@ -19923,6 +19929,19 @@ dependencies = [
  "polkavm-linux-raw 0.18.0",
 ]
 
+[[package]]
+name = "polkavm"
+version = "0.19.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8379bb48ff026aa8ae0645ea45f27920bfd21c82b2e82ed914224bb233d59f83"
+dependencies = [
+ "libc",
+ "log",
+ "polkavm-assembler 0.19.0",
+ "polkavm-common 0.19.0",
+ "polkavm-linux-raw 0.19.0",
+]
+
 [[package]]
 name = "polkavm-assembler"
 version = "0.9.0"
@@ -19950,6 +19969,15 @@ dependencies = [
  "log",
 ]
 
+[[package]]
+name = "polkavm-assembler"
+version = "0.19.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "57513b596cf0bafb052dab48e9c168f473c35f7522e17f70cc9f96603012d9b7"
+dependencies = [
+ "log",
+]
+
 [[package]]
 name = "polkavm-common"
 version = "0.9.0"
@@ -19979,6 +20007,16 @@ dependencies = [
  "polkavm-assembler 0.18.0",
 ]
 
+[[package]]
+name = "polkavm-common"
+version = "0.19.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a972bd305ba8cbf0de79951d6d49d2abfad47c277596be5a2c6a0924a163abbd"
+dependencies = [
+ "log",
+ "polkavm-assembler 0.19.0",
+]
+
 [[package]]
 name = "polkavm-derive"
 version = "0.9.1"
@@ -20006,6 +20044,15 @@ dependencies = [
  "polkavm-derive-impl-macro 0.18.0",
 ]
 
+[[package]]
+name = "polkavm-derive"
+version = "0.19.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d8d866972a7532d82d05c26b4516563660dd6676d7ab9e64e681d8ef0e29255c"
+dependencies = [
+ "polkavm-derive-impl-macro 0.19.0",
+]
+
 [[package]]
 name = "polkavm-derive-impl"
 version = "0.9.0"
@@ -20042,6 +20089,18 @@ dependencies = [
  "syn 2.0.87",
 ]
 
+[[package]]
+name = "polkavm-derive-impl"
+version = "0.19.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5cffca9d51b21153395a192b65698457687bc51daa41026629895542ccaa65c2"
+dependencies = [
+ "polkavm-common 0.19.0",
+ "proc-macro2 1.0.86",
+ "quote 1.0.37",
+ "syn 2.0.87",
+]
+
 [[package]]
 name = "polkavm-derive-impl-macro"
 version = "0.9.0"
@@ -20072,6 +20131,16 @@ dependencies = [
  "syn 2.0.87",
 ]
 
+[[package]]
+name = "polkavm-derive-impl-macro"
+version = "0.19.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cc0dc0cf2e8f4d30874131eccfa36bdabd4a52cfb79c15f8630508abaf06a2a6"
+dependencies = [
+ "polkavm-derive-impl 0.19.0",
+ "syn 2.0.87",
+]
+
 [[package]]
 name = "polkavm-linker"
 version = "0.9.2"
@@ -20118,6 +20187,22 @@ dependencies = [
  "rustc-demangle",
 ]
 
+[[package]]
+name = "polkavm-linker"
+version = "0.19.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "caec2308f1328b5a667da45322c04fad7ff97ad8b36817d18c7635ea4dd6c6f4"
+dependencies = [
+ "dirs",
+ "gimli 0.31.1",
+ "hashbrown 0.14.5",
+ "log",
+ "object 0.36.1",
+ "polkavm-common 0.19.0",
+ "regalloc2 0.9.3",
+ "rustc-demangle",
+]
+
 [[package]]
 name = "polkavm-linux-raw"
 version = "0.9.0"
@@ -20136,6 +20221,12 @@ version = "0.18.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "23eff02c070c70f31878a3d915e88a914ecf3e153741e2fb572dde28cce20fde"
 
+[[package]]
+name = "polkavm-linux-raw"
+version = "0.19.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "136ae072ab6fa38e584a06d12b1b216cff19f54d5cd202a8f8c5ec2e92e7e4bb"
+
 [[package]]
 name = "polling"
 version = "2.8.0"
diff --git a/Cargo.toml b/Cargo.toml
index c30a9949e85e4212a9ead1f895889ee8bf69a53c..e17f08148b163206bc0073e3610cb5c64586cf69 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -315,6 +315,7 @@ members = [
 	"substrate/frame/asset-conversion",
 	"substrate/frame/asset-conversion/ops",
 	"substrate/frame/asset-rate",
+	"substrate/frame/asset-rewards",
 	"substrate/frame/assets",
 	"substrate/frame/assets-freezer",
 	"substrate/frame/atomic-swap",
@@ -388,7 +389,6 @@ members = [
 	"substrate/frame/nomination-pools/fuzzer",
 	"substrate/frame/nomination-pools/runtime-api",
 	"substrate/frame/nomination-pools/test-delegate-stake",
-	"substrate/frame/nomination-pools/test-transfer-stake",
 	"substrate/frame/offences",
 	"substrate/frame/offences/benchmarking",
 	"substrate/frame/paged-list",
@@ -850,7 +850,7 @@ linked-hash-map = { version = "0.5.4" }
 linked_hash_set = { version = "0.1.4" }
 linregress = { version = "0.5.1" }
 lite-json = { version = "0.2.0", default-features = false }
-litep2p = { version = "0.8.4", features = ["websocket"] }
+litep2p = { version = "0.9.0", features = ["websocket"] }
 log = { version = "0.4.22", default-features = false }
 macro_magic = { version = "0.5.1" }
 maplit = { version = "1.0.2" }
@@ -893,6 +893,7 @@ pallet-asset-conversion = { path = "substrate/frame/asset-conversion", default-f
 pallet-asset-conversion-ops = { path = "substrate/frame/asset-conversion/ops", default-features = false }
 pallet-asset-conversion-tx-payment = { path = "substrate/frame/transaction-payment/asset-conversion-tx-payment", default-features = false }
 pallet-asset-rate = { path = "substrate/frame/asset-rate", default-features = false }
+pallet-asset-rewards = { path = "substrate/frame/asset-rewards", default-features = false }
 pallet-asset-tx-payment = { path = "substrate/frame/transaction-payment/asset-tx-payment", default-features = false }
 pallet-assets = { path = "substrate/frame/assets", default-features = false }
 pallet-assets-freezer = { path = "substrate/frame/assets-freezer", default-features = false }
diff --git a/bridges/relays/lib-substrate-relay/src/error.rs b/bridges/relays/lib-substrate-relay/src/error.rs
index 2ebd9130f3912ba4c0552860a2259b222220e8f8..3a62f30838c7588317234409a6107b7d1d6cacf1 100644
--- a/bridges/relays/lib-substrate-relay/src/error.rs
+++ b/bridges/relays/lib-substrate-relay/src/error.rs
@@ -47,7 +47,7 @@ pub enum Error<Hash: Debug + MaybeDisplay, HeaderNumber: Debug + MaybeDisplay> {
 	#[error("Failed to guess initial {0} GRANDPA authorities set id: checked all possible ids in range [0; {1}]")]
 	GuessInitialAuthorities(&'static str, HeaderNumber),
 	/// Failed to retrieve GRANDPA authorities at the given header from the source chain.
-	#[error("Failed to retrive {0} GRANDPA authorities set at header {1}: {2:?}")]
+	#[error("Failed to retrieve {0} GRANDPA authorities set at header {1}: {2:?}")]
 	RetrieveAuthorities(&'static str, Hash, client::Error),
 	/// Failed to decode GRANDPA authorities at the given header of the source chain.
 	#[error("Failed to decode {0} GRANDPA authorities set at header {1}: {2:?}")]
diff --git a/bridges/relays/messages/src/message_lane_loop.rs b/bridges/relays/messages/src/message_lane_loop.rs
index 36de637f04c437450e9054dd5088c3da5eb329cd..cdc94b9fae493cc9701449a1485308476d5c758f 100644
--- a/bridges/relays/messages/src/message_lane_loop.rs
+++ b/bridges/relays/messages/src/message_lane_loop.rs
@@ -1041,7 +1041,7 @@ pub(crate) mod tests {
 	#[test]
 	fn message_lane_loop_is_able_to_recover_from_unsuccessful_transaction() {
 		// with this configuration, both source and target clients will mine their transactions, but
-		// their corresponding nonce won't be udpated => reconnect will happen
+		// their corresponding nonce won't be updated => reconnect will happen
 		let (exit_sender, exit_receiver) = unbounded();
 		let result = run_loop_test(
 			Arc::new(Mutex::new(TestClientData {
diff --git a/bridges/snowbridge/primitives/core/src/location.rs b/bridges/snowbridge/primitives/core/src/location.rs
index f49a245c4126f410994390908a81b6d55e613b15..eb5ac66d46db7192d249141dbe7104fce8d619b0 100644
--- a/bridges/snowbridge/primitives/core/src/location.rs
+++ b/bridges/snowbridge/primitives/core/src/location.rs
@@ -206,7 +206,7 @@ mod tests {
 		for token in token_locations {
 			assert!(
 				TokenIdOf::convert_location(&token).is_some(),
-				"Valid token = {token:?} yeilds no TokenId."
+				"Valid token = {token:?} yields no TokenId."
 			);
 		}
 
@@ -220,7 +220,7 @@ mod tests {
 		for token in non_token_locations {
 			assert!(
 				TokenIdOf::convert_location(&token).is_none(),
-				"Invalid token = {token:?} yeilds a TokenId."
+				"Invalid token = {token:?} yields a TokenId."
 			);
 		}
 	}
diff --git a/cumulus/parachains/integration-tests/emulated/chains/parachains/assets/asset-hub-rococo/Cargo.toml b/cumulus/parachains/integration-tests/emulated/chains/parachains/assets/asset-hub-rococo/Cargo.toml
index a164a8197f72e6e22bf9b4a1e3a49815583992dc..c6a8baeff3b37b147c82b21bfcf1668a90d48e1d 100644
--- a/cumulus/parachains/integration-tests/emulated/chains/parachains/assets/asset-hub-rococo/Cargo.toml
+++ b/cumulus/parachains/integration-tests/emulated/chains/parachains/assets/asset-hub-rococo/Cargo.toml
@@ -14,6 +14,7 @@ workspace = true
 
 # Substrate
 frame-support = { workspace = true }
+pallet-asset-rewards = { workspace = true }
 sp-core = { workspace = true }
 sp-keyring = { workspace = true }
 
diff --git a/cumulus/parachains/integration-tests/emulated/common/src/lib.rs b/cumulus/parachains/integration-tests/emulated/common/src/lib.rs
index e2757f8b9a35b7ba07067ac708fe37babdd16af4..f5466a63f1f5b9b7018465268c15e2175d976a3c 100644
--- a/cumulus/parachains/integration-tests/emulated/common/src/lib.rs
+++ b/cumulus/parachains/integration-tests/emulated/common/src/lib.rs
@@ -58,6 +58,8 @@ pub const USDT_ID: u32 = 1984;
 
 pub const PENPAL_A_ID: u32 = 2000;
 pub const PENPAL_B_ID: u32 = 2001;
+pub const ASSET_HUB_ROCOCO_ID: u32 = 1000;
+pub const ASSET_HUB_WESTEND_ID: u32 = 1000;
 pub const ASSETS_PALLET_ID: u8 = 50;
 
 parameter_types! {
diff --git a/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-rococo/Cargo.toml b/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-rococo/Cargo.toml
index 9e8b8f2a52d7819e3af0422f9a4f9f534a407a1d..b53edb39c73b59555994a279f122af6c234a4ce0 100644
--- a/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-rococo/Cargo.toml
+++ b/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-rococo/Cargo.toml
@@ -17,6 +17,7 @@ codec = { workspace = true }
 # Substrate
 frame-support = { workspace = true }
 pallet-asset-conversion = { workspace = true }
+pallet-asset-rewards = { workspace = true }
 pallet-assets = { workspace = true }
 pallet-balances = { workspace = true }
 pallet-message-queue = { workspace = true }
diff --git a/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-rococo/src/lib.rs b/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-rococo/src/lib.rs
index f3a1b3f5bfa282b21aba49bbe79a81780508f975..513ca278a319e49e326ae0725d62cbb1324f48bf 100644
--- a/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-rococo/src/lib.rs
+++ b/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-rococo/src/lib.rs
@@ -76,10 +76,11 @@ mod imports {
 			genesis::ED as ROCOCO_ED,
 			rococo_runtime::{
 				governance as rococo_governance,
+				governance::pallet_custom_origins::Origin::Treasurer,
 				xcm_config::{
 					UniversalLocation as RococoUniversalLocation, XcmConfig as RococoXcmConfig,
 				},
-				OriginCaller as RococoOriginCaller,
+				Dmp, OriginCaller as RococoOriginCaller,
 			},
 			RococoRelayPallet as RococoPallet,
 		},
diff --git a/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-rococo/src/tests/mod.rs b/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-rococo/src/tests/mod.rs
index 88fa379c4072b97e867f3eccb68bb23b0eeedff2..75714acb07cd9e2b126bd4238e6c0377c8561e8f 100644
--- a/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-rococo/src/tests/mod.rs
+++ b/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-rococo/src/tests/mod.rs
@@ -16,6 +16,7 @@
 mod claim_assets;
 mod hybrid_transfers;
 mod reserve_transfer;
+mod reward_pool;
 mod send;
 mod set_xcm_versions;
 mod swap;
diff --git a/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-rococo/src/tests/reward_pool.rs b/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-rococo/src/tests/reward_pool.rs
new file mode 100644
index 0000000000000000000000000000000000000000..2f3ee536a7b9948e17d6be9ccf2033cce625b580
--- /dev/null
+++ b/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-rococo/src/tests/reward_pool.rs
@@ -0,0 +1,114 @@
+// Copyright (C) Parity Technologies (UK) Ltd.
+// SPDX-License-Identifier: Apache-2.0
+
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// 	http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+use crate::imports::*;
+use codec::Encode;
+use frame_support::{assert_ok, sp_runtime::traits::Dispatchable, traits::schedule::DispatchTime};
+use xcm_executor::traits::ConvertLocation;
+
+#[test]
+fn treasury_creates_asset_reward_pool() {
+	AssetHubRococo::execute_with(|| {
+		type RuntimeEvent = <AssetHubRococo as Chain>::RuntimeEvent;
+		type Balances = <AssetHubRococo as AssetHubRococoPallet>::Balances;
+
+		let treasurer =
+			Location::new(1, [Plurality { id: BodyId::Treasury, part: BodyPart::Voice }]);
+		let treasurer_account =
+			ahr_xcm_config::LocationToAccountId::convert_location(&treasurer).unwrap();
+
+		assert_ok!(Balances::force_set_balance(
+			<AssetHubRococo as Chain>::RuntimeOrigin::root(),
+			treasurer_account.clone().into(),
+			ASSET_HUB_ROCOCO_ED * 100_000,
+		));
+
+		let events = AssetHubRococo::events();
+		match events.iter().last() {
+			Some(RuntimeEvent::Balances(pallet_balances::Event::BalanceSet { who, .. })) =>
+				assert_eq!(*who, treasurer_account),
+			_ => panic!("Expected Balances::BalanceSet event"),
+		}
+	});
+
+	Rococo::execute_with(|| {
+		type AssetHubRococoRuntimeCall = <AssetHubRococo as Chain>::RuntimeCall;
+		type AssetHubRococoRuntime = <AssetHubRococo as Chain>::Runtime;
+		type RococoRuntimeCall = <Rococo as Chain>::RuntimeCall;
+		type RococoRuntime = <Rococo as Chain>::Runtime;
+		type RococoRuntimeEvent = <Rococo as Chain>::RuntimeEvent;
+		type RococoRuntimeOrigin = <Rococo as Chain>::RuntimeOrigin;
+
+		Dmp::make_parachain_reachable(AssetHubRococo::para_id());
+
+		let staked_asset_id = bx!(RelayLocation::get());
+		let reward_asset_id = bx!(RelayLocation::get());
+
+		let reward_rate_per_block = 1_000_000_000;
+		let lifetime = 1_000_000_000;
+		let admin = None;
+
+		let create_pool_call =
+			RococoRuntimeCall::XcmPallet(pallet_xcm::Call::<RococoRuntime>::send {
+				dest: bx!(VersionedLocation::V4(
+					xcm::v4::Junction::Parachain(AssetHubRococo::para_id().into()).into()
+				)),
+				message: bx!(VersionedXcm::V5(Xcm(vec![
+					UnpaidExecution { weight_limit: Unlimited, check_origin: None },
+					Transact {
+						origin_kind: OriginKind::SovereignAccount,
+						fallback_max_weight: None,
+						call: AssetHubRococoRuntimeCall::AssetRewards(
+							pallet_asset_rewards::Call::<AssetHubRococoRuntime>::create_pool {
+								staked_asset_id,
+								reward_asset_id,
+								reward_rate_per_block,
+								expiry: DispatchTime::After(lifetime),
+								admin
+							}
+						)
+						.encode()
+						.into(),
+					}
+				]))),
+			});
+
+		let treasury_origin: RococoRuntimeOrigin = Treasurer.into();
+		assert_ok!(create_pool_call.dispatch(treasury_origin));
+
+		assert_expected_events!(
+			Rococo,
+			vec![
+				RococoRuntimeEvent::XcmPallet(pallet_xcm::Event::Sent { .. }) => {},
+			]
+		);
+	});
+
+	AssetHubRococo::execute_with(|| {
+		type Runtime = <AssetHubRococo as Chain>::Runtime;
+		type RuntimeEvent = <AssetHubRococo as Chain>::RuntimeEvent;
+
+		assert_eq!(1, pallet_asset_rewards::Pools::<Runtime>::iter().count());
+
+		let events = AssetHubRococo::events();
+		match events.iter().last() {
+			Some(RuntimeEvent::MessageQueue(pallet_message_queue::Event::Processed {
+				success: true,
+				..
+			})) => (),
+			_ => panic!("Expected MessageQueue::Processed event"),
+		}
+	});
+}
diff --git a/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-westend/Cargo.toml b/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-westend/Cargo.toml
index 5cd00c239e60d24df018bccb5937fe4ed4277659..ef68a53c3b18bd629ca2feda51bb7b76defb081e 100644
--- a/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-westend/Cargo.toml
+++ b/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-westend/Cargo.toml
@@ -19,6 +19,7 @@ frame-metadata-hash-extension = { workspace = true, default-features = true }
 frame-support = { workspace = true }
 frame-system = { workspace = true }
 pallet-asset-conversion = { workspace = true }
+pallet-asset-rewards = { workspace = true }
 pallet-asset-tx-payment = { workspace = true }
 pallet-assets = { workspace = true }
 pallet-balances = { workspace = true }
diff --git a/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-westend/src/lib.rs b/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-westend/src/lib.rs
index 36630e2d22217a73f691d6ed780f0471a7ba2b05..68dc87250f76bdcd458166069cf4e4b78f61e94f 100644
--- a/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-westend/src/lib.rs
+++ b/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-westend/src/lib.rs
@@ -79,8 +79,12 @@ mod imports {
 		},
 		westend_emulated_chain::{
 			genesis::ED as WESTEND_ED,
-			westend_runtime::xcm_config::{
-				UniversalLocation as WestendUniversalLocation, XcmConfig as WestendXcmConfig,
+			westend_runtime::{
+				governance::pallet_custom_origins::Origin::Treasurer,
+				xcm_config::{
+					UniversalLocation as WestendUniversalLocation, XcmConfig as WestendXcmConfig,
+				},
+				Dmp,
 			},
 			WestendRelayPallet as WestendPallet,
 		},
diff --git a/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-westend/src/tests/mod.rs b/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-westend/src/tests/mod.rs
index 0dfe7a85f4c2a08ecb7d7701cabe64d0045a13f2..576c44fc542fdc5fd26365078d96ac7ee4796515 100644
--- a/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-westend/src/tests/mod.rs
+++ b/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-westend/src/tests/mod.rs
@@ -17,6 +17,7 @@ mod claim_assets;
 mod fellowship_treasury;
 mod hybrid_transfers;
 mod reserve_transfer;
+mod reward_pool;
 mod send;
 mod set_asset_claimer;
 mod set_xcm_versions;
diff --git a/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-westend/src/tests/reward_pool.rs b/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-westend/src/tests/reward_pool.rs
new file mode 100644
index 0000000000000000000000000000000000000000..4df51abcacebf4ff0102b5d50271ca9cad7e14e6
--- /dev/null
+++ b/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-westend/src/tests/reward_pool.rs
@@ -0,0 +1,113 @@
+// Copyright (C) Parity Technologies (UK) Ltd.
+// SPDX-License-Identifier: Apache-2.0
+
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// 	http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+use crate::imports::*;
+use codec::Encode;
+use frame_support::{assert_ok, sp_runtime::traits::Dispatchable, traits::schedule::DispatchTime};
+use xcm_executor::traits::ConvertLocation;
+
+#[test]
+fn treasury_creates_asset_reward_pool() {
+	AssetHubWestend::execute_with(|| {
+		type RuntimeEvent = <AssetHubWestend as Chain>::RuntimeEvent;
+		type Balances = <AssetHubWestend as AssetHubWestendPallet>::Balances;
+
+		let treasurer =
+			Location::new(1, [Plurality { id: BodyId::Treasury, part: BodyPart::Voice }]);
+		let treasurer_account =
+			ahw_xcm_config::LocationToAccountId::convert_location(&treasurer).unwrap();
+
+		assert_ok!(Balances::force_set_balance(
+			<AssetHubWestend as Chain>::RuntimeOrigin::root(),
+			treasurer_account.clone().into(),
+			ASSET_HUB_WESTEND_ED * 100_000,
+		));
+
+		let events = AssetHubWestend::events();
+		match events.iter().last() {
+			Some(RuntimeEvent::Balances(pallet_balances::Event::BalanceSet { who, .. })) =>
+				assert_eq!(*who, treasurer_account),
+			_ => panic!("Expected Balances::BalanceSet event"),
+		}
+	});
+	Westend::execute_with(|| {
+		type AssetHubWestendRuntimeCall = <AssetHubWestend as Chain>::RuntimeCall;
+		type AssetHubWestendRuntime = <AssetHubWestend as Chain>::Runtime;
+		type WestendRuntimeCall = <Westend as Chain>::RuntimeCall;
+		type WestendRuntime = <Westend as Chain>::Runtime;
+		type WestendRuntimeEvent = <Westend as Chain>::RuntimeEvent;
+		type WestendRuntimeOrigin = <Westend as Chain>::RuntimeOrigin;
+
+		Dmp::make_parachain_reachable(AssetHubWestend::para_id());
+
+		let staked_asset_id = bx!(RelayLocation::get());
+		let reward_asset_id = bx!(RelayLocation::get());
+
+		let reward_rate_per_block = 1_000_000_000;
+		let lifetime = 1_000_000_000;
+		let admin = None;
+
+		let create_pool_call =
+			WestendRuntimeCall::XcmPallet(pallet_xcm::Call::<WestendRuntime>::send {
+				dest: bx!(VersionedLocation::V4(
+					xcm::v4::Junction::Parachain(AssetHubWestend::para_id().into()).into()
+				)),
+				message: bx!(VersionedXcm::V5(Xcm(vec![
+					UnpaidExecution { weight_limit: Unlimited, check_origin: None },
+					Transact {
+						origin_kind: OriginKind::SovereignAccount,
+						fallback_max_weight: None,
+						call: AssetHubWestendRuntimeCall::AssetRewards(
+							pallet_asset_rewards::Call::<AssetHubWestendRuntime>::create_pool {
+								staked_asset_id,
+								reward_asset_id,
+								reward_rate_per_block,
+								expiry: DispatchTime::After(lifetime),
+								admin
+							}
+						)
+						.encode()
+						.into(),
+					}
+				]))),
+			});
+
+		let treasury_origin: WestendRuntimeOrigin = Treasurer.into();
+		assert_ok!(create_pool_call.dispatch(treasury_origin));
+
+		assert_expected_events!(
+			Westend,
+			vec![
+				WestendRuntimeEvent::XcmPallet(pallet_xcm::Event::Sent { .. }) => {},
+			]
+		);
+	});
+
+	AssetHubWestend::execute_with(|| {
+		type Runtime = <AssetHubWestend as Chain>::Runtime;
+		type RuntimeEvent = <AssetHubWestend as Chain>::RuntimeEvent;
+
+		assert_eq!(1, pallet_asset_rewards::Pools::<Runtime>::iter().count());
+
+		let events = AssetHubWestend::events();
+		match events.iter().last() {
+			Some(RuntimeEvent::MessageQueue(pallet_message_queue::Event::Processed {
+				success: true,
+				..
+			})) => (),
+			_ => panic!("Expected MessageQueue::Processed event"),
+		}
+	});
+}
diff --git a/cumulus/parachains/runtimes/assets/asset-hub-rococo/Cargo.toml b/cumulus/parachains/runtimes/assets/asset-hub-rococo/Cargo.toml
index abe59a8439a8af9f064dd179f07e629a9e7a5038..d612dd03c247a101049de02c4bf7822c73302783 100644
--- a/cumulus/parachains/runtimes/assets/asset-hub-rococo/Cargo.toml
+++ b/cumulus/parachains/runtimes/assets/asset-hub-rococo/Cargo.toml
@@ -30,6 +30,7 @@ frame-try-runtime = { optional = true, workspace = true }
 pallet-asset-conversion = { workspace = true }
 pallet-asset-conversion-ops = { workspace = true }
 pallet-asset-conversion-tx-payment = { workspace = true }
+pallet-asset-rewards = { workspace = true }
 pallet-assets = { workspace = true }
 pallet-assets-freezer = { workspace = true }
 pallet-aura = { workspace = true }
@@ -61,6 +62,7 @@ sp-storage = { workspace = true }
 sp-transaction-pool = { workspace = true }
 sp-version = { workspace = true }
 sp-weights = { workspace = true }
+
 # num-traits feature needed for dex integer sq root:
 primitive-types = { features = ["codec", "num-traits", "scale-info"], workspace = true }
 
@@ -123,6 +125,7 @@ runtime-benchmarks = [
 	"pallet-asset-conversion-ops/runtime-benchmarks",
 	"pallet-asset-conversion-tx-payment/runtime-benchmarks",
 	"pallet-asset-conversion/runtime-benchmarks",
+	"pallet-asset-rewards/runtime-benchmarks",
 	"pallet-assets-freezer/runtime-benchmarks",
 	"pallet-assets/runtime-benchmarks",
 	"pallet-balances/runtime-benchmarks",
@@ -162,6 +165,7 @@ try-runtime = [
 	"pallet-asset-conversion-ops/try-runtime",
 	"pallet-asset-conversion-tx-payment/try-runtime",
 	"pallet-asset-conversion/try-runtime",
+	"pallet-asset-rewards/try-runtime",
 	"pallet-assets-freezer/try-runtime",
 	"pallet-assets/try-runtime",
 	"pallet-aura/try-runtime",
@@ -212,6 +216,7 @@ std = [
 	"pallet-asset-conversion-ops/std",
 	"pallet-asset-conversion-tx-payment/std",
 	"pallet-asset-conversion/std",
+	"pallet-asset-rewards/std",
 	"pallet-assets-freezer/std",
 	"pallet-assets/std",
 	"pallet-aura/std",
diff --git a/cumulus/parachains/runtimes/assets/asset-hub-rococo/src/lib.rs b/cumulus/parachains/runtimes/assets/asset-hub-rococo/src/lib.rs
index db9a8201ebbe0517dceb1e7ba187171e32292721..43b7bf0ba1184e095c2f53eeefceb2b88b154a9a 100644
--- a/cumulus/parachains/runtimes/assets/asset-hub-rococo/src/lib.rs
+++ b/cumulus/parachains/runtimes/assets/asset-hub-rococo/src/lib.rs
@@ -35,7 +35,7 @@ use assets_common::{
 	foreign_creators::ForeignCreators,
 	local_and_foreign_assets::{LocalFromLeft, TargetFromLeft},
 	matching::{FromNetwork, FromSiblingParachain},
-	AssetIdForTrustBackedAssetsConvert,
+	AssetIdForPoolAssets, AssetIdForPoolAssetsConvert, AssetIdForTrustBackedAssetsConvert,
 };
 use cumulus_pallet_parachain_system::RelayNumberMonotonicallyIncreases;
 use cumulus_primitives_core::{AggregateMessageOrigin, ClaimQueueOffset, CoreSelector};
@@ -61,9 +61,9 @@ use frame_support::{
 	genesis_builder_helper::{build_state, get_preset},
 	ord_parameter_types, parameter_types,
 	traits::{
-		fungible, fungibles, tokens::imbalance::ResolveAssetTo, AsEnsureOriginWithArg, ConstBool,
-		ConstU128, ConstU32, ConstU64, ConstU8, EitherOfDiverse, Equals, InstanceFilter,
-		TransformOrigin,
+		fungible, fungible::HoldConsideration, fungibles, tokens::imbalance::ResolveAssetTo,
+		AsEnsureOriginWithArg, ConstBool, ConstU128, ConstU32, ConstU64, ConstU8,
+		ConstantStoragePrice, EitherOfDiverse, Equals, InstanceFilter, TransformOrigin,
 	},
 	weights::{ConstantMultiplier, Weight, WeightToFee as _},
 	BoundedVec, PalletId,
@@ -84,8 +84,8 @@ use sp_runtime::{Perbill, RuntimeDebug};
 use testnet_parachains_constants::rococo::{consensus::*, currency::*, fee::WeightToFee, time::*};
 use xcm_config::{
 	ForeignAssetsConvertedConcreteId, GovernanceLocation, LocationToAccountId,
-	PoolAssetsConvertedConcreteId, TokenLocation, TrustBackedAssetsConvertedConcreteId,
-	TrustBackedAssetsPalletLocation,
+	PoolAssetsConvertedConcreteId, PoolAssetsPalletLocation, TokenLocation,
+	TrustBackedAssetsConvertedConcreteId, TrustBackedAssetsPalletLocation,
 };
 
 #[cfg(test)]
@@ -111,6 +111,9 @@ use xcm_runtime_apis::{
 	fees::Error as XcmPaymentApiError,
 };
 
+#[cfg(feature = "runtime-benchmarks")]
+use frame_support::traits::PalletInfoAccess;
+
 use weights::{BlockExecutionWeight, ExtrinsicBaseWeight, RocksDbWeight};
 
 impl_opaque_keys! {
@@ -217,8 +220,8 @@ impl pallet_balances::Config for Runtime {
 	type ReserveIdentifier = [u8; 8];
 	type RuntimeHoldReason = RuntimeHoldReason;
 	type RuntimeFreezeReason = RuntimeFreezeReason;
-	type FreezeIdentifier = ();
-	type MaxFreezes = ConstU32<0>;
+	type FreezeIdentifier = RuntimeFreezeReason;
+	type MaxFreezes = ConstU32<50>;
 	type DoneSlashHandler = ();
 }
 
@@ -302,7 +305,7 @@ impl pallet_assets::Config<PoolAssetsInstance> for Runtime {
 	type RuntimeEvent = RuntimeEvent;
 	type Balance = Balance;
 	type RemoveItemsLimit = ConstU32<1000>;
-	type AssetId = u32;
+	type AssetId = AssetIdForPoolAssets;
 	type AssetIdParameter = u32;
 	type Currency = Balances;
 	type CreateOrigin =
@@ -343,8 +346,21 @@ pub type LocalAndForeignAssets = fungibles::UnionOf<
 	AccountId,
 >;
 
-/// Union fungibles implementation for [`LocalAndForeignAssets`] and `Balances`.
-pub type NativeAndAssets = fungible::UnionOf<
+/// Union fungibles implementation for `AssetsFreezer` and `ForeignAssetsFreezer`.
+pub type LocalAndForeignAssetsFreezer = fungibles::UnionOf<
+	AssetsFreezer,
+	ForeignAssetsFreezer,
+	LocalFromLeft<
+		AssetIdForTrustBackedAssetsConvert<TrustBackedAssetsPalletLocation, xcm::v5::Location>,
+		AssetIdForTrustBackedAssets,
+		xcm::v5::Location,
+	>,
+	xcm::v5::Location,
+	AccountId,
+>;
+
+/// Union fungibles implementation for [`LocalAndForeignAssets`] and [`Balances`].
+pub type NativeAndNonPoolAssets = fungible::UnionOf<
 	Balances,
 	LocalAndForeignAssets,
 	TargetFromLeft<TokenLocation, xcm::v5::Location>,
@@ -352,6 +368,45 @@ pub type NativeAndAssets = fungible::UnionOf<
 	AccountId,
 >;
 
+/// Union fungibles implementation for [`LocalAndForeignAssetsFreezer`] and [`Balances`].
+pub type NativeAndNonPoolAssetsFreezer = fungible::UnionOf<
+	Balances,
+	LocalAndForeignAssetsFreezer,
+	TargetFromLeft<TokenLocation, xcm::v5::Location>,
+	xcm::v5::Location,
+	AccountId,
+>;
+
+/// Union fungibles implementation for [`PoolAssets`] and [`NativeAndNonPoolAssets`].
+///
+/// NOTE: Should be kept updated to include ALL balances and assets in the runtime.
+pub type NativeAndAllAssets = fungibles::UnionOf<
+	PoolAssets,
+	NativeAndNonPoolAssets,
+	LocalFromLeft<
+		AssetIdForPoolAssetsConvert<PoolAssetsPalletLocation, xcm::v5::Location>,
+		AssetIdForPoolAssets,
+		xcm::v5::Location,
+	>,
+	xcm::v5::Location,
+	AccountId,
+>;
+
+/// Union fungibles implementation for [`PoolAssetsFreezer`] and [`NativeAndNonPoolAssetsFreezer`].
+///
+/// NOTE: Should be kept updated to include ALL balances and assets in the runtime.
+pub type NativeAndAllAssetsFreezer = fungibles::UnionOf<
+	PoolAssetsFreezer,
+	NativeAndNonPoolAssetsFreezer,
+	LocalFromLeft<
+		AssetIdForPoolAssetsConvert<PoolAssetsPalletLocation, xcm::v5::Location>,
+		AssetIdForPoolAssets,
+		xcm::v5::Location,
+	>,
+	xcm::v5::Location,
+	AccountId,
+>;
+
 pub type PoolIdToAccountId = pallet_asset_conversion::AccountIdConverter<
 	AssetConversionPalletId,
 	(xcm::v5::Location, xcm::v5::Location),
@@ -362,7 +417,7 @@ impl pallet_asset_conversion::Config for Runtime {
 	type Balance = Balance;
 	type HigherPrecisionBalance = sp_core::U256;
 	type AssetKind = xcm::v5::Location;
-	type Assets = NativeAndAssets;
+	type Assets = NativeAndNonPoolAssets;
 	type PoolId = (Self::AssetKind, Self::AssetKind);
 	type PoolLocator = pallet_asset_conversion::WithFirstAsset<
 		TokenLocation,
@@ -823,9 +878,9 @@ impl pallet_asset_conversion_tx_payment::Config for Runtime {
 	type AssetId = xcm::v5::Location;
 	type OnChargeAssetTransaction = SwapAssetAdapter<
 		TokenLocation,
-		NativeAndAssets,
+		NativeAndNonPoolAssets,
 		AssetConversion,
-		ResolveAssetTo<StakingPot, NativeAndAssets>,
+		ResolveAssetTo<StakingPot, NativeAndNonPoolAssets>,
 	>;
 	type WeightInfo = weights::pallet_asset_conversion_tx_payment::WeightInfo<Runtime>;
 	#[cfg(feature = "runtime-benchmarks")]
@@ -953,6 +1008,55 @@ impl pallet_xcm_bridge_hub_router::Config<ToWestendXcmRouterInstance> for Runtim
 	type FeeAsset = xcm_config::bridging::XcmBridgeHubRouterFeeAssetId;
 }
 
+#[cfg(feature = "runtime-benchmarks")]
+pub struct PalletAssetRewardsBenchmarkHelper;
+
+#[cfg(feature = "runtime-benchmarks")]
+impl pallet_asset_rewards::benchmarking::BenchmarkHelper<xcm::v5::Location>
+	for PalletAssetRewardsBenchmarkHelper
+{
+	fn staked_asset() -> Location {
+		Location::new(
+			0,
+			[PalletInstance(<Assets as PalletInfoAccess>::index() as u8), GeneralIndex(100)],
+		)
+	}
+	fn reward_asset() -> Location {
+		Location::new(
+			0,
+			[PalletInstance(<Assets as PalletInfoAccess>::index() as u8), GeneralIndex(101)],
+		)
+	}
+}
+
+parameter_types! {
+	pub const AssetRewardsPalletId: PalletId = PalletId(*b"py/astrd");
+	pub const RewardsPoolCreationHoldReason: RuntimeHoldReason =
+		RuntimeHoldReason::AssetRewards(pallet_asset_rewards::HoldReason::PoolCreation);
+	// 1 item, 135 bytes into the storage on pool creation.
+	pub const StakePoolCreationDeposit: Balance = deposit(1, 135);
+}
+
+impl pallet_asset_rewards::Config for Runtime {
+	type RuntimeEvent = RuntimeEvent;
+	type PalletId = AssetRewardsPalletId;
+	type Balance = Balance;
+	type Assets = NativeAndAllAssets;
+	type AssetsFreezer = NativeAndAllAssetsFreezer;
+	type AssetId = xcm::v5::Location;
+	type CreatePoolOrigin = EnsureSigned<AccountId>;
+	type RuntimeFreezeReason = RuntimeFreezeReason;
+	type Consideration = HoldConsideration<
+		AccountId,
+		Balances,
+		RewardsPoolCreationHoldReason,
+		ConstantStoragePrice<StakePoolCreationDeposit, Balance>,
+	>;
+	type WeightInfo = weights::pallet_asset_rewards::WeightInfo<Runtime>;
+	#[cfg(feature = "runtime-benchmarks")]
+	type BenchmarkHelper = PalletAssetRewardsBenchmarkHelper;
+}
+
 // Create the runtime by composing the FRAME pallets that were previously configured.
 construct_runtime!(
 	pub enum Runtime
@@ -998,10 +1102,13 @@ construct_runtime!(
 		NftFractionalization: pallet_nft_fractionalization = 54,
 		PoolAssets: pallet_assets::<Instance3> = 55,
 		AssetConversion: pallet_asset_conversion = 56,
+
 		AssetsFreezer: pallet_assets_freezer::<Instance1> = 57,
 		ForeignAssetsFreezer: pallet_assets_freezer::<Instance2> = 58,
 		PoolAssetsFreezer: pallet_assets_freezer::<Instance3> = 59,
 
+		AssetRewards: pallet_asset_rewards = 60,
+
 		// TODO: the pallet instance should be removed once all pools have migrated
 		// to the new account IDs.
 		AssetConversionMigration: pallet_asset_conversion_ops = 200,
@@ -1193,6 +1300,7 @@ mod benches {
 		[pallet_assets, Foreign]
 		[pallet_assets, Pool]
 		[pallet_asset_conversion, AssetConversion]
+		[pallet_asset_rewards, AssetRewards]
 		[pallet_asset_conversion_tx_payment, AssetTxPayment]
 		[pallet_balances, Balances]
 		[pallet_message_queue, MessageQueue]
@@ -1503,6 +1611,12 @@ impl_runtime_apis! {
 		}
 	}
 
+	impl pallet_asset_rewards::AssetRewards<Block, Balance> for Runtime {
+		fn pool_creation_cost() -> Balance {
+			StakePoolCreationDeposit::get()
+		}
+	}
+
 	impl cumulus_primitives_core::GetCoreSelectorApi<Block> for Runtime {
 		fn core_selector() -> (CoreSelector, ClaimQueueOffset) {
 			ParachainSystem::core_selector()
diff --git a/cumulus/parachains/runtimes/assets/asset-hub-rococo/src/weights/mod.rs b/cumulus/parachains/runtimes/assets/asset-hub-rococo/src/weights/mod.rs
index ae78a56d8b3c1fef27e54e37b940098396426883..6893766ac72d2113f993e54d3b28e5e136acac3d 100644
--- a/cumulus/parachains/runtimes/assets/asset-hub-rococo/src/weights/mod.rs
+++ b/cumulus/parachains/runtimes/assets/asset-hub-rococo/src/weights/mod.rs
@@ -24,6 +24,7 @@ pub mod frame_system_extensions;
 pub mod pallet_asset_conversion;
 pub mod pallet_asset_conversion_ops;
 pub mod pallet_asset_conversion_tx_payment;
+pub mod pallet_asset_rewards;
 pub mod pallet_assets_foreign;
 pub mod pallet_assets_local;
 pub mod pallet_assets_pool;
diff --git a/cumulus/parachains/runtimes/assets/asset-hub-rococo/src/weights/pallet_asset_rewards.rs b/cumulus/parachains/runtimes/assets/asset-hub-rococo/src/weights/pallet_asset_rewards.rs
new file mode 100644
index 0000000000000000000000000000000000000000..218c93c51035037e5d325dc5b3d31897e164f0ec
--- /dev/null
+++ b/cumulus/parachains/runtimes/assets/asset-hub-rococo/src/weights/pallet_asset_rewards.rs
@@ -0,0 +1,217 @@
+// Copyright (C) Parity Technologies (UK) Ltd.
+// This file is part of Cumulus.
+
+// Cumulus is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+
+// Cumulus is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+
+// You should have received a copy of the GNU General Public License
+// along with Cumulus.  If not, see <http://www.gnu.org/licenses/>.
+
+//! Autogenerated weights for `pallet_asset_rewards`
+//!
+//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 32.0.0
+//! DATE: 2025-01-14, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]`
+//! WORST CASE MAP SIZE: `1000000`
+//! HOSTNAME: `runner-ys-ssygq-project-674-concurrent-0`, CPU: `Intel(R) Xeon(R) CPU @ 2.60GHz`
+//! WASM-EXECUTION: `Compiled`, CHAIN: `Some("asset-hub-rococo-dev")`, DB CACHE: 1024
+
+// Executed Command:
+// target/production/polkadot-parachain
+// benchmark
+// pallet
+// --steps=50
+// --repeat=20
+// --extrinsic=*
+// --wasm-execution=compiled
+// --heap-pages=4096
+// --json-file=/builds/parity/mirrors/polkadot-sdk/.git/.artifacts/bench.json
+// --pallet=pallet_asset_rewards
+// --chain=asset-hub-rococo-dev
+// --header=./cumulus/file_header.txt
+// --output=./cumulus/parachains/runtimes/assets/asset-hub-rococo/src/weights/
+
+#![cfg_attr(rustfmt, rustfmt_skip)]
+#![allow(unused_parens)]
+#![allow(unused_imports)]
+#![allow(missing_docs)]
+
+use frame_support::{traits::Get, weights::Weight};
+use core::marker::PhantomData;
+
+/// Weight functions for `pallet_asset_rewards`.
+pub struct WeightInfo<T>(PhantomData<T>);
+impl<T: frame_system::Config> pallet_asset_rewards::WeightInfo for WeightInfo<T> {
+	/// Storage: `Assets::Asset` (r:2 w:0)
+	/// Proof: `Assets::Asset` (`max_values`: None, `max_size`: Some(210), added: 2685, mode: `MaxEncodedLen`)
+	/// Storage: `AssetRewards::NextPoolId` (r:1 w:1)
+	/// Proof: `AssetRewards::NextPoolId` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`)
+	/// Storage: `System::Account` (r:1 w:1)
+	/// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`)
+	/// Storage: `Balances::Holds` (r:1 w:1)
+	/// Proof: `Balances::Holds` (`max_values`: None, `max_size`: Some(85), added: 2560, mode: `MaxEncodedLen`)
+	/// Storage: `AssetRewards::PoolCost` (r:0 w:1)
+	/// Proof: `AssetRewards::PoolCost` (`max_values`: None, `max_size`: Some(68), added: 2543, mode: `MaxEncodedLen`)
+	/// Storage: `AssetRewards::Pools` (r:0 w:1)
+	/// Proof: `AssetRewards::Pools` (`max_values`: None, `max_size`: Some(1344), added: 3819, mode: `MaxEncodedLen`)
+	fn create_pool() -> Weight {
+		// Proof Size summary in bytes:
+		//  Measured:  `358`
+		//  Estimated: `6360`
+		// Minimum execution time: 65_882_000 picoseconds.
+		Weight::from_parts(67_073_000, 0)
+			.saturating_add(Weight::from_parts(0, 6360))
+			.saturating_add(T::DbWeight::get().reads(5))
+			.saturating_add(T::DbWeight::get().writes(5))
+	}
+	/// Storage: `AssetRewards::Pools` (r:1 w:1)
+	/// Proof: `AssetRewards::Pools` (`max_values`: None, `max_size`: Some(1344), added: 3819, mode: `MaxEncodedLen`)
+	/// Storage: `AssetRewards::PoolStakers` (r:1 w:1)
+	/// Proof: `AssetRewards::PoolStakers` (`max_values`: None, `max_size`: Some(116), added: 2591, mode: `MaxEncodedLen`)
+	/// Storage: `AssetsFreezer::Freezes` (r:1 w:1)
+	/// Proof: `AssetsFreezer::Freezes` (`max_values`: None, `max_size`: Some(87), added: 2562, mode: `MaxEncodedLen`)
+	/// Storage: `Assets::Account` (r:1 w:0)
+	/// Proof: `Assets::Account` (`max_values`: None, `max_size`: Some(134), added: 2609, mode: `MaxEncodedLen`)
+	/// Storage: `AssetsFreezer::FrozenBalances` (r:1 w:1)
+	/// Proof: `AssetsFreezer::FrozenBalances` (`max_values`: None, `max_size`: Some(84), added: 2559, mode: `MaxEncodedLen`)
+	fn stake() -> Weight {
+		// Proof Size summary in bytes:
+		//  Measured:  `872`
+		//  Estimated: `4809`
+		// Minimum execution time: 56_950_000 picoseconds.
+		Weight::from_parts(58_088_000, 0)
+			.saturating_add(Weight::from_parts(0, 4809))
+			.saturating_add(T::DbWeight::get().reads(5))
+			.saturating_add(T::DbWeight::get().writes(4))
+	}
+	/// Storage: `AssetRewards::Pools` (r:1 w:1)
+	/// Proof: `AssetRewards::Pools` (`max_values`: None, `max_size`: Some(1344), added: 3819, mode: `MaxEncodedLen`)
+	/// Storage: `AssetRewards::PoolStakers` (r:1 w:1)
+	/// Proof: `AssetRewards::PoolStakers` (`max_values`: None, `max_size`: Some(116), added: 2591, mode: `MaxEncodedLen`)
+	/// Storage: `AssetsFreezer::Freezes` (r:1 w:1)
+	/// Proof: `AssetsFreezer::Freezes` (`max_values`: None, `max_size`: Some(87), added: 2562, mode: `MaxEncodedLen`)
+	/// Storage: `Assets::Account` (r:1 w:0)
+	/// Proof: `Assets::Account` (`max_values`: None, `max_size`: Some(134), added: 2609, mode: `MaxEncodedLen`)
+	/// Storage: `AssetsFreezer::FrozenBalances` (r:1 w:1)
+	/// Proof: `AssetsFreezer::FrozenBalances` (`max_values`: None, `max_size`: Some(84), added: 2559, mode: `MaxEncodedLen`)
+	fn unstake() -> Weight {
+		// Proof Size summary in bytes:
+		//  Measured:  `872`
+		//  Estimated: `4809`
+		// Minimum execution time: 59_509_000 picoseconds.
+		Weight::from_parts(61_064_000, 0)
+			.saturating_add(Weight::from_parts(0, 4809))
+			.saturating_add(T::DbWeight::get().reads(5))
+			.saturating_add(T::DbWeight::get().writes(4))
+	}
+	/// Storage: `AssetRewards::Pools` (r:1 w:0)
+	/// Proof: `AssetRewards::Pools` (`max_values`: None, `max_size`: Some(1344), added: 3819, mode: `MaxEncodedLen`)
+	/// Storage: `AssetRewards::PoolStakers` (r:1 w:1)
+	/// Proof: `AssetRewards::PoolStakers` (`max_values`: None, `max_size`: Some(116), added: 2591, mode: `MaxEncodedLen`)
+	/// Storage: `Assets::Asset` (r:1 w:1)
+	/// Proof: `Assets::Asset` (`max_values`: None, `max_size`: Some(210), added: 2685, mode: `MaxEncodedLen`)
+	/// Storage: `Assets::Account` (r:2 w:2)
+	/// Proof: `Assets::Account` (`max_values`: None, `max_size`: Some(134), added: 2609, mode: `MaxEncodedLen`)
+	/// Storage: `AssetsFreezer::FrozenBalances` (r:1 w:0)
+	/// Proof: `AssetsFreezer::FrozenBalances` (`max_values`: None, `max_size`: Some(84), added: 2559, mode: `MaxEncodedLen`)
+	fn harvest_rewards() -> Weight {
+		// Proof Size summary in bytes:
+		//  Measured:  `1072`
+		//  Estimated: `6208`
+		// Minimum execution time: 80_685_000 picoseconds.
+		Weight::from_parts(83_505_000, 0)
+			.saturating_add(Weight::from_parts(0, 6208))
+			.saturating_add(T::DbWeight::get().reads(6))
+			.saturating_add(T::DbWeight::get().writes(4))
+	}
+	/// Storage: `AssetRewards::Pools` (r:1 w:1)
+	/// Proof: `AssetRewards::Pools` (`max_values`: None, `max_size`: Some(1344), added: 3819, mode: `MaxEncodedLen`)
+	fn set_pool_reward_rate_per_block() -> Weight {
+		// Proof Size summary in bytes:
+		//  Measured:  `318`
+		//  Estimated: `4809`
+		// Minimum execution time: 17_032_000 picoseconds.
+		Weight::from_parts(17_628_000, 0)
+			.saturating_add(Weight::from_parts(0, 4809))
+			.saturating_add(T::DbWeight::get().reads(1))
+			.saturating_add(T::DbWeight::get().writes(1))
+	}
+	/// Storage: `AssetRewards::Pools` (r:1 w:1)
+	/// Proof: `AssetRewards::Pools` (`max_values`: None, `max_size`: Some(1344), added: 3819, mode: `MaxEncodedLen`)
+	fn set_pool_admin() -> Weight {
+		// Proof Size summary in bytes:
+		//  Measured:  `318`
+		//  Estimated: `4809`
+		// Minimum execution time: 15_290_000 picoseconds.
+		Weight::from_parts(16_212_000, 0)
+			.saturating_add(Weight::from_parts(0, 4809))
+			.saturating_add(T::DbWeight::get().reads(1))
+			.saturating_add(T::DbWeight::get().writes(1))
+	}
+	/// Storage: `AssetRewards::Pools` (r:1 w:1)
+	/// Proof: `AssetRewards::Pools` (`max_values`: None, `max_size`: Some(1344), added: 3819, mode: `MaxEncodedLen`)
+	fn set_pool_expiry_block() -> Weight {
+		// Proof Size summary in bytes:
+		//  Measured:  `318`
+		//  Estimated: `4809`
+		// Minimum execution time: 17_721_000 picoseconds.
+		Weight::from_parts(18_603_000, 0)
+			.saturating_add(Weight::from_parts(0, 4809))
+			.saturating_add(T::DbWeight::get().reads(1))
+			.saturating_add(T::DbWeight::get().writes(1))
+	}
+	/// Storage: `AssetRewards::Pools` (r:1 w:0)
+	/// Proof: `AssetRewards::Pools` (`max_values`: None, `max_size`: Some(1344), added: 3819, mode: `MaxEncodedLen`)
+	/// Storage: `Assets::Asset` (r:1 w:1)
+	/// Proof: `Assets::Asset` (`max_values`: None, `max_size`: Some(210), added: 2685, mode: `MaxEncodedLen`)
+	/// Storage: `Assets::Account` (r:2 w:2)
+	/// Proof: `Assets::Account` (`max_values`: None, `max_size`: Some(134), added: 2609, mode: `MaxEncodedLen`)
+	/// Storage: `AssetsFreezer::FrozenBalances` (r:1 w:0)
+	/// Proof: `AssetsFreezer::FrozenBalances` (`max_values`: None, `max_size`: Some(84), added: 2559, mode: `MaxEncodedLen`)
+	/// Storage: `System::Account` (r:1 w:1)
+	/// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`)
+	fn deposit_reward_tokens() -> Weight {
+		// Proof Size summary in bytes:
+		//  Measured:  `747`
+		//  Estimated: `6208`
+		// Minimum execution time: 67_754_000 picoseconds.
+		Weight::from_parts(69_428_000, 0)
+			.saturating_add(Weight::from_parts(0, 6208))
+			.saturating_add(T::DbWeight::get().reads(6))
+			.saturating_add(T::DbWeight::get().writes(4))
+	}
+	/// Storage: `AssetRewards::Pools` (r:1 w:1)
+	/// Proof: `AssetRewards::Pools` (`max_values`: None, `max_size`: Some(1344), added: 3819, mode: `MaxEncodedLen`)
+	/// Storage: `AssetRewards::PoolStakers` (r:1 w:0)
+	/// Proof: `AssetRewards::PoolStakers` (`max_values`: None, `max_size`: Some(116), added: 2591, mode: `MaxEncodedLen`)
+	/// Storage: `Assets::Asset` (r:1 w:1)
+	/// Proof: `Assets::Asset` (`max_values`: None, `max_size`: Some(210), added: 2685, mode: `MaxEncodedLen`)
+	/// Storage: `Assets::Account` (r:2 w:2)
+	/// Proof: `Assets::Account` (`max_values`: None, `max_size`: Some(134), added: 2609, mode: `MaxEncodedLen`)
+	/// Storage: `AssetsFreezer::FrozenBalances` (r:1 w:1)
+	/// Proof: `AssetsFreezer::FrozenBalances` (`max_values`: None, `max_size`: Some(84), added: 2559, mode: `MaxEncodedLen`)
+	/// Storage: `System::Account` (r:2 w:2)
+	/// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`)
+	/// Storage: `AssetRewards::PoolCost` (r:1 w:1)
+	/// Proof: `AssetRewards::PoolCost` (`max_values`: None, `max_size`: Some(68), added: 2543, mode: `MaxEncodedLen`)
+	/// Storage: `Balances::Holds` (r:1 w:1)
+	/// Proof: `Balances::Holds` (`max_values`: None, `max_size`: Some(85), added: 2560, mode: `MaxEncodedLen`)
+	/// Storage: `AssetsFreezer::Freezes` (r:0 w:1)
+	/// Proof: `AssetsFreezer::Freezes` (`max_values`: None, `max_size`: Some(87), added: 2562, mode: `MaxEncodedLen`)
+	fn cleanup_pool() -> Weight {
+		// Proof Size summary in bytes:
+		//  Measured:  `1105`
+		//  Estimated: `6208`
+		// Minimum execution time: 127_524_000 picoseconds.
+		Weight::from_parts(130_238_000, 0)
+			.saturating_add(Weight::from_parts(0, 6208))
+			.saturating_add(T::DbWeight::get().reads(10))
+			.saturating_add(T::DbWeight::get().writes(10))
+	}
+}
diff --git a/cumulus/parachains/runtimes/assets/asset-hub-rococo/src/xcm_config.rs b/cumulus/parachains/runtimes/assets/asset-hub-rococo/src/xcm_config.rs
index 08b2f520c4b9ac1abb39f9d185c821dec7241d0f..0c6ff5e4bfddc6c236fe1ec4a5a5f425027f41f5 100644
--- a/cumulus/parachains/runtimes/assets/asset-hub-rococo/src/xcm_config.rs
+++ b/cumulus/parachains/runtimes/assets/asset-hub-rococo/src/xcm_config.rs
@@ -76,6 +76,10 @@ parameter_types! {
 	pub TrustBackedAssetsPalletLocation: Location =
 		PalletInstance(TrustBackedAssetsPalletIndex::get()).into();
 	pub TrustBackedAssetsPalletIndex: u8 = <Assets as PalletInfoAccess>::index() as u8;
+	pub TrustBackedAssetsPalletLocationV3: xcm::v3::Location =
+		xcm::v3::Junction::PalletInstance(<Assets as PalletInfoAccess>::index() as u8).into();
+	pub PoolAssetsPalletLocationV3: xcm::v3::Location =
+		xcm::v3::Junction::PalletInstance(<PoolAssets as PalletInfoAccess>::index() as u8).into();
 	pub ForeignAssetsPalletLocation: Location =
 		PalletInstance(<ForeignAssets as PalletInfoAccess>::index() as u8).into();
 	pub PoolAssetsPalletLocation: Location =
@@ -336,7 +340,7 @@ pub type TrustedTeleporters = (
 /// asset and the asset required for fee payment.
 pub type PoolAssetsExchanger = SingleAssetExchangeAdapter<
 	crate::AssetConversion,
-	crate::NativeAndAssets,
+	crate::NativeAndNonPoolAssets,
 	(
 		TrustBackedAssetsAsLocation<TrustBackedAssetsPalletLocation, Balance, xcm::v5::Location>,
 		ForeignAssetsConvertedConcreteId,
@@ -387,7 +391,7 @@ impl xcm_executor::Config for XcmConfig {
 			TokenLocation,
 			crate::AssetConversion,
 			WeightToFee,
-			crate::NativeAndAssets,
+			crate::NativeAndNonPoolAssets,
 			(
 				TrustBackedAssetsAsLocation<
 					TrustBackedAssetsPalletLocation,
@@ -396,7 +400,7 @@ impl xcm_executor::Config for XcmConfig {
 				>,
 				ForeignAssetsConvertedConcreteId,
 			),
-			ResolveAssetTo<StakingPot, crate::NativeAndAssets>,
+			ResolveAssetTo<StakingPot, crate::NativeAndNonPoolAssets>,
 			AccountId,
 		>,
 		// This trader allows to pay with `is_sufficient=true` "Trust Backed" assets from dedicated
diff --git a/cumulus/parachains/runtimes/assets/asset-hub-westend/Cargo.toml b/cumulus/parachains/runtimes/assets/asset-hub-westend/Cargo.toml
index cb10ae9a480034b3067e436a4760692357789645..65ef63a7fb356c1b2c20a9fcddbc4bdc3d3b8bff 100644
--- a/cumulus/parachains/runtimes/assets/asset-hub-westend/Cargo.toml
+++ b/cumulus/parachains/runtimes/assets/asset-hub-westend/Cargo.toml
@@ -30,6 +30,7 @@ frame-try-runtime = { optional = true, workspace = true }
 pallet-asset-conversion = { workspace = true }
 pallet-asset-conversion-ops = { workspace = true }
 pallet-asset-conversion-tx-payment = { workspace = true }
+pallet-asset-rewards = { workspace = true }
 pallet-assets = { workspace = true }
 pallet-assets-freezer = { workspace = true }
 pallet-aura = { workspace = true }
@@ -62,6 +63,7 @@ sp-std = { workspace = true }
 sp-storage = { workspace = true }
 sp-transaction-pool = { workspace = true }
 sp-version = { workspace = true }
+
 # num-traits feature needed for dex integer sq root:
 primitive-types = { features = ["codec", "num-traits", "scale-info"], workspace = true }
 
@@ -125,6 +127,7 @@ runtime-benchmarks = [
 	"pallet-asset-conversion-ops/runtime-benchmarks",
 	"pallet-asset-conversion-tx-payment/runtime-benchmarks",
 	"pallet-asset-conversion/runtime-benchmarks",
+	"pallet-asset-rewards/runtime-benchmarks",
 	"pallet-assets-freezer/runtime-benchmarks",
 	"pallet-assets/runtime-benchmarks",
 	"pallet-balances/runtime-benchmarks",
@@ -166,6 +169,7 @@ try-runtime = [
 	"pallet-asset-conversion-ops/try-runtime",
 	"pallet-asset-conversion-tx-payment/try-runtime",
 	"pallet-asset-conversion/try-runtime",
+	"pallet-asset-rewards/try-runtime",
 	"pallet-assets-freezer/try-runtime",
 	"pallet-assets/try-runtime",
 	"pallet-aura/try-runtime",
@@ -218,6 +222,7 @@ std = [
 	"pallet-asset-conversion-ops/std",
 	"pallet-asset-conversion-tx-payment/std",
 	"pallet-asset-conversion/std",
+	"pallet-asset-rewards/std",
 	"pallet-assets-freezer/std",
 	"pallet-assets/std",
 	"pallet-aura/std",
diff --git a/cumulus/parachains/runtimes/assets/asset-hub-westend/src/lib.rs b/cumulus/parachains/runtimes/assets/asset-hub-westend/src/lib.rs
index cfc150ce5d6f7614c1d23cc6cb0bfb408a11f722..41f29fe2c56a098dd64adfaa39f2c28bfb968e01 100644
--- a/cumulus/parachains/runtimes/assets/asset-hub-westend/src/lib.rs
+++ b/cumulus/parachains/runtimes/assets/asset-hub-westend/src/lib.rs
@@ -33,7 +33,7 @@ extern crate alloc;
 use alloc::{vec, vec::Vec};
 use assets_common::{
 	local_and_foreign_assets::{LocalFromLeft, TargetFromLeft},
-	AssetIdForTrustBackedAssetsConvert,
+	AssetIdForPoolAssets, AssetIdForPoolAssetsConvert, AssetIdForTrustBackedAssetsConvert,
 };
 use codec::{Decode, Encode, MaxEncodedLen};
 use cumulus_pallet_parachain_system::RelayNumberMonotonicallyIncreases;
@@ -44,10 +44,12 @@ use frame_support::{
 	genesis_builder_helper::{build_state, get_preset},
 	ord_parameter_types, parameter_types,
 	traits::{
-		fungible, fungibles,
+		fungible,
+		fungible::HoldConsideration,
+		fungibles,
 		tokens::{imbalance::ResolveAssetTo, nonfungibles_v2::Inspect},
-		AsEnsureOriginWithArg, ConstBool, ConstU128, ConstU32, ConstU64, ConstU8, Equals,
-		InstanceFilter, Nothing, TransformOrigin,
+		AsEnsureOriginWithArg, ConstBool, ConstU128, ConstU32, ConstU64, ConstU8,
+		ConstantStoragePrice, Equals, InstanceFilter, Nothing, TransformOrigin,
 	},
 	weights::{ConstantMultiplier, Weight, WeightToFee as _},
 	BoundedVec, PalletId,
@@ -81,8 +83,8 @@ use testnet_parachains_constants::westend::{
 };
 use xcm_config::{
 	ForeignAssetsConvertedConcreteId, LocationToAccountId, PoolAssetsConvertedConcreteId,
-	TrustBackedAssetsConvertedConcreteId, TrustBackedAssetsPalletLocation, WestendLocation,
-	XcmOriginToTransactDispatchOrigin,
+	PoolAssetsPalletLocation, TrustBackedAssetsConvertedConcreteId,
+	TrustBackedAssetsPalletLocation, WestendLocation, XcmOriginToTransactDispatchOrigin,
 };
 
 #[cfg(any(feature = "std", test))]
@@ -93,11 +95,15 @@ use assets_common::{
 	matching::{FromNetwork, FromSiblingParachain},
 };
 use polkadot_runtime_common::{BlockHashCount, SlowAdjustingFeeUpdate};
+use weights::{BlockExecutionWeight, ExtrinsicBaseWeight, RocksDbWeight};
 use xcm::{
 	latest::prelude::AssetId,
 	prelude::{VersionedAsset, VersionedAssetId, VersionedAssets, VersionedLocation, VersionedXcm},
 };
 
+#[cfg(feature = "runtime-benchmarks")]
+use frame_support::traits::PalletInfoAccess;
+
 #[cfg(feature = "runtime-benchmarks")]
 use xcm::latest::prelude::{
 	Asset, Assets as XcmAssets, Fungible, Here, InteriorLocation, Junction, Junction::*, Location,
@@ -109,8 +115,6 @@ use xcm_runtime_apis::{
 	fees::Error as XcmPaymentApiError,
 };
 
-use weights::{BlockExecutionWeight, ExtrinsicBaseWeight, RocksDbWeight};
-
 impl_opaque_keys! {
 	pub struct SessionKeys {
 		pub aura: Aura,
@@ -125,7 +129,7 @@ pub const VERSION: RuntimeVersion = RuntimeVersion {
 	spec_name: alloc::borrow::Cow::Borrowed("westmint"),
 	impl_name: alloc::borrow::Cow::Borrowed("westmint"),
 	authoring_version: 1,
-	spec_version: 1_017_003,
+	spec_version: 1_017_004,
 	impl_version: 0,
 	apis: RUNTIME_API_VERSIONS,
 	transaction_version: 16,
@@ -218,8 +222,8 @@ impl pallet_balances::Config for Runtime {
 	type ReserveIdentifier = [u8; 8];
 	type RuntimeHoldReason = RuntimeHoldReason;
 	type RuntimeFreezeReason = RuntimeFreezeReason;
-	type FreezeIdentifier = ();
-	type MaxFreezes = ConstU32<0>;
+	type FreezeIdentifier = RuntimeFreezeReason;
+	type MaxFreezes = ConstU32<50>;
 	type DoneSlashHandler = ();
 }
 
@@ -341,8 +345,22 @@ pub type LocalAndForeignAssets = fungibles::UnionOf<
 	xcm::v5::Location,
 	AccountId,
 >;
+
+/// Union fungibles implementation for `AssetsFreezer` and `ForeignAssetsFreezer`.
+pub type LocalAndForeignAssetsFreezer = fungibles::UnionOf<
+	AssetsFreezer,
+	ForeignAssetsFreezer,
+	LocalFromLeft<
+		AssetIdForTrustBackedAssetsConvert<TrustBackedAssetsPalletLocation, xcm::v5::Location>,
+		AssetIdForTrustBackedAssets,
+		xcm::v5::Location,
+	>,
+	xcm::v5::Location,
+	AccountId,
+>;
+
 /// Union fungibles implementation for [`LocalAndForeignAssets`] and `Balances`.
-pub type NativeAndAssets = fungible::UnionOf<
+pub type NativeAndNonPoolAssets = fungible::UnionOf<
 	Balances,
 	LocalAndForeignAssets,
 	TargetFromLeft<WestendLocation, xcm::v5::Location>,
@@ -350,6 +368,45 @@ pub type NativeAndAssets = fungible::UnionOf<
 	AccountId,
 >;
 
+/// Union fungibles implementation for [`LocalAndForeignAssetsFreezer`] and [`Balances`].
+pub type NativeAndNonPoolAssetsFreezer = fungible::UnionOf<
+	Balances,
+	LocalAndForeignAssetsFreezer,
+	TargetFromLeft<WestendLocation, xcm::v5::Location>,
+	xcm::v5::Location,
+	AccountId,
+>;
+
+/// Union fungibles implementation for [`PoolAssets`] and [`NativeAndNonPoolAssets`].
+///
+/// NOTE: Should be kept updated to include ALL balances and assets in the runtime.
+pub type NativeAndAllAssets = fungibles::UnionOf<
+	PoolAssets,
+	NativeAndNonPoolAssets,
+	LocalFromLeft<
+		AssetIdForPoolAssetsConvert<PoolAssetsPalletLocation, xcm::v5::Location>,
+		AssetIdForPoolAssets,
+		xcm::v5::Location,
+	>,
+	xcm::v5::Location,
+	AccountId,
+>;
+
+/// Union fungibles implementation for [`PoolAssetsFreezer`] and [`NativeAndNonPoolAssetsFreezer`].
+///
+/// NOTE: Should be kept updated to include ALL balances and assets in the runtime.
+pub type NativeAndAllAssetsFreezer = fungibles::UnionOf<
+	PoolAssetsFreezer,
+	NativeAndNonPoolAssetsFreezer,
+	LocalFromLeft<
+		AssetIdForPoolAssetsConvert<PoolAssetsPalletLocation, xcm::v5::Location>,
+		AssetIdForPoolAssets,
+		xcm::v5::Location,
+	>,
+	xcm::v5::Location,
+	AccountId,
+>;
+
 pub type PoolIdToAccountId = pallet_asset_conversion::AccountIdConverter<
 	AssetConversionPalletId,
 	(xcm::v5::Location, xcm::v5::Location),
@@ -360,7 +417,7 @@ impl pallet_asset_conversion::Config for Runtime {
 	type Balance = Balance;
 	type HigherPrecisionBalance = sp_core::U256;
 	type AssetKind = xcm::v5::Location;
-	type Assets = NativeAndAssets;
+	type Assets = NativeAndNonPoolAssets;
 	type PoolId = (Self::AssetKind, Self::AssetKind);
 	type PoolLocator = pallet_asset_conversion::WithFirstAsset<
 		WestendLocation,
@@ -388,6 +445,55 @@ impl pallet_asset_conversion::Config for Runtime {
 	>;
 }
 
+#[cfg(feature = "runtime-benchmarks")]
+pub struct PalletAssetRewardsBenchmarkHelper;
+
+#[cfg(feature = "runtime-benchmarks")]
+impl pallet_asset_rewards::benchmarking::BenchmarkHelper<xcm::v5::Location>
+	for PalletAssetRewardsBenchmarkHelper
+{
+	fn staked_asset() -> Location {
+		Location::new(
+			0,
+			[PalletInstance(<Assets as PalletInfoAccess>::index() as u8), GeneralIndex(100)],
+		)
+	}
+	fn reward_asset() -> Location {
+		Location::new(
+			0,
+			[PalletInstance(<Assets as PalletInfoAccess>::index() as u8), GeneralIndex(101)],
+		)
+	}
+}
+
+parameter_types! {
+	pub const AssetRewardsPalletId: PalletId = PalletId(*b"py/astrd");
+	pub const RewardsPoolCreationHoldReason: RuntimeHoldReason =
+		RuntimeHoldReason::AssetRewards(pallet_asset_rewards::HoldReason::PoolCreation);
+	// 1 item, 135 bytes into the storage on pool creation.
+	pub const StakePoolCreationDeposit: Balance = deposit(1, 135);
+}
+
+impl pallet_asset_rewards::Config for Runtime {
+	type RuntimeEvent = RuntimeEvent;
+	type PalletId = AssetRewardsPalletId;
+	type Balance = Balance;
+	type Assets = NativeAndAllAssets;
+	type AssetsFreezer = NativeAndAllAssetsFreezer;
+	type AssetId = xcm::v5::Location;
+	type CreatePoolOrigin = EnsureSigned<AccountId>;
+	type RuntimeFreezeReason = RuntimeFreezeReason;
+	type Consideration = HoldConsideration<
+		AccountId,
+		Balances,
+		RewardsPoolCreationHoldReason,
+		ConstantStoragePrice<StakePoolCreationDeposit, Balance>,
+	>;
+	type WeightInfo = weights::pallet_asset_rewards::WeightInfo<Runtime>;
+	#[cfg(feature = "runtime-benchmarks")]
+	type BenchmarkHelper = PalletAssetRewardsBenchmarkHelper;
+}
+
 impl pallet_asset_conversion_ops::Config for Runtime {
 	type RuntimeEvent = RuntimeEvent;
 	type PriorAccountIdConverter = pallet_asset_conversion::AccountIdConverterNoSeed<
@@ -816,9 +922,9 @@ impl pallet_asset_conversion_tx_payment::Config for Runtime {
 	type AssetId = xcm::v5::Location;
 	type OnChargeAssetTransaction = SwapAssetAdapter<
 		WestendLocation,
-		NativeAndAssets,
+		NativeAndNonPoolAssets,
 		AssetConversion,
-		ResolveAssetTo<StakingPot, NativeAndAssets>,
+		ResolveAssetTo<StakingPot, NativeAndNonPoolAssets>,
 	>;
 	type WeightInfo = weights::pallet_asset_conversion_tx_payment::WeightInfo<Runtime>;
 	#[cfg(feature = "runtime-benchmarks")]
@@ -952,11 +1058,6 @@ parameter_types! {
 	pub CodeHashLockupDepositPercent: Perbill = Perbill::from_percent(30);
 }
 
-type EventRecord = frame_system::EventRecord<
-	<Runtime as frame_system::Config>::RuntimeEvent,
-	<Runtime as frame_system::Config>::Hash,
->;
-
 impl pallet_revive::Config for Runtime {
 	type Time = Timestamp;
 	type Currency = Balances;
@@ -976,7 +1077,6 @@ impl pallet_revive::Config for Runtime {
 	type InstantiateOrigin = EnsureSigned<Self::AccountId>;
 	type RuntimeHoldReason = RuntimeHoldReason;
 	type CodeHashLockupDepositPercent = CodeHashLockupDepositPercent;
-	type Debug = ();
 	type Xcm = pallet_xcm::Pallet<Self>;
 	type ChainId = ConstU64<420_420_421>;
 	type NativeToEthRatio = ConstU32<1_000_000>; // 10^(18 - 12) Eth is 10^18, Native is 10^12.
@@ -1040,11 +1140,14 @@ construct_runtime!(
 		NftFractionalization: pallet_nft_fractionalization = 54,
 		PoolAssets: pallet_assets::<Instance3> = 55,
 		AssetConversion: pallet_asset_conversion = 56,
+
 		AssetsFreezer: pallet_assets_freezer::<Instance1> = 57,
 		ForeignAssetsFreezer: pallet_assets_freezer::<Instance2> = 58,
 		PoolAssetsFreezer: pallet_assets_freezer::<Instance3> = 59,
 		Revive: pallet_revive = 60,
 
+		AssetRewards: pallet_asset_rewards = 61,
+
 		StateTrieMigration: pallet_state_trie_migration = 70,
 
 		// TODO: the pallet instance should be removed once all pools have migrated
@@ -1322,6 +1425,7 @@ mod benches {
 		[pallet_assets, Foreign]
 		[pallet_assets, Pool]
 		[pallet_asset_conversion, AssetConversion]
+		[pallet_asset_rewards, AssetRewards]
 		[pallet_asset_conversion_tx_payment, AssetTxPayment]
 		[pallet_balances, Balances]
 		[pallet_message_queue, MessageQueue]
@@ -1679,6 +1783,12 @@ impl_runtime_apis! {
 		}
 	}
 
+	impl pallet_asset_rewards::AssetRewards<Block, Balance> for Runtime {
+		fn pool_creation_cost() -> Balance {
+			StakePoolCreationDeposit::get()
+		}
+	}
+
 	impl cumulus_primitives_core::GetCoreSelectorApi<Block> for Runtime {
 		fn core_selector() -> (CoreSelector, ClaimQueueOffset) {
 			ParachainSystem::core_selector()
@@ -2073,7 +2183,7 @@ impl_runtime_apis! {
 		}
 	}
 
-	impl pallet_revive::ReviveApi<Block, AccountId, Balance, Nonce, BlockNumber, EventRecord> for Runtime
+	impl pallet_revive::ReviveApi<Block, AccountId, Balance, Nonce, BlockNumber> for Runtime
 	{
 		fn balance(address: H160) -> U256 {
 			Revive::evm_balance(&address)
@@ -2108,7 +2218,7 @@ impl_runtime_apis! {
 			gas_limit: Option<Weight>,
 			storage_deposit_limit: Option<Balance>,
 			input_data: Vec<u8>,
-		) -> pallet_revive::ContractResult<pallet_revive::ExecReturnValue, Balance, EventRecord> {
+		) -> pallet_revive::ContractResult<pallet_revive::ExecReturnValue, Balance> {
 			let blockweights= <Runtime as frame_system::Config>::BlockWeights::get();
 			Revive::bare_call(
 				RuntimeOrigin::signed(origin),
@@ -2117,8 +2227,6 @@ impl_runtime_apis! {
 				gas_limit.unwrap_or(blockweights.max_block),
 				pallet_revive::DepositLimit::Balance(storage_deposit_limit.unwrap_or(u128::MAX)),
 				input_data,
-				pallet_revive::DebugInfo::UnsafeDebug,
-				pallet_revive::CollectEvents::UnsafeCollect,
 			)
 		}
 
@@ -2130,7 +2238,7 @@ impl_runtime_apis! {
 			code: pallet_revive::Code,
 			data: Vec<u8>,
 			salt: Option<[u8; 32]>,
-		) -> pallet_revive::ContractResult<pallet_revive::InstantiateReturnValue, Balance, EventRecord>
+		) -> pallet_revive::ContractResult<pallet_revive::InstantiateReturnValue, Balance>
 		{
 			let blockweights= <Runtime as frame_system::Config>::BlockWeights::get();
 			Revive::bare_instantiate(
@@ -2141,8 +2249,6 @@ impl_runtime_apis! {
 				code,
 				data,
 				salt,
-				pallet_revive::DebugInfo::UnsafeDebug,
-				pallet_revive::CollectEvents::UnsafeCollect,
 			)
 		}
 
diff --git a/cumulus/parachains/runtimes/assets/asset-hub-westend/src/weights/mod.rs b/cumulus/parachains/runtimes/assets/asset-hub-westend/src/weights/mod.rs
index 442b58635f48ad5ae910cc3612d45cab512e4a51..d653838ad80e61d9dd96a705a1871fe4c4858fa8 100644
--- a/cumulus/parachains/runtimes/assets/asset-hub-westend/src/weights/mod.rs
+++ b/cumulus/parachains/runtimes/assets/asset-hub-westend/src/weights/mod.rs
@@ -23,6 +23,7 @@ pub mod frame_system_extensions;
 pub mod pallet_asset_conversion;
 pub mod pallet_asset_conversion_ops;
 pub mod pallet_asset_conversion_tx_payment;
+pub mod pallet_asset_rewards;
 pub mod pallet_assets_foreign;
 pub mod pallet_assets_local;
 pub mod pallet_assets_pool;
diff --git a/cumulus/parachains/runtimes/assets/asset-hub-westend/src/weights/pallet_asset_rewards.rs b/cumulus/parachains/runtimes/assets/asset-hub-westend/src/weights/pallet_asset_rewards.rs
new file mode 100644
index 0000000000000000000000000000000000000000..3bbc289fec7b61433041c8007cc1d579a74ea7e5
--- /dev/null
+++ b/cumulus/parachains/runtimes/assets/asset-hub-westend/src/weights/pallet_asset_rewards.rs
@@ -0,0 +1,217 @@
+// Copyright (C) Parity Technologies (UK) Ltd.
+// This file is part of Cumulus.
+
+// Cumulus is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+
+// Cumulus is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+
+// You should have received a copy of the GNU General Public License
+// along with Cumulus.  If not, see <http://www.gnu.org/licenses/>.
+
+//! Autogenerated weights for `pallet_asset_rewards`
+//!
+//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 32.0.0
+//! DATE: 2025-01-14, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]`
+//! WORST CASE MAP SIZE: `1000000`
+//! HOSTNAME: `runner-ys-ssygq-project-674-concurrent-0`, CPU: `Intel(R) Xeon(R) CPU @ 2.60GHz`
+//! WASM-EXECUTION: `Compiled`, CHAIN: `Some("asset-hub-westend-dev")`, DB CACHE: 1024
+
+// Executed Command:
+// target/production/polkadot-parachain
+// benchmark
+// pallet
+// --steps=50
+// --repeat=20
+// --extrinsic=*
+// --wasm-execution=compiled
+// --heap-pages=4096
+// --json-file=/builds/parity/mirrors/polkadot-sdk/.git/.artifacts/bench.json
+// --pallet=pallet_asset_rewards
+// --chain=asset-hub-westend-dev
+// --header=./cumulus/file_header.txt
+// --output=./cumulus/parachains/runtimes/assets/asset-hub-westend/src/weights/
+
+#![cfg_attr(rustfmt, rustfmt_skip)]
+#![allow(unused_parens)]
+#![allow(unused_imports)]
+#![allow(missing_docs)]
+
+use frame_support::{traits::Get, weights::Weight};
+use core::marker::PhantomData;
+
+/// Weight functions for `pallet_asset_rewards`.
+pub struct WeightInfo<T>(PhantomData<T>);
+impl<T: frame_system::Config> pallet_asset_rewards::WeightInfo for WeightInfo<T> {
+	/// Storage: `Assets::Asset` (r:2 w:0)
+	/// Proof: `Assets::Asset` (`max_values`: None, `max_size`: Some(210), added: 2685, mode: `MaxEncodedLen`)
+	/// Storage: `AssetRewards::NextPoolId` (r:1 w:1)
+	/// Proof: `AssetRewards::NextPoolId` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`)
+	/// Storage: `System::Account` (r:1 w:1)
+	/// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`)
+	/// Storage: `Balances::Holds` (r:1 w:1)
+	/// Proof: `Balances::Holds` (`max_values`: None, `max_size`: Some(157), added: 2632, mode: `MaxEncodedLen`)
+	/// Storage: `AssetRewards::PoolCost` (r:0 w:1)
+	/// Proof: `AssetRewards::PoolCost` (`max_values`: None, `max_size`: Some(68), added: 2543, mode: `MaxEncodedLen`)
+	/// Storage: `AssetRewards::Pools` (r:0 w:1)
+	/// Proof: `AssetRewards::Pools` (`max_values`: None, `max_size`: Some(1344), added: 3819, mode: `MaxEncodedLen`)
+	fn create_pool() -> Weight {
+		// Proof Size summary in bytes:
+		//  Measured:  `392`
+		//  Estimated: `6360`
+		// Minimum execution time: 60_734_000 picoseconds.
+		Weight::from_parts(61_828_000, 0)
+			.saturating_add(Weight::from_parts(0, 6360))
+			.saturating_add(T::DbWeight::get().reads(5))
+			.saturating_add(T::DbWeight::get().writes(5))
+	}
+	/// Storage: `AssetRewards::Pools` (r:1 w:1)
+	/// Proof: `AssetRewards::Pools` (`max_values`: None, `max_size`: Some(1344), added: 3819, mode: `MaxEncodedLen`)
+	/// Storage: `AssetRewards::PoolStakers` (r:1 w:1)
+	/// Proof: `AssetRewards::PoolStakers` (`max_values`: None, `max_size`: Some(116), added: 2591, mode: `MaxEncodedLen`)
+	/// Storage: `AssetsFreezer::Freezes` (r:1 w:1)
+	/// Proof: `AssetsFreezer::Freezes` (`max_values`: None, `max_size`: Some(87), added: 2562, mode: `MaxEncodedLen`)
+	/// Storage: `Assets::Account` (r:1 w:0)
+	/// Proof: `Assets::Account` (`max_values`: None, `max_size`: Some(134), added: 2609, mode: `MaxEncodedLen`)
+	/// Storage: `AssetsFreezer::FrozenBalances` (r:1 w:1)
+	/// Proof: `AssetsFreezer::FrozenBalances` (`max_values`: None, `max_size`: Some(84), added: 2559, mode: `MaxEncodedLen`)
+	fn stake() -> Weight {
+		// Proof Size summary in bytes:
+		//  Measured:  `906`
+		//  Estimated: `4809`
+		// Minimum execution time: 56_014_000 picoseconds.
+		Weight::from_parts(58_487_000, 0)
+			.saturating_add(Weight::from_parts(0, 4809))
+			.saturating_add(T::DbWeight::get().reads(5))
+			.saturating_add(T::DbWeight::get().writes(4))
+	}
+	/// Storage: `AssetRewards::Pools` (r:1 w:1)
+	/// Proof: `AssetRewards::Pools` (`max_values`: None, `max_size`: Some(1344), added: 3819, mode: `MaxEncodedLen`)
+	/// Storage: `AssetRewards::PoolStakers` (r:1 w:1)
+	/// Proof: `AssetRewards::PoolStakers` (`max_values`: None, `max_size`: Some(116), added: 2591, mode: `MaxEncodedLen`)
+	/// Storage: `AssetsFreezer::Freezes` (r:1 w:1)
+	/// Proof: `AssetsFreezer::Freezes` (`max_values`: None, `max_size`: Some(87), added: 2562, mode: `MaxEncodedLen`)
+	/// Storage: `Assets::Account` (r:1 w:0)
+	/// Proof: `Assets::Account` (`max_values`: None, `max_size`: Some(134), added: 2609, mode: `MaxEncodedLen`)
+	/// Storage: `AssetsFreezer::FrozenBalances` (r:1 w:1)
+	/// Proof: `AssetsFreezer::FrozenBalances` (`max_values`: None, `max_size`: Some(84), added: 2559, mode: `MaxEncodedLen`)
+	fn unstake() -> Weight {
+		// Proof Size summary in bytes:
+		//  Measured:  `906`
+		//  Estimated: `4809`
+		// Minimum execution time: 59_071_000 picoseconds.
+		Weight::from_parts(60_631_000, 0)
+			.saturating_add(Weight::from_parts(0, 4809))
+			.saturating_add(T::DbWeight::get().reads(5))
+			.saturating_add(T::DbWeight::get().writes(4))
+	}
+	/// Storage: `AssetRewards::Pools` (r:1 w:0)
+	/// Proof: `AssetRewards::Pools` (`max_values`: None, `max_size`: Some(1344), added: 3819, mode: `MaxEncodedLen`)
+	/// Storage: `AssetRewards::PoolStakers` (r:1 w:1)
+	/// Proof: `AssetRewards::PoolStakers` (`max_values`: None, `max_size`: Some(116), added: 2591, mode: `MaxEncodedLen`)
+	/// Storage: `Assets::Asset` (r:1 w:1)
+	/// Proof: `Assets::Asset` (`max_values`: None, `max_size`: Some(210), added: 2685, mode: `MaxEncodedLen`)
+	/// Storage: `Assets::Account` (r:2 w:2)
+	/// Proof: `Assets::Account` (`max_values`: None, `max_size`: Some(134), added: 2609, mode: `MaxEncodedLen`)
+	/// Storage: `AssetsFreezer::FrozenBalances` (r:1 w:0)
+	/// Proof: `AssetsFreezer::FrozenBalances` (`max_values`: None, `max_size`: Some(84), added: 2559, mode: `MaxEncodedLen`)
+	fn harvest_rewards() -> Weight {
+		// Proof Size summary in bytes:
+		//  Measured:  `1106`
+		//  Estimated: `6208`
+		// Minimum execution time: 80_585_000 picoseconds.
+		Weight::from_parts(82_186_000, 0)
+			.saturating_add(Weight::from_parts(0, 6208))
+			.saturating_add(T::DbWeight::get().reads(6))
+			.saturating_add(T::DbWeight::get().writes(4))
+	}
+	/// Storage: `AssetRewards::Pools` (r:1 w:1)
+	/// Proof: `AssetRewards::Pools` (`max_values`: None, `max_size`: Some(1344), added: 3819, mode: `MaxEncodedLen`)
+	fn set_pool_reward_rate_per_block() -> Weight {
+		// Proof Size summary in bytes:
+		//  Measured:  `318`
+		//  Estimated: `4809`
+		// Minimum execution time: 17_083_000 picoseconds.
+		Weight::from_parts(17_816_000, 0)
+			.saturating_add(Weight::from_parts(0, 4809))
+			.saturating_add(T::DbWeight::get().reads(1))
+			.saturating_add(T::DbWeight::get().writes(1))
+	}
+	/// Storage: `AssetRewards::Pools` (r:1 w:1)
+	/// Proof: `AssetRewards::Pools` (`max_values`: None, `max_size`: Some(1344), added: 3819, mode: `MaxEncodedLen`)
+	fn set_pool_admin() -> Weight {
+		// Proof Size summary in bytes:
+		//  Measured:  `318`
+		//  Estimated: `4809`
+		// Minimum execution time: 15_269_000 picoseconds.
+		Weight::from_parts(15_881_000, 0)
+			.saturating_add(Weight::from_parts(0, 4809))
+			.saturating_add(T::DbWeight::get().reads(1))
+			.saturating_add(T::DbWeight::get().writes(1))
+	}
+	/// Storage: `AssetRewards::Pools` (r:1 w:1)
+	/// Proof: `AssetRewards::Pools` (`max_values`: None, `max_size`: Some(1344), added: 3819, mode: `MaxEncodedLen`)
+	fn set_pool_expiry_block() -> Weight {
+		// Proof Size summary in bytes:
+		//  Measured:  `318`
+		//  Estimated: `4809`
+		// Minimum execution time: 17_482_000 picoseconds.
+		Weight::from_parts(18_124_000, 0)
+			.saturating_add(Weight::from_parts(0, 4809))
+			.saturating_add(T::DbWeight::get().reads(1))
+			.saturating_add(T::DbWeight::get().writes(1))
+	}
+	/// Storage: `AssetRewards::Pools` (r:1 w:0)
+	/// Proof: `AssetRewards::Pools` (`max_values`: None, `max_size`: Some(1344), added: 3819, mode: `MaxEncodedLen`)
+	/// Storage: `Assets::Asset` (r:1 w:1)
+	/// Proof: `Assets::Asset` (`max_values`: None, `max_size`: Some(210), added: 2685, mode: `MaxEncodedLen`)
+	/// Storage: `Assets::Account` (r:2 w:2)
+	/// Proof: `Assets::Account` (`max_values`: None, `max_size`: Some(134), added: 2609, mode: `MaxEncodedLen`)
+	/// Storage: `AssetsFreezer::FrozenBalances` (r:1 w:0)
+	/// Proof: `AssetsFreezer::FrozenBalances` (`max_values`: None, `max_size`: Some(84), added: 2559, mode: `MaxEncodedLen`)
+	/// Storage: `System::Account` (r:1 w:1)
+	/// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`)
+	fn deposit_reward_tokens() -> Weight {
+		// Proof Size summary in bytes:
+		//  Measured:  `781`
+		//  Estimated: `6208`
+		// Minimum execution time: 66_644_000 picoseconds.
+		Weight::from_parts(67_950_000, 0)
+			.saturating_add(Weight::from_parts(0, 6208))
+			.saturating_add(T::DbWeight::get().reads(6))
+			.saturating_add(T::DbWeight::get().writes(4))
+	}
+	/// Storage: `AssetRewards::Pools` (r:1 w:1)
+	/// Proof: `AssetRewards::Pools` (`max_values`: None, `max_size`: Some(1344), added: 3819, mode: `MaxEncodedLen`)
+	/// Storage: `AssetRewards::PoolStakers` (r:1 w:0)
+	/// Proof: `AssetRewards::PoolStakers` (`max_values`: None, `max_size`: Some(116), added: 2591, mode: `MaxEncodedLen`)
+	/// Storage: `Assets::Asset` (r:1 w:1)
+	/// Proof: `Assets::Asset` (`max_values`: None, `max_size`: Some(210), added: 2685, mode: `MaxEncodedLen`)
+	/// Storage: `Assets::Account` (r:2 w:2)
+	/// Proof: `Assets::Account` (`max_values`: None, `max_size`: Some(134), added: 2609, mode: `MaxEncodedLen`)
+	/// Storage: `AssetsFreezer::FrozenBalances` (r:1 w:1)
+	/// Proof: `AssetsFreezer::FrozenBalances` (`max_values`: None, `max_size`: Some(84), added: 2559, mode: `MaxEncodedLen`)
+	/// Storage: `System::Account` (r:2 w:2)
+	/// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`)
+	/// Storage: `AssetRewards::PoolCost` (r:1 w:1)
+	/// Proof: `AssetRewards::PoolCost` (`max_values`: None, `max_size`: Some(68), added: 2543, mode: `MaxEncodedLen`)
+	/// Storage: `Balances::Holds` (r:1 w:1)
+	/// Proof: `Balances::Holds` (`max_values`: None, `max_size`: Some(157), added: 2632, mode: `MaxEncodedLen`)
+	/// Storage: `AssetsFreezer::Freezes` (r:0 w:1)
+	/// Proof: `AssetsFreezer::Freezes` (`max_values`: None, `max_size`: Some(87), added: 2562, mode: `MaxEncodedLen`)
+	fn cleanup_pool() -> Weight {
+		// Proof Size summary in bytes:
+		//  Measured:  `1139`
+		//  Estimated: `6208`
+		// Minimum execution time: 124_136_000 picoseconds.
+		Weight::from_parts(128_642_000, 0)
+			.saturating_add(Weight::from_parts(0, 6208))
+			.saturating_add(T::DbWeight::get().reads(10))
+			.saturating_add(T::DbWeight::get().writes(10))
+	}
+}
diff --git a/cumulus/parachains/runtimes/assets/asset-hub-westend/src/xcm_config.rs b/cumulus/parachains/runtimes/assets/asset-hub-westend/src/xcm_config.rs
index b4e938f1f8b5709515a855302d1e26b03afb968e..1ea2ce5136abd09348e8843b48b7a356b00c57f0 100644
--- a/cumulus/parachains/runtimes/assets/asset-hub-westend/src/xcm_config.rs
+++ b/cumulus/parachains/runtimes/assets/asset-hub-westend/src/xcm_config.rs
@@ -65,6 +65,7 @@ use xcm_executor::XcmExecutor;
 parameter_types! {
 	pub const RootLocation: Location = Location::here();
 	pub const WestendLocation: Location = Location::parent();
+	pub const GovernanceLocation: Location = Location::parent();
 	pub const RelayNetwork: Option<NetworkId> = Some(NetworkId::ByGenesis(WESTEND_GENESIS_HASH));
 	pub RelayChainOrigin: RuntimeOrigin = cumulus_pallet_xcm::Origin::Relay.into();
 	pub UniversalLocation: InteriorLocation =
@@ -359,7 +360,7 @@ pub type TrustedTeleporters = (
 /// asset and the asset required for fee payment.
 pub type PoolAssetsExchanger = SingleAssetExchangeAdapter<
 	crate::AssetConversion,
-	crate::NativeAndAssets,
+	crate::NativeAndNonPoolAssets,
 	(
 		TrustBackedAssetsAsLocation<TrustBackedAssetsPalletLocation, Balance, xcm::v5::Location>,
 		ForeignAssetsConvertedConcreteId,
@@ -409,7 +410,7 @@ impl xcm_executor::Config for XcmConfig {
 			WestendLocation,
 			crate::AssetConversion,
 			WeightToFee,
-			crate::NativeAndAssets,
+			crate::NativeAndNonPoolAssets,
 			(
 				TrustBackedAssetsAsLocation<
 					TrustBackedAssetsPalletLocation,
@@ -418,7 +419,7 @@ impl xcm_executor::Config for XcmConfig {
 				>,
 				ForeignAssetsConvertedConcreteId,
 			),
-			ResolveAssetTo<StakingPot, crate::NativeAndAssets>,
+			ResolveAssetTo<StakingPot, crate::NativeAndNonPoolAssets>,
 			AccountId,
 		>,
 		// This trader allows to pay with `is_sufficient=true` "Trust Backed" assets from dedicated
diff --git a/cumulus/parachains/runtimes/assets/common/src/lib.rs b/cumulus/parachains/runtimes/assets/common/src/lib.rs
index 25c2df6b68d16c9eb282b564e8b7a4acf23f9e3a..50b1b63146bc863945aa9602797db4478d08bd53 100644
--- a/cumulus/parachains/runtimes/assets/common/src/lib.rs
+++ b/cumulus/parachains/runtimes/assets/common/src/lib.rs
@@ -123,10 +123,11 @@ pub type ForeignAssetsConvertedConcreteId<
 	BalanceConverter,
 >;
 
-type AssetIdForPoolAssets = u32;
+pub type AssetIdForPoolAssets = u32;
+
 /// `Location` vs `AssetIdForPoolAssets` converter for `PoolAssets`.
-pub type AssetIdForPoolAssetsConvert<PoolAssetsPalletLocation> =
-	AsPrefixedGeneralIndex<PoolAssetsPalletLocation, AssetIdForPoolAssets, TryConvertInto>;
+pub type AssetIdForPoolAssetsConvert<PoolAssetsPalletLocation, L = Location> =
+	AsPrefixedGeneralIndex<PoolAssetsPalletLocation, AssetIdForPoolAssets, TryConvertInto, L>;
 /// [`MatchedConvertedConcreteId`] converter dedicated for `PoolAssets`
 pub type PoolAssetsConvertedConcreteId<PoolAssetsPalletLocation, Balance> =
 	MatchedConvertedConcreteId<
diff --git a/docs/sdk/packages/guides/first-pallet/Cargo.toml b/docs/sdk/packages/guides/first-pallet/Cargo.toml
index a1411580119da456e389715c0f47bc2df8691c8f..e6325c31781a6571055983607879f0c920a13f0f 100644
--- a/docs/sdk/packages/guides/first-pallet/Cargo.toml
+++ b/docs/sdk/packages/guides/first-pallet/Cargo.toml
@@ -18,7 +18,7 @@ targets = ["x86_64-unknown-linux-gnu"]
 [dependencies]
 codec = { workspace = true }
 docify = { workspace = true }
-frame = { workspace = true, features = ["experimental", "runtime"] }
+frame = { workspace = true, features = ["runtime"] }
 scale-info = { workspace = true }
 
 [features]
diff --git a/docs/sdk/packages/guides/first-runtime/Cargo.toml b/docs/sdk/packages/guides/first-runtime/Cargo.toml
index 303d5c5e7f5fc8a11c52d48d0105f4872e77bceb..8ed17dea1b71ec90295a2a75d6fa064e289ed6ca 100644
--- a/docs/sdk/packages/guides/first-runtime/Cargo.toml
+++ b/docs/sdk/packages/guides/first-runtime/Cargo.toml
@@ -18,7 +18,7 @@ scale-info = { workspace = true }
 serde_json = { workspace = true }
 
 # this is a frame-based runtime, thus importing `frame` with runtime feature enabled.
-frame = { workspace = true, features = ["experimental", "runtime"] }
+frame = { workspace = true, features = ["runtime"] }
 
 # pallets that we want to use
 pallet-balances = { workspace = true }
diff --git a/polkadot/runtime/rococo/src/xcm_config.rs b/polkadot/runtime/rococo/src/xcm_config.rs
index bb77ec0000e56888f2bb5068a4dfba919bc527e0..10c3f6c0cbfcf2a25efc02524e46f6c4d256674d 100644
--- a/polkadot/runtime/rococo/src/xcm_config.rs
+++ b/polkadot/runtime/rococo/src/xcm_config.rs
@@ -18,7 +18,8 @@
 
 use super::{
 	parachains_origin, AccountId, AllPalletsWithSystem, Balances, Dmp, Fellows, ParaId, Runtime,
-	RuntimeCall, RuntimeEvent, RuntimeOrigin, TransactionByteFee, Treasury, WeightToFee, XcmPallet,
+	RuntimeCall, RuntimeEvent, RuntimeOrigin, TransactionByteFee, Treasurer, Treasury, WeightToFee,
+	XcmPallet,
 };
 
 use crate::governance::StakingAdmin;
@@ -228,11 +229,14 @@ impl xcm_executor::Config for XcmConfig {
 }
 
 parameter_types! {
+	/// Collective pluralistic body.
 	pub const CollectiveBodyId: BodyId = BodyId::Unit;
-	// StakingAdmin pluralistic body.
+	/// StakingAdmin pluralistic body.
 	pub const StakingAdminBodyId: BodyId = BodyId::Defense;
-	// Fellows pluralistic body.
+	/// Fellows pluralistic body.
 	pub const FellowsBodyId: BodyId = BodyId::Technical;
+	/// Treasury pluralistic body.
+	pub const TreasuryBodyId: BodyId = BodyId::Treasury;
 }
 
 /// Type to convert an `Origin` type value into a `Location` value which represents an interior
@@ -249,6 +253,9 @@ pub type StakingAdminToPlurality =
 /// Type to convert the Fellows origin to a Plurality `Location` value.
 pub type FellowsToPlurality = OriginToPluralityVoice<RuntimeOrigin, Fellows, FellowsBodyId>;
 
+/// Type to convert the Treasury origin to a Plurality `Location` value.
+pub type TreasurerToPlurality = OriginToPluralityVoice<RuntimeOrigin, Treasurer, TreasuryBodyId>;
+
 /// Type to convert a pallet `Origin` type value into a `Location` value which represents an
 /// interior location of this chain for a destination chain.
 pub type LocalPalletOriginToLocation = (
@@ -256,13 +263,18 @@ pub type LocalPalletOriginToLocation = (
 	StakingAdminToPlurality,
 	// Fellows origin to be used in XCM as a corresponding Plurality `Location` value.
 	FellowsToPlurality,
+	// Treasurer origin to be used in XCM as a corresponding Plurality `Location` value.
+	TreasurerToPlurality,
 );
 
 impl pallet_xcm::Config for Runtime {
 	type RuntimeEvent = RuntimeEvent;
 	// Note that this configuration of `SendXcmOrigin` is different from the one present in
 	// production.
-	type SendXcmOrigin = xcm_builder::EnsureXcmOrigin<RuntimeOrigin, LocalOriginToLocation>;
+	type SendXcmOrigin = xcm_builder::EnsureXcmOrigin<
+		RuntimeOrigin,
+		(LocalPalletOriginToLocation, LocalOriginToLocation),
+	>;
 	type XcmRouter = XcmRouter;
 	// Anyone can execute XCM messages locally.
 	type ExecuteXcmOrigin = xcm_builder::EnsureXcmOrigin<RuntimeOrigin, LocalOriginToLocation>;
diff --git a/polkadot/runtime/test-runtime/src/lib.rs b/polkadot/runtime/test-runtime/src/lib.rs
index 4f9ba8d8508cdecc904efefcde280e386fddc1d3..cdf6fa92da2f5d5f09c6f48b21e2e4f13035d4ef 100644
--- a/polkadot/runtime/test-runtime/src/lib.rs
+++ b/polkadot/runtime/test-runtime/src/lib.rs
@@ -366,11 +366,13 @@ impl onchain::Config for OnChainSeqPhragmen {
 const MAX_QUOTA_NOMINATIONS: u32 = 16;
 
 impl pallet_staking::Config for Runtime {
+	type OldCurrency = Balances;
 	type Currency = Balances;
 	type CurrencyBalance = Balance;
 	type UnixTime = Timestamp;
 	type CurrencyToVote = polkadot_runtime_common::CurrencyToVote;
 	type RewardRemainder = ();
+	type RuntimeHoldReason = RuntimeHoldReason;
 	type RuntimeEvent = RuntimeEvent;
 	type Slash = ();
 	type Reward = ();
diff --git a/polkadot/runtime/westend/src/lib.rs b/polkadot/runtime/westend/src/lib.rs
index 58d2bdcb7c7db76e8625aff189a18acbde44b60e..a9ba0778fe0eff7b4801df2b045f038752390113 100644
--- a/polkadot/runtime/westend/src/lib.rs
+++ b/polkadot/runtime/westend/src/lib.rs
@@ -728,8 +728,10 @@ parameter_types! {
 }
 
 impl pallet_staking::Config for Runtime {
+	type OldCurrency = Balances;
 	type Currency = Balances;
 	type CurrencyBalance = Balance;
+	type RuntimeHoldReason = RuntimeHoldReason;
 	type UnixTime = Timestamp;
 	type CurrencyToVote = CurrencyToVote;
 	type RewardRemainder = ();
@@ -1085,6 +1087,7 @@ pub enum ProxyType {
 	CancelProxy,
 	Auction,
 	NominationPools,
+	ParaRegistration,
 }
 impl Default for ProxyType {
 	fn default() -> Self {
@@ -1181,6 +1184,15 @@ impl InstanceFilter<RuntimeCall> for ProxyType {
 					RuntimeCall::Registrar(..) |
 					RuntimeCall::Slots(..)
 			),
+			ProxyType::ParaRegistration => matches!(
+				c,
+				RuntimeCall::Registrar(paras_registrar::Call::reserve { .. }) |
+					RuntimeCall::Registrar(paras_registrar::Call::register { .. }) |
+					RuntimeCall::Utility(pallet_utility::Call::batch { .. }) |
+					RuntimeCall::Utility(pallet_utility::Call::batch_all { .. }) |
+					RuntimeCall::Utility(pallet_utility::Call::force_batch { .. }) |
+					RuntimeCall::Proxy(pallet_proxy::Call::remove_proxy { .. })
+			),
 		}
 	}
 	fn is_superset(&self, o: &Self) -> bool {
diff --git a/polkadot/runtime/westend/src/tests.rs b/polkadot/runtime/westend/src/tests.rs
index fcdaf7ff2de6d3c95207291bd6b2f47043877860..65b81cc00f06975e7be235cb7d7700824b356853 100644
--- a/polkadot/runtime/westend/src/tests.rs
+++ b/polkadot/runtime/westend/src/tests.rs
@@ -155,25 +155,27 @@ mod remote_tests {
 
 		let transport: Transport = var("WS").unwrap_or("ws://127.0.0.1:9900".to_string()).into();
 		let maybe_state_snapshot: Option<SnapshotConfig> = var("SNAP").map(|s| s.into()).ok();
+		let online_config = OnlineConfig {
+			transport,
+			state_snapshot: maybe_state_snapshot.clone(),
+			child_trie: false,
+			pallets: vec![
+				"Staking".into(),
+				"System".into(),
+				"Balances".into(),
+				"NominationPools".into(),
+				"DelegatedStaking".into(),
+			],
+			..Default::default()
+		};
 		let mut ext = Builder::<Block>::default()
 			.mode(if let Some(state_snapshot) = maybe_state_snapshot {
 				Mode::OfflineOrElseOnline(
 					OfflineConfig { state_snapshot: state_snapshot.clone() },
-					OnlineConfig {
-						transport,
-						state_snapshot: Some(state_snapshot),
-						pallets: vec![
-							"staking".into(),
-							"system".into(),
-							"balances".into(),
-							"nomination-pools".into(),
-							"delegated-staking".into(),
-						],
-						..Default::default()
-					},
+					online_config,
 				)
 			} else {
-				Mode::Online(OnlineConfig { transport, ..Default::default() })
+				Mode::Online(online_config)
 			})
 			.build()
 			.await
@@ -241,6 +243,77 @@ mod remote_tests {
 			);
 		});
 	}
+
+	#[tokio::test]
+	async fn staking_curr_fun_migrate() {
+		// Intended to be run only manually.
+		if var("RUN_MIGRATION_TESTS").is_err() {
+			return;
+		}
+		sp_tracing::try_init_simple();
+
+		let transport: Transport = var("WS").unwrap_or("ws://127.0.0.1:9944".to_string()).into();
+		let maybe_state_snapshot: Option<SnapshotConfig> = var("SNAP").map(|s| s.into()).ok();
+		let online_config = OnlineConfig {
+			transport,
+			state_snapshot: maybe_state_snapshot.clone(),
+			child_trie: false,
+			pallets: vec!["Staking".into(), "System".into(), "Balances".into()],
+			..Default::default()
+		};
+		let mut ext = Builder::<Block>::default()
+			.mode(if let Some(state_snapshot) = maybe_state_snapshot {
+				Mode::OfflineOrElseOnline(
+					OfflineConfig { state_snapshot: state_snapshot.clone() },
+					online_config,
+				)
+			} else {
+				Mode::Online(online_config)
+			})
+			.build()
+			.await
+			.unwrap();
+		ext.execute_with(|| {
+			// create an account with some balance
+			let alice = AccountId::from([1u8; 32]);
+			use frame_support::traits::Currency;
+			let _ = Balances::deposit_creating(&alice, 100_000 * UNITS);
+
+			let mut success = 0;
+			let mut err = 0;
+			let mut force_withdraw_acc = 0;
+			// iterate over all pools
+			pallet_staking::Ledger::<Runtime>::iter().for_each(|(ctrl, ledger)| {
+				match pallet_staking::Pallet::<Runtime>::migrate_currency(
+					RuntimeOrigin::signed(alice.clone()).into(),
+					ledger.stash.clone(),
+				) {
+					Ok(_) => {
+						let updated_ledger =
+							pallet_staking::Ledger::<Runtime>::get(&ctrl).expect("ledger exists");
+						let force_withdraw = ledger.total - updated_ledger.total;
+						if force_withdraw > 0 {
+							force_withdraw_acc += force_withdraw;
+							log::info!(target: "remote_test", "Force withdraw from stash {:?}: value {:?}", ledger.stash, force_withdraw);
+						}
+						success += 1;
+					},
+					Err(e) => {
+						log::error!(target: "remote_test", "Error migrating {:?}: {:?}", ledger.stash, e);
+						err += 1;
+					},
+				}
+			});
+
+			log::info!(
+				target: "remote_test",
+				"Migration stats: success: {}, err: {}, total force withdrawn stake: {}",
+				success,
+				err,
+				force_withdraw_acc
+			);
+		});
+	}
 }
 
 #[test]
diff --git a/polkadot/runtime/westend/src/weights/pallet_staking.rs b/polkadot/runtime/westend/src/weights/pallet_staking.rs
index 393fa0b37176a0ca5ce8160eec7e8e3ac56c3458..f1e7f5ba1576eafbd768f51cf98faa41cbe02b5c 100644
--- a/polkadot/runtime/westend/src/weights/pallet_staking.rs
+++ b/polkadot/runtime/westend/src/weights/pallet_staking.rs
@@ -17,9 +17,9 @@
 //! Autogenerated weights for `pallet_staking`
 //!
 //! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 32.0.0
-//! DATE: 2024-03-27, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]`
+//! DATE: 2024-09-17, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]`
 //! WORST CASE MAP SIZE: `1000000`
-//! HOSTNAME: `runner-h2rr8wx7-project-674-concurrent-0`, CPU: `Intel(R) Xeon(R) CPU @ 2.60GHz`
+//! HOSTNAME: `runner-obbyq9g6-project-674-concurrent-0`, CPU: `Intel(R) Xeon(R) CPU @ 2.60GHz`
 //! WASM-EXECUTION: `Compiled`, CHAIN: `Some("westend-dev")`, DB CACHE: 1024
 
 // Executed Command:
@@ -52,19 +52,19 @@ impl<T: frame_system::Config> pallet_staking::WeightInfo for WeightInfo<T> {
 	/// Proof: `Staking::Bonded` (`max_values`: None, `max_size`: Some(72), added: 2547, mode: `MaxEncodedLen`)
 	/// Storage: `Staking::Ledger` (r:1 w:1)
 	/// Proof: `Staking::Ledger` (`max_values`: None, `max_size`: Some(1091), added: 3566, mode: `MaxEncodedLen`)
-	/// Storage: `Balances::Locks` (r:1 w:1)
-	/// Proof: `Balances::Locks` (`max_values`: None, `max_size`: Some(1299), added: 3774, mode: `MaxEncodedLen`)
-	/// Storage: `Balances::Freezes` (r:1 w:0)
-	/// Proof: `Balances::Freezes` (`max_values`: None, `max_size`: Some(67), added: 2542, mode: `MaxEncodedLen`)
+	/// Storage: `Balances::Holds` (r:1 w:1)
+	/// Proof: `Balances::Holds` (`max_values`: None, `max_size`: Some(103), added: 2578, mode: `MaxEncodedLen`)
+	/// Storage: `Staking::VirtualStakers` (r:1 w:0)
+	/// Proof: `Staking::VirtualStakers` (`max_values`: None, `max_size`: Some(40), added: 2515, mode: `MaxEncodedLen`)
 	/// Storage: `Staking::Payee` (r:0 w:1)
 	/// Proof: `Staking::Payee` (`max_values`: None, `max_size`: Some(73), added: 2548, mode: `MaxEncodedLen`)
 	fn bond() -> Weight {
 		// Proof Size summary in bytes:
-		//  Measured:  `1009`
-		//  Estimated: `4764`
-		// Minimum execution time: 40_585_000 picoseconds.
-		Weight::from_parts(41_800_000, 0)
-			.saturating_add(Weight::from_parts(0, 4764))
+		//  Measured:  `1035`
+		//  Estimated: `4556`
+		// Minimum execution time: 70_147_000 picoseconds.
+		Weight::from_parts(71_795_000, 0)
+			.saturating_add(Weight::from_parts(0, 4556))
 			.saturating_add(T::DbWeight::get().reads(4))
 			.saturating_add(T::DbWeight::get().writes(4))
 	}
@@ -72,20 +72,20 @@ impl<T: frame_system::Config> pallet_staking::WeightInfo for WeightInfo<T> {
 	/// Proof: `Staking::Bonded` (`max_values`: None, `max_size`: Some(72), added: 2547, mode: `MaxEncodedLen`)
 	/// Storage: `Staking::Ledger` (r:1 w:1)
 	/// Proof: `Staking::Ledger` (`max_values`: None, `max_size`: Some(1091), added: 3566, mode: `MaxEncodedLen`)
-	/// Storage: `Balances::Locks` (r:1 w:1)
-	/// Proof: `Balances::Locks` (`max_values`: None, `max_size`: Some(1299), added: 3774, mode: `MaxEncodedLen`)
-	/// Storage: `Balances::Freezes` (r:1 w:0)
-	/// Proof: `Balances::Freezes` (`max_values`: None, `max_size`: Some(67), added: 2542, mode: `MaxEncodedLen`)
+	/// Storage: `Staking::VirtualStakers` (r:1 w:0)
+	/// Proof: `Staking::VirtualStakers` (`max_values`: None, `max_size`: Some(40), added: 2515, mode: `MaxEncodedLen`)
+	/// Storage: `Balances::Holds` (r:1 w:1)
+	/// Proof: `Balances::Holds` (`max_values`: None, `max_size`: Some(103), added: 2578, mode: `MaxEncodedLen`)
 	/// Storage: `VoterList::ListNodes` (r:3 w:3)
 	/// Proof: `VoterList::ListNodes` (`max_values`: None, `max_size`: Some(154), added: 2629, mode: `MaxEncodedLen`)
 	/// Storage: `VoterList::ListBags` (r:2 w:2)
 	/// Proof: `VoterList::ListBags` (`max_values`: None, `max_size`: Some(82), added: 2557, mode: `MaxEncodedLen`)
 	fn bond_extra() -> Weight {
 		// Proof Size summary in bytes:
-		//  Measured:  `1921`
+		//  Measured:  `1947`
 		//  Estimated: `8877`
-		// Minimum execution time: 81_809_000 picoseconds.
-		Weight::from_parts(84_387_000, 0)
+		// Minimum execution time: 125_203_000 picoseconds.
+		Weight::from_parts(128_088_000, 0)
 			.saturating_add(Weight::from_parts(0, 8877))
 			.saturating_add(T::DbWeight::get().reads(9))
 			.saturating_add(T::DbWeight::get().writes(7))
@@ -100,23 +100,23 @@ impl<T: frame_system::Config> pallet_staking::WeightInfo for WeightInfo<T> {
 	/// Proof: `Staking::MinNominatorBond` (`max_values`: Some(1), `max_size`: Some(16), added: 511, mode: `MaxEncodedLen`)
 	/// Storage: `Staking::CurrentEra` (r:1 w:0)
 	/// Proof: `Staking::CurrentEra` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`)
-	/// Storage: `Balances::Locks` (r:1 w:1)
-	/// Proof: `Balances::Locks` (`max_values`: None, `max_size`: Some(1299), added: 3774, mode: `MaxEncodedLen`)
-	/// Storage: `Balances::Freezes` (r:1 w:0)
-	/// Proof: `Balances::Freezes` (`max_values`: None, `max_size`: Some(67), added: 2542, mode: `MaxEncodedLen`)
+	/// Storage: `Staking::VirtualStakers` (r:1 w:0)
+	/// Proof: `Staking::VirtualStakers` (`max_values`: None, `max_size`: Some(40), added: 2515, mode: `MaxEncodedLen`)
+	/// Storage: `Balances::Holds` (r:1 w:0)
+	/// Proof: `Balances::Holds` (`max_values`: None, `max_size`: Some(103), added: 2578, mode: `MaxEncodedLen`)
 	/// Storage: `VoterList::ListNodes` (r:3 w:3)
 	/// Proof: `VoterList::ListNodes` (`max_values`: None, `max_size`: Some(154), added: 2629, mode: `MaxEncodedLen`)
 	/// Storage: `VoterList::ListBags` (r:2 w:2)
 	/// Proof: `VoterList::ListBags` (`max_values`: None, `max_size`: Some(82), added: 2557, mode: `MaxEncodedLen`)
 	fn unbond() -> Weight {
 		// Proof Size summary in bytes:
-		//  Measured:  `2128`
+		//  Measured:  `2051`
 		//  Estimated: `8877`
-		// Minimum execution time: 89_419_000 picoseconds.
-		Weight::from_parts(91_237_000, 0)
+		// Minimum execution time: 101_991_000 picoseconds.
+		Weight::from_parts(104_567_000, 0)
 			.saturating_add(Weight::from_parts(0, 8877))
 			.saturating_add(T::DbWeight::get().reads(12))
-			.saturating_add(T::DbWeight::get().writes(7))
+			.saturating_add(T::DbWeight::get().writes(6))
 	}
 	/// Storage: `Staking::Ledger` (r:1 w:1)
 	/// Proof: `Staking::Ledger` (`max_values`: None, `max_size`: Some(1091), added: 3566, mode: `MaxEncodedLen`)
@@ -124,23 +124,25 @@ impl<T: frame_system::Config> pallet_staking::WeightInfo for WeightInfo<T> {
 	/// Proof: `Staking::Bonded` (`max_values`: None, `max_size`: Some(72), added: 2547, mode: `MaxEncodedLen`)
 	/// Storage: `Staking::CurrentEra` (r:1 w:0)
 	/// Proof: `Staking::CurrentEra` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`)
-	/// Storage: `Balances::Locks` (r:1 w:1)
-	/// Proof: `Balances::Locks` (`max_values`: None, `max_size`: Some(1299), added: 3774, mode: `MaxEncodedLen`)
-	/// Storage: `Balances::Freezes` (r:1 w:0)
-	/// Proof: `Balances::Freezes` (`max_values`: None, `max_size`: Some(67), added: 2542, mode: `MaxEncodedLen`)
+	/// Storage: `Staking::VirtualStakers` (r:1 w:0)
+	/// Proof: `Staking::VirtualStakers` (`max_values`: None, `max_size`: Some(40), added: 2515, mode: `MaxEncodedLen`)
+	/// Storage: `Balances::Holds` (r:1 w:1)
+	/// Proof: `Balances::Holds` (`max_values`: None, `max_size`: Some(103), added: 2578, mode: `MaxEncodedLen`)
 	/// Storage: `NominationPools::ReversePoolIdLookup` (r:1 w:0)
 	/// Proof: `NominationPools::ReversePoolIdLookup` (`max_values`: None, `max_size`: Some(44), added: 2519, mode: `MaxEncodedLen`)
+	/// Storage: `DelegatedStaking::Agents` (r:1 w:0)
+	/// Proof: `DelegatedStaking::Agents` (`max_values`: None, `max_size`: Some(120), added: 2595, mode: `MaxEncodedLen`)
 	/// The range of component `s` is `[0, 100]`.
 	fn withdraw_unbonded_update(s: u32, ) -> Weight {
 		// Proof Size summary in bytes:
-		//  Measured:  `1223`
-		//  Estimated: `4764`
-		// Minimum execution time: 45_152_000 picoseconds.
-		Weight::from_parts(46_460_819, 0)
-			.saturating_add(Weight::from_parts(0, 4764))
-			// Standard Error: 972
-			.saturating_add(Weight::from_parts(55_473, 0).saturating_mul(s.into()))
-			.saturating_add(T::DbWeight::get().reads(6))
+		//  Measured:  `1253`
+		//  Estimated: `4556`
+		// Minimum execution time: 76_450_000 picoseconds.
+		Weight::from_parts(78_836_594, 0)
+			.saturating_add(Weight::from_parts(0, 4556))
+			// Standard Error: 1_529
+			.saturating_add(Weight::from_parts(66_662, 0).saturating_mul(s.into()))
+			.saturating_add(T::DbWeight::get().reads(7))
 			.saturating_add(T::DbWeight::get().writes(2))
 	}
 	/// Storage: `Staking::Ledger` (r:1 w:1)
@@ -151,10 +153,10 @@ impl<T: frame_system::Config> pallet_staking::WeightInfo for WeightInfo<T> {
 	/// Proof: `Staking::CurrentEra` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`)
 	/// Storage: `Staking::SlashingSpans` (r:1 w:1)
 	/// Proof: `Staking::SlashingSpans` (`max_values`: None, `max_size`: None, mode: `Measured`)
-	/// Storage: `Balances::Locks` (r:1 w:1)
-	/// Proof: `Balances::Locks` (`max_values`: None, `max_size`: Some(1299), added: 3774, mode: `MaxEncodedLen`)
-	/// Storage: `Balances::Freezes` (r:1 w:0)
-	/// Proof: `Balances::Freezes` (`max_values`: None, `max_size`: Some(67), added: 2542, mode: `MaxEncodedLen`)
+	/// Storage: `Staking::VirtualStakers` (r:1 w:1)
+	/// Proof: `Staking::VirtualStakers` (`max_values`: None, `max_size`: Some(40), added: 2515, mode: `MaxEncodedLen`)
+	/// Storage: `Balances::Holds` (r:1 w:1)
+	/// Proof: `Balances::Holds` (`max_values`: None, `max_size`: Some(103), added: 2578, mode: `MaxEncodedLen`)
 	/// Storage: `Staking::Validators` (r:1 w:0)
 	/// Proof: `Staking::Validators` (`max_values`: None, `max_size`: Some(45), added: 2520, mode: `MaxEncodedLen`)
 	/// Storage: `Staking::Nominators` (r:1 w:1)
@@ -174,15 +176,15 @@ impl<T: frame_system::Config> pallet_staking::WeightInfo for WeightInfo<T> {
 	/// The range of component `s` is `[0, 100]`.
 	fn withdraw_unbonded_kill(s: u32, ) -> Weight {
 		// Proof Size summary in bytes:
-		//  Measured:  `2127 + s * (4 ±0)`
+		//  Measured:  `2153 + s * (4 ±0)`
 		//  Estimated: `6248 + s * (4 ±0)`
-		// Minimum execution time: 82_762_000 picoseconds.
-		Weight::from_parts(91_035_077, 0)
+		// Minimum execution time: 121_962_000 picoseconds.
+		Weight::from_parts(131_000_151, 0)
 			.saturating_add(Weight::from_parts(0, 6248))
-			// Standard Error: 3_771
-			.saturating_add(Weight::from_parts(1_217_871, 0).saturating_mul(s.into()))
+			// Standard Error: 3_846
+			.saturating_add(Weight::from_parts(1_277_843, 0).saturating_mul(s.into()))
 			.saturating_add(T::DbWeight::get().reads(13))
-			.saturating_add(T::DbWeight::get().writes(11))
+			.saturating_add(T::DbWeight::get().writes(12))
 			.saturating_add(T::DbWeight::get().writes((1_u64).saturating_mul(s.into())))
 			.saturating_add(Weight::from_parts(0, 4).saturating_mul(s.into()))
 	}
@@ -210,10 +212,10 @@ impl<T: frame_system::Config> pallet_staking::WeightInfo for WeightInfo<T> {
 	/// Proof: `Staking::CounterForValidators` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`)
 	fn validate() -> Weight {
 		// Proof Size summary in bytes:
-		//  Measured:  `1301`
+		//  Measured:  `1334`
 		//  Estimated: `4556`
-		// Minimum execution time: 50_555_000 picoseconds.
-		Weight::from_parts(52_052_000, 0)
+		// Minimum execution time: 66_450_000 picoseconds.
+		Weight::from_parts(68_302_000, 0)
 			.saturating_add(Weight::from_parts(0, 4556))
 			.saturating_add(T::DbWeight::get().reads(11))
 			.saturating_add(T::DbWeight::get().writes(5))
@@ -227,13 +229,13 @@ impl<T: frame_system::Config> pallet_staking::WeightInfo for WeightInfo<T> {
 	/// The range of component `k` is `[1, 128]`.
 	fn kick(k: u32, ) -> Weight {
 		// Proof Size summary in bytes:
-		//  Measured:  `1778 + k * (572 ±0)`
+		//  Measured:  `1811 + k * (572 ±0)`
 		//  Estimated: `4556 + k * (3033 ±0)`
-		// Minimum execution time: 35_037_000 picoseconds.
-		Weight::from_parts(35_081_878, 0)
+		// Minimum execution time: 43_875_000 picoseconds.
+		Weight::from_parts(47_332_240, 0)
 			.saturating_add(Weight::from_parts(0, 4556))
-			// Standard Error: 5_473
-			.saturating_add(Weight::from_parts(6_667_924, 0).saturating_mul(k.into()))
+			// Standard Error: 6_530
+			.saturating_add(Weight::from_parts(7_398_001, 0).saturating_mul(k.into()))
 			.saturating_add(T::DbWeight::get().reads(2))
 			.saturating_add(T::DbWeight::get().reads((1_u64).saturating_mul(k.into())))
 			.saturating_add(T::DbWeight::get().writes((1_u64).saturating_mul(k.into())))
@@ -264,13 +266,13 @@ impl<T: frame_system::Config> pallet_staking::WeightInfo for WeightInfo<T> {
 	/// The range of component `n` is `[1, 16]`.
 	fn nominate(n: u32, ) -> Weight {
 		// Proof Size summary in bytes:
-		//  Measured:  `1797 + n * (102 ±0)`
+		//  Measured:  `1830 + n * (102 ±0)`
 		//  Estimated: `6248 + n * (2520 ±0)`
-		// Minimum execution time: 62_098_000 picoseconds.
-		Weight::from_parts(60_154_061, 0)
+		// Minimum execution time: 80_640_000 picoseconds.
+		Weight::from_parts(78_801_092, 0)
 			.saturating_add(Weight::from_parts(0, 6248))
-			// Standard Error: 19_257
-			.saturating_add(Weight::from_parts(3_839_855, 0).saturating_mul(n.into()))
+			// Standard Error: 22_249
+			.saturating_add(Weight::from_parts(4_996_344, 0).saturating_mul(n.into()))
 			.saturating_add(T::DbWeight::get().reads(12))
 			.saturating_add(T::DbWeight::get().reads((1_u64).saturating_mul(n.into())))
 			.saturating_add(T::DbWeight::get().writes(6))
@@ -294,10 +296,10 @@ impl<T: frame_system::Config> pallet_staking::WeightInfo for WeightInfo<T> {
 	/// Proof: `VoterList::CounterForListNodes` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`)
 	fn chill() -> Weight {
 		// Proof Size summary in bytes:
-		//  Measured:  `1747`
+		//  Measured:  `1780`
 		//  Estimated: `6248`
-		// Minimum execution time: 54_993_000 picoseconds.
-		Weight::from_parts(56_698_000, 0)
+		// Minimum execution time: 71_494_000 picoseconds.
+		Weight::from_parts(73_487_000, 0)
 			.saturating_add(Weight::from_parts(0, 6248))
 			.saturating_add(T::DbWeight::get().reads(9))
 			.saturating_add(T::DbWeight::get().writes(6))
@@ -310,10 +312,10 @@ impl<T: frame_system::Config> pallet_staking::WeightInfo for WeightInfo<T> {
 	/// Proof: `Staking::Payee` (`max_values`: None, `max_size`: Some(73), added: 2548, mode: `MaxEncodedLen`)
 	fn set_payee() -> Weight {
 		// Proof Size summary in bytes:
-		//  Measured:  `865`
+		//  Measured:  `898`
 		//  Estimated: `4556`
-		// Minimum execution time: 18_100_000 picoseconds.
-		Weight::from_parts(18_547_000, 0)
+		// Minimum execution time: 24_310_000 picoseconds.
+		Weight::from_parts(24_676_000, 0)
 			.saturating_add(Weight::from_parts(0, 4556))
 			.saturating_add(T::DbWeight::get().reads(2))
 			.saturating_add(T::DbWeight::get().writes(1))
@@ -326,10 +328,10 @@ impl<T: frame_system::Config> pallet_staking::WeightInfo for WeightInfo<T> {
 	/// Proof: `Staking::Payee` (`max_values`: None, `max_size`: Some(73), added: 2548, mode: `MaxEncodedLen`)
 	fn update_payee() -> Weight {
 		// Proof Size summary in bytes:
-		//  Measured:  `932`
+		//  Measured:  `965`
 		//  Estimated: `4556`
-		// Minimum execution time: 23_428_000 picoseconds.
-		Weight::from_parts(24_080_000, 0)
+		// Minimum execution time: 31_348_000 picoseconds.
+		Weight::from_parts(32_384_000, 0)
 			.saturating_add(Weight::from_parts(0, 4556))
 			.saturating_add(T::DbWeight::get().reads(3))
 			.saturating_add(T::DbWeight::get().writes(1))
@@ -340,10 +342,10 @@ impl<T: frame_system::Config> pallet_staking::WeightInfo for WeightInfo<T> {
 	/// Proof: `Staking::Ledger` (`max_values`: None, `max_size`: Some(1091), added: 3566, mode: `MaxEncodedLen`)
 	fn set_controller() -> Weight {
 		// Proof Size summary in bytes:
-		//  Measured:  `865`
+		//  Measured:  `898`
 		//  Estimated: `8122`
-		// Minimum execution time: 21_159_000 picoseconds.
-		Weight::from_parts(21_706_000, 0)
+		// Minimum execution time: 27_537_000 picoseconds.
+		Weight::from_parts(28_714_000, 0)
 			.saturating_add(Weight::from_parts(0, 8122))
 			.saturating_add(T::DbWeight::get().reads(3))
 			.saturating_add(T::DbWeight::get().writes(3))
@@ -354,8 +356,8 @@ impl<T: frame_system::Config> pallet_staking::WeightInfo for WeightInfo<T> {
 		// Proof Size summary in bytes:
 		//  Measured:  `0`
 		//  Estimated: `0`
-		// Minimum execution time: 1_910_000 picoseconds.
-		Weight::from_parts(2_003_000, 0)
+		// Minimum execution time: 2_362_000 picoseconds.
+		Weight::from_parts(2_518_000, 0)
 			.saturating_add(Weight::from_parts(0, 0))
 			.saturating_add(T::DbWeight::get().writes(1))
 	}
@@ -365,8 +367,8 @@ impl<T: frame_system::Config> pallet_staking::WeightInfo for WeightInfo<T> {
 		// Proof Size summary in bytes:
 		//  Measured:  `0`
 		//  Estimated: `0`
-		// Minimum execution time: 7_076_000 picoseconds.
-		Weight::from_parts(7_349_000, 0)
+		// Minimum execution time: 7_752_000 picoseconds.
+		Weight::from_parts(8_105_000, 0)
 			.saturating_add(Weight::from_parts(0, 0))
 			.saturating_add(T::DbWeight::get().writes(1))
 	}
@@ -376,8 +378,8 @@ impl<T: frame_system::Config> pallet_staking::WeightInfo for WeightInfo<T> {
 		// Proof Size summary in bytes:
 		//  Measured:  `0`
 		//  Estimated: `0`
-		// Minimum execution time: 7_067_000 picoseconds.
-		Weight::from_parts(7_389_000, 0)
+		// Minimum execution time: 7_868_000 picoseconds.
+		Weight::from_parts(8_175_000, 0)
 			.saturating_add(Weight::from_parts(0, 0))
 			.saturating_add(T::DbWeight::get().writes(1))
 	}
@@ -387,8 +389,8 @@ impl<T: frame_system::Config> pallet_staking::WeightInfo for WeightInfo<T> {
 		// Proof Size summary in bytes:
 		//  Measured:  `0`
 		//  Estimated: `0`
-		// Minimum execution time: 7_148_000 picoseconds.
-		Weight::from_parts(7_446_000, 0)
+		// Minimum execution time: 7_945_000 picoseconds.
+		Weight::from_parts(8_203_000, 0)
 			.saturating_add(Weight::from_parts(0, 0))
 			.saturating_add(T::DbWeight::get().writes(1))
 	}
@@ -399,11 +401,11 @@ impl<T: frame_system::Config> pallet_staking::WeightInfo for WeightInfo<T> {
 		// Proof Size summary in bytes:
 		//  Measured:  `0`
 		//  Estimated: `0`
-		// Minimum execution time: 2_025_000 picoseconds.
-		Weight::from_parts(2_229_953, 0)
+		// Minimum execution time: 2_458_000 picoseconds.
+		Weight::from_parts(2_815_664, 0)
 			.saturating_add(Weight::from_parts(0, 0))
 			// Standard Error: 67
-			.saturating_add(Weight::from_parts(11_785, 0).saturating_mul(v.into()))
+			.saturating_add(Weight::from_parts(12_287, 0).saturating_mul(v.into()))
 			.saturating_add(T::DbWeight::get().writes(1))
 	}
 	/// Storage: `Staking::Ledger` (r:1502 w:1502)
@@ -415,13 +417,13 @@ impl<T: frame_system::Config> pallet_staking::WeightInfo for WeightInfo<T> {
 	/// The range of component `i` is `[0, 751]`.
 	fn deprecate_controller_batch(i: u32, ) -> Weight {
 		// Proof Size summary in bytes:
-		//  Measured:  `680 + i * (227 ±0)`
+		//  Measured:  `713 + i * (227 ±0)`
 		//  Estimated: `990 + i * (7132 ±0)`
-		// Minimum execution time: 4_321_000 picoseconds.
-		Weight::from_parts(4_407_000, 0)
+		// Minimum execution time: 4_976_000 picoseconds.
+		Weight::from_parts(5_102_000, 0)
 			.saturating_add(Weight::from_parts(0, 990))
-			// Standard Error: 37_239
-			.saturating_add(Weight::from_parts(21_300_598, 0).saturating_mul(i.into()))
+			// Standard Error: 36_458
+			.saturating_add(Weight::from_parts(25_359_275, 0).saturating_mul(i.into()))
 			.saturating_add(T::DbWeight::get().reads((4_u64).saturating_mul(i.into())))
 			.saturating_add(T::DbWeight::get().writes((3_u64).saturating_mul(i.into())))
 			.saturating_add(Weight::from_parts(0, 7132).saturating_mul(i.into()))
@@ -432,10 +434,10 @@ impl<T: frame_system::Config> pallet_staking::WeightInfo for WeightInfo<T> {
 	/// Proof: `Staking::Bonded` (`max_values`: None, `max_size`: Some(72), added: 2547, mode: `MaxEncodedLen`)
 	/// Storage: `Staking::Ledger` (r:1 w:1)
 	/// Proof: `Staking::Ledger` (`max_values`: None, `max_size`: Some(1091), added: 3566, mode: `MaxEncodedLen`)
-	/// Storage: `Balances::Locks` (r:1 w:1)
-	/// Proof: `Balances::Locks` (`max_values`: None, `max_size`: Some(1299), added: 3774, mode: `MaxEncodedLen`)
-	/// Storage: `Balances::Freezes` (r:1 w:0)
-	/// Proof: `Balances::Freezes` (`max_values`: None, `max_size`: Some(67), added: 2542, mode: `MaxEncodedLen`)
+	/// Storage: `Staking::VirtualStakers` (r:1 w:1)
+	/// Proof: `Staking::VirtualStakers` (`max_values`: None, `max_size`: Some(40), added: 2515, mode: `MaxEncodedLen`)
+	/// Storage: `Balances::Holds` (r:1 w:1)
+	/// Proof: `Balances::Holds` (`max_values`: None, `max_size`: Some(103), added: 2578, mode: `MaxEncodedLen`)
 	/// Storage: `System::Account` (r:1 w:1)
 	/// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`)
 	/// Storage: `Staking::Validators` (r:1 w:0)
@@ -457,15 +459,15 @@ impl<T: frame_system::Config> pallet_staking::WeightInfo for WeightInfo<T> {
 	/// The range of component `s` is `[0, 100]`.
 	fn force_unstake(s: u32, ) -> Weight {
 		// Proof Size summary in bytes:
-		//  Measured:  `2127 + s * (4 ±0)`
+		//  Measured:  `2153 + s * (4 ±0)`
 		//  Estimated: `6248 + s * (4 ±0)`
-		// Minimum execution time: 78_908_000 picoseconds.
-		Weight::from_parts(84_886_373, 0)
+		// Minimum execution time: 116_776_000 picoseconds.
+		Weight::from_parts(125_460_389, 0)
 			.saturating_add(Weight::from_parts(0, 6248))
-			// Standard Error: 3_376
-			.saturating_add(Weight::from_parts(1_217_850, 0).saturating_mul(s.into()))
+			// Standard Error: 3_095
+			.saturating_add(Weight::from_parts(1_300_502, 0).saturating_mul(s.into()))
 			.saturating_add(T::DbWeight::get().reads(13))
-			.saturating_add(T::DbWeight::get().writes(12))
+			.saturating_add(T::DbWeight::get().writes(13))
 			.saturating_add(T::DbWeight::get().writes((1_u64).saturating_mul(s.into())))
 			.saturating_add(Weight::from_parts(0, 4).saturating_mul(s.into()))
 	}
@@ -474,13 +476,13 @@ impl<T: frame_system::Config> pallet_staking::WeightInfo for WeightInfo<T> {
 	/// The range of component `s` is `[1, 1000]`.
 	fn cancel_deferred_slash(s: u32, ) -> Weight {
 		// Proof Size summary in bytes:
-		//  Measured:  `66639`
-		//  Estimated: `70104`
-		// Minimum execution time: 136_389_000 picoseconds.
-		Weight::from_parts(1_207_241_524, 0)
-			.saturating_add(Weight::from_parts(0, 70104))
-			// Standard Error: 77_138
-			.saturating_add(Weight::from_parts(6_443_948, 0).saturating_mul(s.into()))
+		//  Measured:  `66672`
+		//  Estimated: `70137`
+		// Minimum execution time: 135_135_000 picoseconds.
+		Weight::from_parts(937_565_332, 0)
+			.saturating_add(Weight::from_parts(0, 70137))
+			// Standard Error: 57_675
+			.saturating_add(Weight::from_parts(4_828_080, 0).saturating_mul(s.into()))
 			.saturating_add(T::DbWeight::get().reads(1))
 			.saturating_add(T::DbWeight::get().writes(1))
 	}
@@ -498,12 +500,10 @@ impl<T: frame_system::Config> pallet_staking::WeightInfo for WeightInfo<T> {
 	/// Proof: `Staking::CurrentEra` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`)
 	/// Storage: `Staking::ErasValidatorReward` (r:1 w:0)
 	/// Proof: `Staking::ErasValidatorReward` (`max_values`: None, `max_size`: Some(28), added: 2503, mode: `MaxEncodedLen`)
-	/// Storage: `Balances::Locks` (r:65 w:65)
-	/// Proof: `Balances::Locks` (`max_values`: None, `max_size`: Some(1299), added: 3774, mode: `MaxEncodedLen`)
-	/// Storage: `Balances::Freezes` (r:65 w:0)
-	/// Proof: `Balances::Freezes` (`max_values`: None, `max_size`: Some(67), added: 2542, mode: `MaxEncodedLen`)
-	/// Storage: `System::Account` (r:65 w:65)
-	/// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`)
+	/// Storage: `Staking::VirtualStakers` (r:65 w:0)
+	/// Proof: `Staking::VirtualStakers` (`max_values`: None, `max_size`: Some(40), added: 2515, mode: `MaxEncodedLen`)
+	/// Storage: `Balances::Holds` (r:65 w:65)
+	/// Proof: `Balances::Holds` (`max_values`: None, `max_size`: Some(103), added: 2578, mode: `MaxEncodedLen`)
 	/// Storage: `Staking::ErasStakersPaged` (r:1 w:0)
 	/// Proof: `Staking::ErasStakersPaged` (`max_values`: None, `max_size`: None, mode: `Measured`)
 	/// Storage: `Staking::ErasRewardPoints` (r:1 w:0)
@@ -512,30 +512,32 @@ impl<T: frame_system::Config> pallet_staking::WeightInfo for WeightInfo<T> {
 	/// Proof: `Staking::ErasValidatorPrefs` (`max_values`: None, `max_size`: Some(57), added: 2532, mode: `MaxEncodedLen`)
 	/// Storage: `Staking::Payee` (r:65 w:0)
 	/// Proof: `Staking::Payee` (`max_values`: None, `max_size`: Some(73), added: 2548, mode: `MaxEncodedLen`)
+	/// Storage: `System::Account` (r:65 w:65)
+	/// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`)
 	/// The range of component `n` is `[0, 64]`.
 	fn payout_stakers_alive_staked(n: u32, ) -> Weight {
 		// Proof Size summary in bytes:
-		//  Measured:  `8249 + n * (396 ±0)`
-		//  Estimated: `10779 + n * (3774 ±0)`
-		// Minimum execution time: 130_222_000 picoseconds.
-		Weight::from_parts(167_236_150, 0)
-			.saturating_add(Weight::from_parts(0, 10779))
-			// Standard Error: 34_051
-			.saturating_add(Weight::from_parts(39_899_917, 0).saturating_mul(n.into()))
+		//  Measured:  `8275 + n * (389 ±0)`
+		//  Estimated: `10805 + n * (3566 ±0)`
+		// Minimum execution time: 180_144_000 picoseconds.
+		Weight::from_parts(237_134_733, 0)
+			.saturating_add(Weight::from_parts(0, 10805))
+			// Standard Error: 52_498
+			.saturating_add(Weight::from_parts(73_633_326, 0).saturating_mul(n.into()))
 			.saturating_add(T::DbWeight::get().reads(14))
 			.saturating_add(T::DbWeight::get().reads((6_u64).saturating_mul(n.into())))
 			.saturating_add(T::DbWeight::get().writes(4))
 			.saturating_add(T::DbWeight::get().writes((3_u64).saturating_mul(n.into())))
-			.saturating_add(Weight::from_parts(0, 3774).saturating_mul(n.into()))
+			.saturating_add(Weight::from_parts(0, 3566).saturating_mul(n.into()))
 	}
 	/// Storage: `Staking::Ledger` (r:1 w:1)
 	/// Proof: `Staking::Ledger` (`max_values`: None, `max_size`: Some(1091), added: 3566, mode: `MaxEncodedLen`)
 	/// Storage: `Staking::Bonded` (r:1 w:0)
 	/// Proof: `Staking::Bonded` (`max_values`: None, `max_size`: Some(72), added: 2547, mode: `MaxEncodedLen`)
-	/// Storage: `Balances::Locks` (r:1 w:1)
-	/// Proof: `Balances::Locks` (`max_values`: None, `max_size`: Some(1299), added: 3774, mode: `MaxEncodedLen`)
-	/// Storage: `Balances::Freezes` (r:1 w:0)
-	/// Proof: `Balances::Freezes` (`max_values`: None, `max_size`: Some(67), added: 2542, mode: `MaxEncodedLen`)
+	/// Storage: `Staking::VirtualStakers` (r:1 w:0)
+	/// Proof: `Staking::VirtualStakers` (`max_values`: None, `max_size`: Some(40), added: 2515, mode: `MaxEncodedLen`)
+	/// Storage: `Balances::Holds` (r:1 w:0)
+	/// Proof: `Balances::Holds` (`max_values`: None, `max_size`: Some(103), added: 2578, mode: `MaxEncodedLen`)
 	/// Storage: `VoterList::ListNodes` (r:3 w:3)
 	/// Proof: `VoterList::ListNodes` (`max_values`: None, `max_size`: Some(154), added: 2629, mode: `MaxEncodedLen`)
 	/// Storage: `VoterList::ListBags` (r:2 w:2)
@@ -543,26 +545,26 @@ impl<T: frame_system::Config> pallet_staking::WeightInfo for WeightInfo<T> {
 	/// The range of component `l` is `[1, 32]`.
 	fn rebond(l: u32, ) -> Weight {
 		// Proof Size summary in bytes:
-		//  Measured:  `1922 + l * (5 ±0)`
+		//  Measured:  `1845 + l * (5 ±0)`
 		//  Estimated: `8877`
-		// Minimum execution time: 79_136_000 picoseconds.
-		Weight::from_parts(82_129_497, 0)
+		// Minimum execution time: 89_307_000 picoseconds.
+		Weight::from_parts(92_902_634, 0)
 			.saturating_add(Weight::from_parts(0, 8877))
-			// Standard Error: 3_867
-			.saturating_add(Weight::from_parts(75_156, 0).saturating_mul(l.into()))
+			// Standard Error: 4_446
+			.saturating_add(Weight::from_parts(73_546, 0).saturating_mul(l.into()))
 			.saturating_add(T::DbWeight::get().reads(9))
-			.saturating_add(T::DbWeight::get().writes(7))
+			.saturating_add(T::DbWeight::get().writes(6))
 	}
+	/// Storage: `Staking::VirtualStakers` (r:1 w:1)
+	/// Proof: `Staking::VirtualStakers` (`max_values`: None, `max_size`: Some(40), added: 2515, mode: `MaxEncodedLen`)
 	/// Storage: `Staking::Bonded` (r:1 w:1)
 	/// Proof: `Staking::Bonded` (`max_values`: None, `max_size`: Some(72), added: 2547, mode: `MaxEncodedLen`)
 	/// Storage: `Staking::Ledger` (r:1 w:1)
 	/// Proof: `Staking::Ledger` (`max_values`: None, `max_size`: Some(1091), added: 3566, mode: `MaxEncodedLen`)
 	/// Storage: `Staking::SlashingSpans` (r:1 w:1)
 	/// Proof: `Staking::SlashingSpans` (`max_values`: None, `max_size`: None, mode: `Measured`)
-	/// Storage: `Balances::Locks` (r:1 w:1)
-	/// Proof: `Balances::Locks` (`max_values`: None, `max_size`: Some(1299), added: 3774, mode: `MaxEncodedLen`)
-	/// Storage: `Balances::Freezes` (r:1 w:0)
-	/// Proof: `Balances::Freezes` (`max_values`: None, `max_size`: Some(67), added: 2542, mode: `MaxEncodedLen`)
+	/// Storage: `Balances::Holds` (r:1 w:1)
+	/// Proof: `Balances::Holds` (`max_values`: None, `max_size`: Some(103), added: 2578, mode: `MaxEncodedLen`)
 	/// Storage: `Staking::Validators` (r:1 w:0)
 	/// Proof: `Staking::Validators` (`max_values`: None, `max_size`: Some(45), added: 2520, mode: `MaxEncodedLen`)
 	/// Storage: `Staking::Nominators` (r:1 w:1)
@@ -582,15 +584,15 @@ impl<T: frame_system::Config> pallet_staking::WeightInfo for WeightInfo<T> {
 	/// The range of component `s` is `[1, 100]`.
 	fn reap_stash(s: u32, ) -> Weight {
 		// Proof Size summary in bytes:
-		//  Measured:  `2127 + s * (4 ±0)`
+		//  Measured:  `2153 + s * (4 ±0)`
 		//  Estimated: `6248 + s * (4 ±0)`
-		// Minimum execution time: 89_375_000 picoseconds.
-		Weight::from_parts(91_224_907, 0)
+		// Minimum execution time: 130_544_000 picoseconds.
+		Weight::from_parts(133_260_598, 0)
 			.saturating_add(Weight::from_parts(0, 6248))
-			// Standard Error: 3_424
-			.saturating_add(Weight::from_parts(1_219_542, 0).saturating_mul(s.into()))
+			// Standard Error: 3_545
+			.saturating_add(Weight::from_parts(1_313_348, 0).saturating_mul(s.into()))
 			.saturating_add(T::DbWeight::get().reads(12))
-			.saturating_add(T::DbWeight::get().writes(11))
+			.saturating_add(T::DbWeight::get().writes(12))
 			.saturating_add(T::DbWeight::get().writes((1_u64).saturating_mul(s.into())))
 			.saturating_add(Weight::from_parts(0, 4).saturating_mul(s.into()))
 	}
@@ -633,14 +635,14 @@ impl<T: frame_system::Config> pallet_staking::WeightInfo for WeightInfo<T> {
 	fn new_era(v: u32, n: u32, ) -> Weight {
 		// Proof Size summary in bytes:
 		//  Measured:  `0 + n * (716 ±0) + v * (3594 ±0)`
-		//  Estimated: `456136 + n * (3566 ±4) + v * (3566 ±0)`
-		// Minimum execution time: 520_905_000 picoseconds.
-		Weight::from_parts(523_771_000, 0)
+		//  Estimated: `456136 + n * (3566 ±4) + v * (3566 ±40)`
+		// Minimum execution time: 654_756_000 picoseconds.
+		Weight::from_parts(658_861_000, 0)
 			.saturating_add(Weight::from_parts(0, 456136))
-			// Standard Error: 2_142_714
-			.saturating_add(Weight::from_parts(68_631_588, 0).saturating_mul(v.into()))
-			// Standard Error: 213_509
-			.saturating_add(Weight::from_parts(19_343_025, 0).saturating_mul(n.into()))
+			// Standard Error: 2_078_102
+			.saturating_add(Weight::from_parts(67_775_668, 0).saturating_mul(v.into()))
+			// Standard Error: 207_071
+			.saturating_add(Weight::from_parts(22_624_711, 0).saturating_mul(n.into()))
 			.saturating_add(T::DbWeight::get().reads(184))
 			.saturating_add(T::DbWeight::get().reads((5_u64).saturating_mul(v.into())))
 			.saturating_add(T::DbWeight::get().reads((4_u64).saturating_mul(n.into())))
@@ -669,15 +671,15 @@ impl<T: frame_system::Config> pallet_staking::WeightInfo for WeightInfo<T> {
 	/// The range of component `n` is `[500, 1000]`.
 	fn get_npos_voters(v: u32, n: u32, ) -> Weight {
 		// Proof Size summary in bytes:
-		//  Measured:  `3108 + n * (907 ±0) + v * (391 ±0)`
+		//  Measured:  `3141 + n * (907 ±0) + v * (391 ±0)`
 		//  Estimated: `456136 + n * (3566 ±0) + v * (3566 ±0)`
-		// Minimum execution time: 36_848_619_000 picoseconds.
-		Weight::from_parts(37_362_442_000, 0)
+		// Minimum execution time: 42_790_195_000 picoseconds.
+		Weight::from_parts(42_954_437_000, 0)
 			.saturating_add(Weight::from_parts(0, 456136))
-			// Standard Error: 415_031
-			.saturating_add(Weight::from_parts(5_204_987, 0).saturating_mul(v.into()))
-			// Standard Error: 415_031
-			.saturating_add(Weight::from_parts(4_132_636, 0).saturating_mul(n.into()))
+			// Standard Error: 478_107
+			.saturating_add(Weight::from_parts(6_744_044, 0).saturating_mul(v.into()))
+			// Standard Error: 478_107
+			.saturating_add(Weight::from_parts(4_837_739, 0).saturating_mul(n.into()))
 			.saturating_add(T::DbWeight::get().reads(179))
 			.saturating_add(T::DbWeight::get().reads((5_u64).saturating_mul(v.into())))
 			.saturating_add(T::DbWeight::get().reads((4_u64).saturating_mul(n.into())))
@@ -692,13 +694,13 @@ impl<T: frame_system::Config> pallet_staking::WeightInfo for WeightInfo<T> {
 	/// The range of component `v` is `[500, 1000]`.
 	fn get_npos_targets(v: u32, ) -> Weight {
 		// Proof Size summary in bytes:
-		//  Measured:  `946 + v * (50 ±0)`
+		//  Measured:  `979 + v * (50 ±0)`
 		//  Estimated: `3510 + v * (2520 ±0)`
-		// Minimum execution time: 2_512_817_000 picoseconds.
-		Weight::from_parts(119_401_374, 0)
+		// Minimum execution time: 2_851_801_000 picoseconds.
+		Weight::from_parts(4_477_533, 0)
 			.saturating_add(Weight::from_parts(0, 3510))
-			// Standard Error: 8_463
-			.saturating_add(Weight::from_parts(4_860_364, 0).saturating_mul(v.into()))
+			// Standard Error: 8_644
+			.saturating_add(Weight::from_parts(5_811_682, 0).saturating_mul(v.into()))
 			.saturating_add(T::DbWeight::get().reads(2))
 			.saturating_add(T::DbWeight::get().reads((1_u64).saturating_mul(v.into())))
 			.saturating_add(Weight::from_parts(0, 2520).saturating_mul(v.into()))
@@ -721,8 +723,8 @@ impl<T: frame_system::Config> pallet_staking::WeightInfo for WeightInfo<T> {
 		// Proof Size summary in bytes:
 		//  Measured:  `0`
 		//  Estimated: `0`
-		// Minimum execution time: 3_686_000 picoseconds.
-		Weight::from_parts(3_881_000, 0)
+		// Minimum execution time: 4_250_000 picoseconds.
+		Weight::from_parts(4_472_000, 0)
 			.saturating_add(Weight::from_parts(0, 0))
 			.saturating_add(T::DbWeight::get().writes(7))
 	}
@@ -744,8 +746,8 @@ impl<T: frame_system::Config> pallet_staking::WeightInfo for WeightInfo<T> {
 		// Proof Size summary in bytes:
 		//  Measured:  `0`
 		//  Estimated: `0`
-		// Minimum execution time: 3_143_000 picoseconds.
-		Weight::from_parts(3_424_000, 0)
+		// Minimum execution time: 3_986_000 picoseconds.
+		Weight::from_parts(4_144_000, 0)
 			.saturating_add(Weight::from_parts(0, 0))
 			.saturating_add(T::DbWeight::get().writes(7))
 	}
@@ -773,10 +775,10 @@ impl<T: frame_system::Config> pallet_staking::WeightInfo for WeightInfo<T> {
 	/// Proof: `VoterList::CounterForListNodes` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`)
 	fn chill_other() -> Weight {
 		// Proof Size summary in bytes:
-		//  Measured:  `1870`
+		//  Measured:  `1903`
 		//  Estimated: `6248`
-		// Minimum execution time: 66_946_000 picoseconds.
-		Weight::from_parts(69_382_000, 0)
+		// Minimum execution time: 87_291_000 picoseconds.
+		Weight::from_parts(89_344_000, 0)
 			.saturating_add(Weight::from_parts(0, 6248))
 			.saturating_add(T::DbWeight::get().reads(12))
 			.saturating_add(T::DbWeight::get().writes(6))
@@ -787,10 +789,10 @@ impl<T: frame_system::Config> pallet_staking::WeightInfo for WeightInfo<T> {
 	/// Proof: `Staking::Validators` (`max_values`: None, `max_size`: Some(45), added: 2520, mode: `MaxEncodedLen`)
 	fn force_apply_min_commission() -> Weight {
 		// Proof Size summary in bytes:
-		//  Measured:  `658`
+		//  Measured:  `691`
 		//  Estimated: `3510`
-		// Minimum execution time: 11_278_000 picoseconds.
-		Weight::from_parts(11_603_000, 0)
+		// Minimum execution time: 16_113_000 picoseconds.
+		Weight::from_parts(16_593_000, 0)
 			.saturating_add(Weight::from_parts(0, 3510))
 			.saturating_add(T::DbWeight::get().reads(2))
 			.saturating_add(T::DbWeight::get().writes(1))
@@ -801,29 +803,53 @@ impl<T: frame_system::Config> pallet_staking::WeightInfo for WeightInfo<T> {
 		// Proof Size summary in bytes:
 		//  Measured:  `0`
 		//  Estimated: `0`
-		// Minimum execution time: 1_963_000 picoseconds.
-		Weight::from_parts(2_077_000, 0)
+		// Minimum execution time: 2_433_000 picoseconds.
+		Weight::from_parts(2_561_000, 0)
 			.saturating_add(Weight::from_parts(0, 0))
 			.saturating_add(T::DbWeight::get().writes(1))
 	}
-	/// Storage: `Balances::Locks` (r:1 w:1)
-	/// Proof: `Balances::Locks` (`max_values`: None, `max_size`: Some(1299), added: 3774, mode: `MaxEncodedLen`)
-	/// Storage: `System::Account` (r:1 w:1)
+	/// Storage: `Staking::VirtualStakers` (r:1 w:0)
+	/// Proof: `Staking::VirtualStakers` (`max_values`: None, `max_size`: Some(40), added: 2515, mode: `MaxEncodedLen`)
+	/// Storage: `Balances::Holds` (r:1 w:0)
+	/// Proof: `Balances::Holds` (`max_values`: None, `max_size`: Some(103), added: 2578, mode: `MaxEncodedLen`)
+	/// Storage: `System::Account` (r:1 w:0)
 	/// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`)
+	/// Storage: `Balances::Locks` (r:1 w:0)
+	/// Proof: `Balances::Locks` (`max_values`: None, `max_size`: Some(1299), added: 3774, mode: `MaxEncodedLen`)
 	/// Storage: `Staking::Bonded` (r:1 w:1)
 	/// Proof: `Staking::Bonded` (`max_values`: None, `max_size`: Some(72), added: 2547, mode: `MaxEncodedLen`)
 	/// Storage: `Staking::Ledger` (r:1 w:1)
 	/// Proof: `Staking::Ledger` (`max_values`: None, `max_size`: Some(1091), added: 3566, mode: `MaxEncodedLen`)
+	fn restore_ledger() -> Weight {
+		// Proof Size summary in bytes:
+		//  Measured:  `1040`
+		//  Estimated: `4764`
+		// Minimum execution time: 50_167_000 picoseconds.
+		Weight::from_parts(51_108_000, 0)
+			.saturating_add(Weight::from_parts(0, 4764))
+			.saturating_add(T::DbWeight::get().reads(6))
+			.saturating_add(T::DbWeight::get().writes(2))
+	}
+	/// Storage: `Staking::VirtualStakers` (r:1 w:0)
+	/// Proof: `Staking::VirtualStakers` (`max_values`: None, `max_size`: Some(40), added: 2515, mode: `MaxEncodedLen`)
+	/// Storage: `Staking::Bonded` (r:1 w:0)
+	/// Proof: `Staking::Bonded` (`max_values`: None, `max_size`: Some(72), added: 2547, mode: `MaxEncodedLen`)
+	/// Storage: `Staking::Ledger` (r:1 w:0)
+	/// Proof: `Staking::Ledger` (`max_values`: None, `max_size`: Some(1091), added: 3566, mode: `MaxEncodedLen`)
+	/// Storage: `Balances::Locks` (r:1 w:1)
+	/// Proof: `Balances::Locks` (`max_values`: None, `max_size`: Some(1299), added: 3774, mode: `MaxEncodedLen`)
+	/// Storage: `Balances::Holds` (r:1 w:1)
+	/// Proof: `Balances::Holds` (`max_values`: None, `max_size`: Some(103), added: 2578, mode: `MaxEncodedLen`)
 	/// Storage: `Balances::Freezes` (r:1 w:0)
 	/// Proof: `Balances::Freezes` (`max_values`: None, `max_size`: Some(67), added: 2542, mode: `MaxEncodedLen`)
-	fn restore_ledger() -> Weight {
+	fn migrate_currency() -> Weight {
 		// Proof Size summary in bytes:
-		//  Measured:  `1014`
+		//  Measured:  `1209`
 		//  Estimated: `4764`
-		// Minimum execution time: 40_258_000 picoseconds.
-		Weight::from_parts(41_210_000, 0)
+		// Minimum execution time: 91_790_000 picoseconds.
+		Weight::from_parts(92_991_000, 0)
 			.saturating_add(Weight::from_parts(0, 4764))
-			.saturating_add(T::DbWeight::get().reads(5))
-			.saturating_add(T::DbWeight::get().writes(4))
+			.saturating_add(T::DbWeight::get().reads(6))
+			.saturating_add(T::DbWeight::get().writes(2))
 	}
 }
diff --git a/polkadot/runtime/westend/src/xcm_config.rs b/polkadot/runtime/westend/src/xcm_config.rs
index 3f6a7304c8a94b8b502cb19f0016e57b69ddee2d..4235edf82b24de71f82c21dda1bd69c231048ac9 100644
--- a/polkadot/runtime/westend/src/xcm_config.rs
+++ b/polkadot/runtime/westend/src/xcm_config.rs
@@ -280,7 +280,10 @@ impl pallet_xcm::Config for Runtime {
 	type RuntimeEvent = RuntimeEvent;
 	// Note that this configuration of `SendXcmOrigin` is different from the one present in
 	// production.
-	type SendXcmOrigin = xcm_builder::EnsureXcmOrigin<RuntimeOrigin, LocalOriginToLocation>;
+	type SendXcmOrigin = xcm_builder::EnsureXcmOrigin<
+		RuntimeOrigin,
+		(LocalPalletOriginToLocation, LocalOriginToLocation),
+	>;
 	type XcmRouter = XcmRouter;
 	// Anyone can execute XCM messages locally.
 	type ExecuteXcmOrigin = xcm_builder::EnsureXcmOrigin<RuntimeOrigin, LocalOriginToLocation>;
diff --git a/polkadot/xcm/docs/Cargo.toml b/polkadot/xcm/docs/Cargo.toml
index 6fa7ea9a23a92c2e3f86082a630ca3994bfce1d1..c3bda50619c1572202db2a3334bc14d4d11c6b4b 100644
--- a/polkadot/xcm/docs/Cargo.toml
+++ b/polkadot/xcm/docs/Cargo.toml
@@ -18,7 +18,7 @@ xcm-simulator = { workspace = true, default-features = true }
 
 # For building FRAME runtimes
 codec = { workspace = true, default-features = true }
-frame = { features = ["experimental", "runtime"], workspace = true, default-features = true }
+frame = { features = ["runtime"], workspace = true, default-features = true }
 polkadot-parachain-primitives = { workspace = true, default-features = true }
 polkadot-primitives = { workspace = true, default-features = true }
 polkadot-runtime-parachains = { workspace = true, default-features = true }
diff --git a/prdoc/pr_3926.prdoc b/prdoc/pr_3926.prdoc
new file mode 100644
index 0000000000000000000000000000000000000000..7f352f7a45fb39876d7d2d0771d3e5cee496ead3
--- /dev/null
+++ b/prdoc/pr_3926.prdoc
@@ -0,0 +1,30 @@
+title: Introduce pallet-asset-rewards
+
+doc:
+  - audience: Runtime Dev
+    description: |
+        Introduce pallet-asset-rewards, which allows accounts to be rewarded for freezing fungible 
+        tokens. The motivation for creating this pallet is to allow incentivising LPs.
+        See the pallet docs for more info about the pallet.
+
+crates:
+  - name: pallet-asset-rewards
+    bump: major
+  - name: polkadot-sdk
+    bump: minor
+  - name: kitchensink-runtime
+    bump: major
+  - name: asset-hub-rococo-runtime
+    bump: major
+  - name: asset-hub-westend-runtime
+    bump: major
+  - name: assets-common
+    bump: minor
+  - name: rococo-runtime
+    bump: minor
+  - name: westend-runtime
+    bump: patch
+  - name: frame-support
+    bump: minor
+  - name: emulated-integration-tests-common
+    bump: minor
diff --git a/prdoc/pr_5501.prdoc b/prdoc/pr_5501.prdoc
new file mode 100644
index 0000000000000000000000000000000000000000..f2a5aa9a4667980452d4ac4dc728752da3591634
--- /dev/null
+++ b/prdoc/pr_5501.prdoc
@@ -0,0 +1,47 @@
+# Schema: Polkadot SDK PRDoc Schema (prdoc) v1.0.0
+# See doc at https://raw.githubusercontent.com/paritytech/polkadot-sdk/master/prdoc/schema_user.json
+
+title: Currency to Fungible migration for pallet-staking
+
+doc:
+  - audience: Runtime User
+    description: |
+      Lazy migration of staking balance from `Currency::locks` to `Fungible::holds`. New extrinsic
+      `staking::migrate_currency` removes the old lock along with other housekeeping. Additionally, any ledger mutation
+      creates hold if it does not exist.
+
+      The pallet-staking configuration item `Currency` is updated to use `fungible::hold::Mutate` type while still
+      requiring `LockableCurrency` type to be passed as `OldCurrency` for migration purposes.
+
+
+crates:
+  - name: westend-runtime
+    bump: major
+  - name: kitchensink-runtime
+    bump: minor
+  - name: pallet-delegated-staking
+    bump: patch
+  - name: pallet-nomination-pools
+    bump: minor
+  - name: pallet-nomination-pools-runtime-api
+    bump: patch
+  - name: sp-staking
+    bump: patch
+  - name: pallet-beefy
+    bump: patch
+  - name: pallet-fast-unstake
+    bump: patch
+  - name: pallet-staking
+    bump: major
+  - name: pallet-grandpa
+    bump: patch
+  - name: pallet-babe
+    bump: patch
+  - name: pallet-nomination-pools-benchmarking
+    bump: patch
+  - name: pallet-session-benchmarking
+    bump: patch
+  - name: pallet-root-offences
+    bump: patch
+  - name: pallet-offences-benchmarking
+    bump: patch
diff --git a/prdoc/pr_6995.prdoc b/prdoc/pr_6995.prdoc
new file mode 100644
index 0000000000000000000000000000000000000000..ffdb4738a6fd5b1841d07ddd8e5e462c42abd704
--- /dev/null
+++ b/prdoc/pr_6995.prdoc
@@ -0,0 +1,14 @@
+title: added new proxy ParaRegistration to Westend
+doc:
+- audience: Runtime User
+  description: |-
+    This adds a new Proxy type to Westend Runtime called ParaRegistration. This is related to: https://github.com/polkadot-fellows/runtimes/pull/520.
+
+    This new proxy allows:
+    1. Reserve paraID
+    2. Register Parachain
+    3. Leverage Utilites pallet
+    4. Remove proxy.
+crates:
+- name: westend-runtime
+  bump: major
diff --git a/prdoc/pr_7091.prdoc b/prdoc/pr_7091.prdoc
new file mode 100644
index 0000000000000000000000000000000000000000..badea4e82fdbec9a48eb8948bd762b90cd49275a
--- /dev/null
+++ b/prdoc/pr_7091.prdoc
@@ -0,0 +1,12 @@
+title: '[pallet-revive] Add new host function `to_account_id`'
+doc:
+- audience: Runtime Dev
+  description: A new host function `to_account_id` is added. It allows retrieving
+    the account id for a `H160` address.
+crates:
+- name: pallet-revive-fixtures
+  bump: minor
+- name: pallet-revive
+  bump: minor
+- name: pallet-revive-uapi
+  bump: minor
diff --git a/prdoc/pr_7099.prdoc b/prdoc/pr_7099.prdoc
new file mode 100644
index 0000000000000000000000000000000000000000..58d809f3c0909b9236381aa0ab34a2f17ba84a40
--- /dev/null
+++ b/prdoc/pr_7099.prdoc
@@ -0,0 +1,16 @@
+title: Provide partial results to speedup GetRecord queries
+
+doc:
+  - audience: Node Dev
+    description: |
+      This PR provides the partial results of the GetRecord kademlia query.
+      
+      This significantly improves the authority discovery records, from ~37 minutes to ~2/3 minutes.
+      In contrast, libp2p discovers authority records in around ~10 minutes.
+      
+      The authority discovery was slow because litep2p provided the records only after the Kademlia query was completed. A normal Kademlia query completes in around 40 seconds to a few minutes.
+      In this PR, partial records are provided as soon as they are discovered from the network.
+
+crates:
+  - name: sc-network
+    bump: patch
diff --git a/prdoc/pr_7163.prdoc b/prdoc/pr_7163.prdoc
new file mode 100644
index 0000000000000000000000000000000000000000..669c480b835bd982699f6a0eec21fbb343d8b6e3
--- /dev/null
+++ b/prdoc/pr_7163.prdoc
@@ -0,0 +1,13 @@
+title: '[pallet-revive] Remove debug buffer'
+doc:
+- audience: Runtime Dev
+  description: Remove the `debug_buffer` feature
+crates:
+- name: asset-hub-westend-runtime
+  bump: minor
+- name: pallet-revive
+  bump: major
+- name: pallet-revive-proc-macro
+  bump: minor
+- name: pallet-revive-uapi
+  bump: minor
diff --git a/prdoc/pr_7164.prdoc b/prdoc/pr_7164.prdoc
new file mode 100644
index 0000000000000000000000000000000000000000..cb0410a9de79954bab775de09629610ba420b2b4
--- /dev/null
+++ b/prdoc/pr_7164.prdoc
@@ -0,0 +1,8 @@
+title: '[pallet-revive] Remove revive events'
+doc:
+- audience: Runtime Dev
+  description: Remove all pallet::events except for the `ContractEmitted` event that
+    is emitted by contracts
+crates:
+- name: pallet-revive
+  bump: major
diff --git a/prdoc/pr_7176.prdoc b/prdoc/pr_7176.prdoc
new file mode 100644
index 0000000000000000000000000000000000000000..b78f42014afea5e04d6c3057e91c62d9f9928db5
--- /dev/null
+++ b/prdoc/pr_7176.prdoc
@@ -0,0 +1,9 @@
+title: '[pallet-revive] Bump asset-hub westend spec version'
+doc:
+- audience: Runtime Dev
+  description: Bump asset-hub westend spec version
+crates:
+- name: asset-hub-westend-runtime
+  bump: minor
+- name: pallet-revive-eth-rpc
+  bump: minor
diff --git a/prdoc/pr_7177.prdoc b/prdoc/pr_7177.prdoc
new file mode 100644
index 0000000000000000000000000000000000000000..9ab0be1f20a936efe9feadf81c79a5f74f3531f6
--- /dev/null
+++ b/prdoc/pr_7177.prdoc
@@ -0,0 +1,20 @@
+title: Make frame crate not experimental
+doc:
+- audience: Runtime Dev
+  description: |-
+    Frame crate may still be unstable, but it is no longer feature gated by the feature `experimental`.
+crates:
+- name: polkadot-sdk-frame
+  bump: minor
+- name: pallet-salary
+  bump: patch
+- name: pallet-multisig
+  bump: patch
+- name: pallet-proxy
+  bump: patch
+- name: pallet-atomic-swap
+  bump: patch
+- name: pallet-mixnet
+  bump: patch
+- name: pallet-node-authorization
+  bump: patch
diff --git a/prdoc/pr_7194.prdoc b/prdoc/pr_7194.prdoc
new file mode 100644
index 0000000000000000000000000000000000000000..3a9db46ceae96fdbb9f352a4558b1ab95ffd1450
--- /dev/null
+++ b/prdoc/pr_7194.prdoc
@@ -0,0 +1,15 @@
+# Schema: Polkadot SDK PRDoc Schema (prdoc) v1.0.0
+# See doc at https://raw.githubusercontent.com/paritytech/polkadot-sdk/master/prdoc/schema_user.json
+
+title: '[FRAME] `pallet_asset_tx_payment`: replace `AssetId` bound from `Copy` to `Clone`'
+
+doc:
+  - audience: Runtime Dev
+    description: |
+      `OnChargeAssetTransaction`'s associated type `AssetId` is bounded by `Copy` which makes it impossible
+      to use `staging_xcm::v4::Location` as `AssetId`. This PR bounds `AssetId` to `Clone` instead, which is 
+      more lenient.
+
+crates:
+  - name: pallet-asset-tx-payment
+    bump: minor
diff --git a/prdoc/pr_7203.prdoc b/prdoc/pr_7203.prdoc
new file mode 100644
index 0000000000000000000000000000000000000000..96a3d19472e9f77189eaaf0f9ad3c829aa27e8a4
--- /dev/null
+++ b/prdoc/pr_7203.prdoc
@@ -0,0 +1,13 @@
+title: 'pallet_revive: Bump PolkaVM'
+doc:
+- audience: Runtime Dev
+  description: Update to PolkaVM `0.19`. This version renumbers the opcodes in order
+    to be in-line with the grey paper. Hopefully, for the last time. This means that
+    it breaks existing contracts.
+crates:
+- name: pallet-revive
+  bump: patch
+- name: pallet-revive-fixtures
+  bump: patch
+- name: pallet-revive-uapi
+  bump: patch
diff --git a/substrate/bin/node/runtime/src/lib.rs b/substrate/bin/node/runtime/src/lib.rs
index e11a009c1c3f5d4081f9bffc6b6a73d24800f17e..26f4dacf9a1e3039d3cd8e8d0f79415e4727be07 100644
--- a/substrate/bin/node/runtime/src/lib.rs
+++ b/substrate/bin/node/runtime/src/lib.rs
@@ -53,13 +53,15 @@ use frame_support::{
 			Balanced, Credit, HoldConsideration, ItemOf, NativeFromLeft, NativeOrWithId, UnionOf,
 		},
 		tokens::{
-			imbalance::ResolveAssetTo, nonfungibles_v2::Inspect, pay::PayAssetFromAccount,
+			imbalance::{ResolveAssetTo, ResolveTo},
+			nonfungibles_v2::Inspect,
+			pay::PayAssetFromAccount,
 			GetSalary, PayFromAccount,
 		},
-		AsEnsureOriginWithArg, ConstBool, ConstU128, ConstU16, ConstU32, ConstU64, Contains,
-		Currency, EitherOfDiverse, EnsureOriginWithArg, EqualPrivilegeOnly, Imbalance, InsideBoth,
-		InstanceFilter, KeyOwnerProofSystem, LinearStoragePrice, LockIdentifier, Nothing,
-		OnUnbalanced, VariantCountOf, WithdrawReasons,
+		AsEnsureOriginWithArg, ConstBool, ConstU128, ConstU16, ConstU32, ConstU64,
+		ConstantStoragePrice, Contains, Currency, EitherOfDiverse, EnsureOriginWithArg,
+		EqualPrivilegeOnly, Imbalance, InsideBoth, InstanceFilter, KeyOwnerProofSystem,
+		LinearStoragePrice, LockIdentifier, Nothing, OnUnbalanced, VariantCountOf, WithdrawReasons,
 	},
 	weights::{
 		constants::{
@@ -511,7 +513,8 @@ impl pallet_glutton::Config for Runtime {
 }
 
 parameter_types! {
-	pub const PreimageHoldReason: RuntimeHoldReason = RuntimeHoldReason::Preimage(pallet_preimage::HoldReason::Preimage);
+	pub const PreimageHoldReason: RuntimeHoldReason =
+		RuntimeHoldReason::Preimage(pallet_preimage::HoldReason::Preimage);
 }
 
 impl pallet_preimage::Config for Runtime {
@@ -618,6 +621,12 @@ impl pallet_transaction_payment::Config for Runtime {
 	type WeightInfo = pallet_transaction_payment::weights::SubstrateWeight<Runtime>;
 }
 
+pub type AssetsFreezerInstance = pallet_assets_freezer::Instance1;
+impl pallet_assets_freezer::Config<AssetsFreezerInstance> for Runtime {
+	type RuntimeFreezeReason = RuntimeFreezeReason;
+	type RuntimeEvent = RuntimeEvent;
+}
+
 impl pallet_asset_conversion_tx_payment::Config for Runtime {
 	type RuntimeEvent = RuntimeEvent;
 	type AssetId = NativeOrWithId<u32>;
@@ -712,13 +721,15 @@ impl pallet_staking::BenchmarkingConfig for StakingBenchmarkingConfig {
 }
 
 impl pallet_staking::Config for Runtime {
+	type OldCurrency = Balances;
 	type Currency = Balances;
 	type CurrencyBalance = Balance;
 	type UnixTime = Timestamp;
 	type CurrencyToVote = sp_staking::currency_to_vote::U128CurrencyToVote;
-	type RewardRemainder = Treasury;
+	type RewardRemainder = ResolveTo<TreasuryAccount, Balances>;
 	type RuntimeEvent = RuntimeEvent;
-	type Slash = Treasury; // send the slashed funds to the treasury.
+	type RuntimeHoldReason = RuntimeHoldReason;
+	type Slash = ResolveTo<TreasuryAccount, Balances>; // send the slashed funds to the treasury.
 	type Reward = (); // rewards are minted from the void
 	type SessionsPerEra = SessionsPerEra;
 	type BondingDuration = BondingDuration;
@@ -741,7 +752,7 @@ impl pallet_staking::Config for Runtime {
 	type MaxUnlockingChunks = ConstU32<32>;
 	type MaxControllersInDeprecationBatch = MaxControllersInDeprecationBatch;
 	type HistoryDepth = HistoryDepth;
-	type EventListeners = NominationPools;
+	type EventListeners = (NominationPools, DelegatedStaking);
 	type WeightInfo = pallet_staking::weights::SubstrateWeight<Runtime>;
 	type BenchmarkingConfig = StakingBenchmarkingConfig;
 	type DisablingStrategy = pallet_staking::UpToLimitWithReEnablingDisablingStrategy;
@@ -925,6 +936,21 @@ impl pallet_bags_list::Config<VoterBagsListInstance> for Runtime {
 	type WeightInfo = pallet_bags_list::weights::SubstrateWeight<Runtime>;
 }
 
+parameter_types! {
+	pub const DelegatedStakingPalletId: PalletId = PalletId(*b"py/dlstk");
+	pub const SlashRewardFraction: Perbill = Perbill::from_percent(1);
+}
+
+impl pallet_delegated_staking::Config for Runtime {
+	type RuntimeEvent = RuntimeEvent;
+	type PalletId = DelegatedStakingPalletId;
+	type Currency = Balances;
+	type OnSlash = ();
+	type SlashRewardFraction = SlashRewardFraction;
+	type RuntimeHoldReason = RuntimeHoldReason;
+	type CoreStaking = Staking;
+}
+
 parameter_types! {
 	pub const PostUnbondPoolsWindow: u32 = 4;
 	pub const NominationPoolsPalletId: PalletId = PalletId(*b"py/nopls");
@@ -953,7 +979,8 @@ impl pallet_nomination_pools::Config for Runtime {
 	type RewardCounter = FixedU128;
 	type BalanceToU256 = BalanceToU256;
 	type U256ToBalance = U256ToBalance;
-	type StakeAdapter = pallet_nomination_pools::adapter::TransferStake<Self, Staking>;
+	type StakeAdapter =
+		pallet_nomination_pools::adapter::DelegateStake<Self, Staking, DelegatedStaking>;
 	type PostUnbondingPoolsWindow = PostUnbondPoolsWindow;
 	type MaxMetadataLen = ConstU32<256>;
 	type MaxUnbonding = ConstU32<8>;
@@ -1464,7 +1491,6 @@ impl pallet_revive::Config for Runtime {
 	type InstantiateOrigin = EnsureSigned<Self::AccountId>;
 	type RuntimeHoldReason = RuntimeHoldReason;
 	type CodeHashLockupDepositPercent = CodeHashLockupDepositPercent;
-	type Debug = ();
 	type Xcm = ();
 	type ChainId = ConstU64<420_420_420>;
 	type NativeToEthRatio = ConstU32<1_000_000>; // 10^(18 - 12) Eth is 10^18, Native is 10^12.
@@ -1858,6 +1884,53 @@ impl pallet_asset_conversion::Config for Runtime {
 	type BenchmarkHelper = ();
 }
 
+pub type NativeAndAssetsFreezer =
+	UnionOf<Balances, AssetsFreezer, NativeFromLeft, NativeOrWithId<u32>, AccountId>;
+
+/// Benchmark Helper
+#[cfg(feature = "runtime-benchmarks")]
+pub struct AssetRewardsBenchmarkHelper;
+
+#[cfg(feature = "runtime-benchmarks")]
+impl pallet_asset_rewards::benchmarking::BenchmarkHelper<NativeOrWithId<u32>>
+	for AssetRewardsBenchmarkHelper
+{
+	fn staked_asset() -> NativeOrWithId<u32> {
+		NativeOrWithId::<u32>::WithId(100)
+	}
+	fn reward_asset() -> NativeOrWithId<u32> {
+		NativeOrWithId::<u32>::WithId(101)
+	}
+}
+
+parameter_types! {
+	pub const StakingRewardsPalletId: PalletId = PalletId(*b"py/stkrd");
+	pub const CreationHoldReason: RuntimeHoldReason =
+		RuntimeHoldReason::AssetRewards(pallet_asset_rewards::HoldReason::PoolCreation);
+	// 1 item, 135 bytes into the storage on pool creation.
+	pub const StakePoolCreationDeposit: Balance = deposit(1, 135);
+}
+
+impl pallet_asset_rewards::Config for Runtime {
+	type RuntimeEvent = RuntimeEvent;
+	type RuntimeFreezeReason = RuntimeFreezeReason;
+	type AssetId = NativeOrWithId<u32>;
+	type Balance = Balance;
+	type Assets = NativeAndAssets;
+	type PalletId = StakingRewardsPalletId;
+	type CreatePoolOrigin = EnsureSigned<AccountId>;
+	type WeightInfo = ();
+	type AssetsFreezer = NativeAndAssetsFreezer;
+	type Consideration = HoldConsideration<
+		AccountId,
+		Balances,
+		CreationHoldReason,
+		ConstantStoragePrice<StakePoolCreationDeposit, Balance>,
+	>;
+	#[cfg(feature = "runtime-benchmarks")]
+	type BenchmarkHelper = AssetRewardsBenchmarkHelper;
+}
+
 impl pallet_asset_conversion_ops::Config for Runtime {
 	type RuntimeEvent = RuntimeEvent;
 	type PriorAccountIdConverter = pallet_asset_conversion::AccountIdConverterNoSeed<(
@@ -2636,6 +2709,15 @@ mod runtime {
 
 	#[runtime::pallet_index(81)]
 	pub type VerifySignature = pallet_verify_signature::Pallet<Runtime>;
+
+	#[runtime::pallet_index(82)]
+	pub type DelegatedStaking = pallet_delegated_staking::Pallet<Runtime>;
+
+	#[runtime::pallet_index(83)]
+	pub type AssetRewards = pallet_asset_rewards::Pallet<Runtime>;
+
+	#[runtime::pallet_index(84)]
+	pub type AssetsFreezer = pallet_assets_freezer::Pallet<Runtime, Instance1>;
 }
 
 impl TryFrom<RuntimeCall> for pallet_revive::Call<Runtime> {
@@ -2846,6 +2928,7 @@ mod benches {
 		[pallet_example_tasks, TasksExample]
 		[pallet_democracy, Democracy]
 		[pallet_asset_conversion, AssetConversion]
+		[pallet_asset_rewards, AssetRewards]
 		[pallet_asset_conversion_tx_payment, AssetConversionTxPayment]
 		[pallet_transaction_payment, TransactionPayment]
 		[pallet_election_provider_multi_phase, ElectionProviderMultiPhase]
@@ -3212,7 +3295,7 @@ impl_runtime_apis! {
 		}
 	}
 
-	impl pallet_revive::ReviveApi<Block, AccountId, Balance, Nonce, BlockNumber, EventRecord> for Runtime
+	impl pallet_revive::ReviveApi<Block, AccountId, Balance, Nonce, BlockNumber> for Runtime
 	{
 		fn balance(address: H160) -> U256 {
 			Revive::evm_balance(&address)
@@ -3247,7 +3330,7 @@ impl_runtime_apis! {
 			gas_limit: Option<Weight>,
 			storage_deposit_limit: Option<Balance>,
 			input_data: Vec<u8>,
-		) -> pallet_revive::ContractResult<pallet_revive::ExecReturnValue, Balance, EventRecord> {
+		) -> pallet_revive::ContractResult<pallet_revive::ExecReturnValue, Balance> {
 			Revive::bare_call(
 				RuntimeOrigin::signed(origin),
 				dest,
@@ -3255,8 +3338,6 @@ impl_runtime_apis! {
 				gas_limit.unwrap_or(RuntimeBlockWeights::get().max_block),
 				pallet_revive::DepositLimit::Balance(storage_deposit_limit.unwrap_or(u128::MAX)),
 				input_data,
-				pallet_revive::DebugInfo::UnsafeDebug,
-				pallet_revive::CollectEvents::UnsafeCollect,
 			)
 		}
 
@@ -3268,7 +3349,7 @@ impl_runtime_apis! {
 			code: pallet_revive::Code,
 			data: Vec<u8>,
 			salt: Option<[u8; 32]>,
-		) -> pallet_revive::ContractResult<pallet_revive::InstantiateReturnValue, Balance, EventRecord>
+		) -> pallet_revive::ContractResult<pallet_revive::InstantiateReturnValue, Balance>
 		{
 			Revive::bare_instantiate(
 				RuntimeOrigin::signed(origin),
@@ -3278,8 +3359,6 @@ impl_runtime_apis! {
 				code,
 				data,
 				salt,
-				pallet_revive::DebugInfo::UnsafeDebug,
-				pallet_revive::CollectEvents::UnsafeCollect,
 			)
 		}
 
@@ -3557,6 +3636,12 @@ impl_runtime_apis! {
 		}
 	}
 
+	impl pallet_asset_rewards::AssetRewards<Block, Balance> for Runtime {
+		fn pool_creation_cost() -> Balance {
+			StakePoolCreationDeposit::get()
+		}
+	}
+
 	#[cfg(feature = "try-runtime")]
 	impl frame_try_runtime::TryRuntime<Block> for Runtime {
 		fn on_runtime_upgrade(checks: frame_try_runtime::UpgradeCheckSelect) -> (Weight, Weight) {
diff --git a/substrate/bin/node/testing/src/genesis.rs b/substrate/bin/node/testing/src/genesis.rs
index 7f5364744c667f711a5e2c32e20636e904ee02c2..0394f6cd7394d3715c268229ba254053d6de5f53 100644
--- a/substrate/bin/node/testing/src/genesis.rs
+++ b/substrate/bin/node/testing/src/genesis.rs
@@ -38,9 +38,9 @@ pub fn config_endowed(extra_endowed: Vec<AccountId>) -> RuntimeGenesisConfig {
 		(alice(), 111 * DOLLARS),
 		(bob(), 100 * DOLLARS),
 		(charlie(), 100_000_000 * DOLLARS),
-		(dave(), 111 * DOLLARS),
+		(dave(), 112 * DOLLARS),
 		(eve(), 101 * DOLLARS),
-		(ferdie(), 100 * DOLLARS),
+		(ferdie(), 101 * DOLLARS),
 	];
 
 	endowed.extend(extra_endowed.into_iter().map(|endowed| (endowed, 100 * DOLLARS)));
diff --git a/substrate/client/network/src/litep2p/discovery.rs b/substrate/client/network/src/litep2p/discovery.rs
index b55df374f60ecc71959327c063c86b21e960f0b3..eb571804f30e61841ace4f985fe7a439bd9a43b7 100644
--- a/substrate/client/network/src/litep2p/discovery.rs
+++ b/substrate/client/network/src/litep2p/discovery.rs
@@ -33,8 +33,8 @@ use litep2p::{
 			identify::{Config as IdentifyConfig, IdentifyEvent},
 			kademlia::{
 				Config as KademliaConfig, ConfigBuilder as KademliaConfigBuilder, ContentProvider,
-				IncomingRecordValidationMode, KademliaEvent, KademliaHandle, QueryId, Quorum,
-				Record, RecordKey, RecordsType,
+				IncomingRecordValidationMode, KademliaEvent, KademliaHandle, PeerRecord, QueryId,
+				Quorum, Record, RecordKey,
 			},
 			ping::{Config as PingConfig, PingEvent},
 		},
@@ -129,13 +129,19 @@ pub enum DiscoveryEvent {
 		address: Multiaddr,
 	},
 
-	/// Record was found from the DHT.
+	/// `GetRecord` query succeeded.
 	GetRecordSuccess {
 		/// Query ID.
 		query_id: QueryId,
+	},
 
-		/// Records.
-		records: RecordsType,
+	/// Record was found from the DHT.
+	GetRecordPartialResult {
+		/// Query ID.
+		query_id: QueryId,
+
+		/// Record.
+		record: PeerRecord,
 	},
 
 	/// Record was successfully stored on the DHT.
@@ -573,13 +579,24 @@ impl Stream for Discovery {
 					peers: peers.into_iter().collect(),
 				}))
 			},
-			Poll::Ready(Some(KademliaEvent::GetRecordSuccess { query_id, records })) => {
+			Poll::Ready(Some(KademliaEvent::GetRecordSuccess { query_id })) => {
 				log::trace!(
 					target: LOG_TARGET,
-					"`GET_RECORD` succeeded for {query_id:?}: {records:?}",
+					"`GET_RECORD` succeeded for {query_id:?}",
 				);
 
-				return Poll::Ready(Some(DiscoveryEvent::GetRecordSuccess { query_id, records }));
+				return Poll::Ready(Some(DiscoveryEvent::GetRecordSuccess { query_id }));
+			},
+			Poll::Ready(Some(KademliaEvent::GetRecordPartialResult { query_id, record })) => {
+				log::trace!(
+					target: LOG_TARGET,
+					"`GET_RECORD` intermediary succeeded for {query_id:?}: {record:?}",
+				);
+
+				return Poll::Ready(Some(DiscoveryEvent::GetRecordPartialResult {
+					query_id,
+					record,
+				}));
 			},
 			Poll::Ready(Some(KademliaEvent::PutRecordSuccess { query_id, key: _ })) =>
 				return Poll::Ready(Some(DiscoveryEvent::PutRecordSuccess { query_id })),
diff --git a/substrate/client/network/src/litep2p/mod.rs b/substrate/client/network/src/litep2p/mod.rs
index 52b2970525df1e0a58c9daf409a2e65355fdea45..fc4cce47628396bd22c8785edd18227c27a7326a 100644
--- a/substrate/client/network/src/litep2p/mod.rs
+++ b/substrate/client/network/src/litep2p/mod.rs
@@ -58,7 +58,7 @@ use litep2p::{
 	protocol::{
 		libp2p::{
 			bitswap::Config as BitswapConfig,
-			kademlia::{QueryId, Record, RecordsType},
+			kademlia::{QueryId, Record},
 		},
 		request_response::ConfigBuilder as RequestResponseConfigBuilder,
 	},
@@ -836,23 +836,45 @@ impl<B: BlockT + 'static, H: ExHashT> NetworkBackend<B, H> for Litep2pNetworkBac
 							self.peerstore_handle.add_known_peer(peer.into());
 						}
 					}
-					Some(DiscoveryEvent::GetRecordSuccess { query_id, records }) => {
+					Some(DiscoveryEvent::GetRecordPartialResult { query_id, record }) => {
+						if !self.pending_queries.contains_key(&query_id) {
+							log::error!(
+								target: LOG_TARGET,
+								"Missing/invalid pending query for `GET_VALUE` partial result: {query_id:?}"
+							);
+
+							continue
+						}
+
+						let peer_id: sc_network_types::PeerId = record.peer.into();
+						let record = PeerRecord {
+							record: P2PRecord {
+								key: record.record.key.to_vec().into(),
+								value: record.record.value,
+								publisher: record.record.publisher.map(|peer_id| {
+									let peer_id: sc_network_types::PeerId = peer_id.into();
+									peer_id.into()
+								}),
+								expires: record.record.expires,
+							},
+							peer: Some(peer_id.into()),
+						};
+
+						self.event_streams.send(
+							Event::Dht(
+								DhtEvent::ValueFound(
+									record.into()
+								)
+							)
+						);
+					}
+					Some(DiscoveryEvent::GetRecordSuccess { query_id }) => {
 						match self.pending_queries.remove(&query_id) {
 							Some(KadQuery::GetValue(key, started)) => {
 								log::trace!(
 									target: LOG_TARGET,
-									"`GET_VALUE` for {:?} ({query_id:?}) succeeded",
-									key,
+									"`GET_VALUE` for {key:?} ({query_id:?}) succeeded",
 								);
-								for record in litep2p_to_libp2p_peer_record(records) {
-									self.event_streams.send(
-										Event::Dht(
-											DhtEvent::ValueFound(
-												record.into()
-											)
-										)
-									);
-								}
 
 								if let Some(ref metrics) = self.metrics {
 									metrics
@@ -1165,42 +1187,3 @@ impl<B: BlockT + 'static, H: ExHashT> NetworkBackend<B, H> for Litep2pNetworkBac
 		}
 	}
 }
-
-// Glue code to convert from a litep2p records type to a libp2p2 PeerRecord.
-fn litep2p_to_libp2p_peer_record(records: RecordsType) -> Vec<PeerRecord> {
-	match records {
-		litep2p::protocol::libp2p::kademlia::RecordsType::LocalStore(record) => {
-			vec![PeerRecord {
-				record: P2PRecord {
-					key: record.key.to_vec().into(),
-					value: record.value,
-					publisher: record.publisher.map(|peer_id| {
-						let peer_id: sc_network_types::PeerId = peer_id.into();
-						peer_id.into()
-					}),
-					expires: record.expires,
-				},
-				peer: None,
-			}]
-		},
-		litep2p::protocol::libp2p::kademlia::RecordsType::Network(records) => records
-			.into_iter()
-			.map(|record| {
-				let peer_id: sc_network_types::PeerId = record.peer.into();
-
-				PeerRecord {
-					record: P2PRecord {
-						key: record.record.key.to_vec().into(),
-						value: record.record.value,
-						publisher: record.record.publisher.map(|peer_id| {
-							let peer_id: sc_network_types::PeerId = peer_id.into();
-							peer_id.into()
-						}),
-						expires: record.record.expires,
-					},
-					peer: Some(peer_id.into()),
-				}
-			})
-			.collect::<Vec<_>>(),
-	}
-}
diff --git a/substrate/frame/asset-rewards/Cargo.toml b/substrate/frame/asset-rewards/Cargo.toml
new file mode 100644
index 0000000000000000000000000000000000000000..a03fa17cf0dc0e1911dadab39a4dbadbb542a372
--- /dev/null
+++ b/substrate/frame/asset-rewards/Cargo.toml
@@ -0,0 +1,71 @@
+[package]
+name = "pallet-asset-rewards"
+version = "0.1.0"
+authors.workspace = true
+edition.workspace = true
+license = "Apache-2.0"
+homepage.workspace = true
+repository.workspace = true
+description = "FRAME asset rewards pallet"
+
+[lints]
+workspace = true
+
+[package.metadata.docs.rs]
+targets = ["x86_64-unknown-linux-gnu"]
+
+[dependencies]
+codec = { workspace = true }
+frame-benchmarking = { workspace = true, optional = true }
+frame-support = { workspace = true, features = ["experimental"] }
+frame-system = { workspace = true }
+scale-info = { workspace = true, features = ["derive"] }
+sp-api = { workspace = true }
+sp-arithmetic = { workspace = true }
+sp-core = { workspace = true }
+sp-io = { workspace = true }
+sp-runtime = { workspace = true }
+sp-std = { workspace = true }
+
+[dev-dependencies]
+pallet-assets = { workspace = true }
+pallet-assets-freezer = { workspace = true }
+pallet-balances = { workspace = true }
+primitive-types = { workspace = true, features = ["codec", "num-traits", "scale-info"] }
+
+[features]
+default = ["std"]
+std = [
+	"codec/std",
+	"frame-benchmarking?/std",
+	"frame-support/std",
+	"frame-system/std",
+	"pallet-assets-freezer/std",
+	"pallet-assets/std",
+	"pallet-balances/std",
+	"primitive-types/std",
+	"scale-info/std",
+	"sp-api/std",
+	"sp-arithmetic/std",
+	"sp-core/std",
+	"sp-io/std",
+	"sp-runtime/std",
+	"sp-std/std",
+]
+runtime-benchmarks = [
+	"frame-benchmarking/runtime-benchmarks",
+	"frame-support/runtime-benchmarks",
+	"frame-system/runtime-benchmarks",
+	"pallet-assets-freezer/runtime-benchmarks",
+	"pallet-assets/runtime-benchmarks",
+	"pallet-balances/runtime-benchmarks",
+	"sp-runtime/runtime-benchmarks",
+]
+try-runtime = [
+	"frame-support/try-runtime",
+	"frame-system/try-runtime",
+	"pallet-assets-freezer/try-runtime",
+	"pallet-assets/try-runtime",
+	"pallet-balances/try-runtime",
+	"sp-runtime/try-runtime",
+]
diff --git a/substrate/frame/asset-rewards/src/benchmarking.rs b/substrate/frame/asset-rewards/src/benchmarking.rs
new file mode 100644
index 0000000000000000000000000000000000000000..5605804dd20ece8eb27509de9bb4fe44d0037ab3
--- /dev/null
+++ b/substrate/frame/asset-rewards/src/benchmarking.rs
@@ -0,0 +1,355 @@
+// This file is part of Substrate.
+
+// Copyright (C) Parity Technologies (UK) Ltd.
+// SPDX-License-Identifier: Apache-2.0
+
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// 	http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+//! Asset Rewards pallet benchmarking.
+
+use super::*;
+use crate::Pallet as AssetRewards;
+use frame_benchmarking::{v2::*, whitelisted_caller, BenchmarkError};
+use frame_support::{
+	assert_ok,
+	traits::{
+		fungibles::{Create, Inspect, Mutate},
+		Consideration, EnsureOrigin, Footprint,
+	},
+};
+use frame_system::{pallet_prelude::BlockNumberFor, Pallet as System, RawOrigin};
+use sp_runtime::{traits::One, Saturating};
+use sp_std::prelude::*;
+
+/// Benchmark Helper
+pub trait BenchmarkHelper<AssetId> {
+	/// Returns the staked asset id.
+	///
+	/// If the asset does not exist, it will be created by the benchmark.
+	fn staked_asset() -> AssetId;
+	/// Returns the reward asset id.
+	///
+	/// If the asset does not exist, it will be created by the benchmark.
+	fn reward_asset() -> AssetId;
+}
+
+fn pool_expire<T: Config>() -> DispatchTime<BlockNumberFor<T>> {
+	DispatchTime::At(BlockNumberFor::<T>::from(100u32))
+}
+
+fn create_reward_pool<T: Config>() -> Result<T::RuntimeOrigin, BenchmarkError>
+where
+	T::Assets: Create<T::AccountId> + Mutate<T::AccountId>,
+{
+	let caller_origin =
+		T::CreatePoolOrigin::try_successful_origin().map_err(|_| BenchmarkError::Weightless)?;
+	let caller = T::CreatePoolOrigin::ensure_origin(caller_origin.clone()).unwrap();
+
+	let footprint = Footprint::from_mel::<(PoolId, PoolInfoFor<T>)>();
+	T::Consideration::ensure_successful(&caller, footprint);
+
+	let staked_asset = T::BenchmarkHelper::staked_asset();
+	let reward_asset = T::BenchmarkHelper::reward_asset();
+
+	let min_staked_balance =
+		T::Assets::minimum_balance(staked_asset.clone()).max(T::Balance::one());
+	if !T::Assets::asset_exists(staked_asset.clone()) {
+		assert_ok!(T::Assets::create(
+			staked_asset.clone(),
+			caller.clone(),
+			true,
+			min_staked_balance
+		));
+	}
+	let min_reward_balance =
+		T::Assets::minimum_balance(reward_asset.clone()).max(T::Balance::one());
+	if !T::Assets::asset_exists(reward_asset.clone()) {
+		assert_ok!(T::Assets::create(
+			reward_asset.clone(),
+			caller.clone(),
+			true,
+			min_reward_balance
+		));
+	}
+
+	assert_ok!(AssetRewards::<T>::create_pool(
+		caller_origin.clone(),
+		Box::new(staked_asset),
+		Box::new(reward_asset),
+		// reward rate per block
+		min_reward_balance,
+		pool_expire::<T>(),
+		Some(caller),
+	));
+
+	Ok(caller_origin)
+}
+
+fn mint_into<T: Config>(caller: &T::AccountId, asset: &T::AssetId) -> T::Balance
+where
+	T::Assets: Mutate<T::AccountId>,
+{
+	let min_balance = T::Assets::minimum_balance(asset.clone());
+	assert_ok!(T::Assets::mint_into(
+		asset.clone(),
+		&caller,
+		min_balance.saturating_mul(10u32.into())
+	));
+	min_balance
+}
+
+fn assert_last_event<T: Config>(generic_event: <T as Config>::RuntimeEvent) {
+	System::<T>::assert_last_event(generic_event.into());
+}
+
+#[benchmarks(where T::Assets: Create<T::AccountId> + Mutate<T::AccountId>)]
+mod benchmarks {
+	use super::*;
+
+	#[benchmark]
+	fn create_pool() -> Result<(), BenchmarkError> {
+		let caller_origin =
+			T::CreatePoolOrigin::try_successful_origin().map_err(|_| BenchmarkError::Weightless)?;
+		let caller = T::CreatePoolOrigin::ensure_origin(caller_origin.clone()).unwrap();
+
+		let footprint = Footprint::from_mel::<(PoolId, PoolInfoFor<T>)>();
+		T::Consideration::ensure_successful(&caller, footprint);
+
+		let staked_asset = T::BenchmarkHelper::staked_asset();
+		let reward_asset = T::BenchmarkHelper::reward_asset();
+
+		let min_balance = T::Assets::minimum_balance(staked_asset.clone()).max(T::Balance::one());
+		if !T::Assets::asset_exists(staked_asset.clone()) {
+			assert_ok!(T::Assets::create(staked_asset.clone(), caller.clone(), true, min_balance));
+		}
+		let min_balance = T::Assets::minimum_balance(reward_asset.clone()).max(T::Balance::one());
+		if !T::Assets::asset_exists(reward_asset.clone()) {
+			assert_ok!(T::Assets::create(reward_asset.clone(), caller.clone(), true, min_balance));
+		}
+
+		#[extrinsic_call]
+		_(
+			caller_origin as T::RuntimeOrigin,
+			Box::new(staked_asset.clone()),
+			Box::new(reward_asset.clone()),
+			min_balance,
+			pool_expire::<T>(),
+			Some(caller.clone()),
+		);
+
+		assert_last_event::<T>(
+			Event::PoolCreated {
+				creator: caller.clone(),
+				admin: caller,
+				staked_asset_id: staked_asset,
+				reward_asset_id: reward_asset,
+				reward_rate_per_block: min_balance,
+				expiry_block: pool_expire::<T>().evaluate(System::<T>::block_number()),
+				pool_id: 0,
+			}
+			.into(),
+		);
+
+		Ok(())
+	}
+
+	#[benchmark]
+	fn stake() -> Result<(), BenchmarkError> {
+		create_reward_pool::<T>()?;
+
+		let staker: T::AccountId = whitelisted_caller();
+		let min_balance = mint_into::<T>(&staker, &T::BenchmarkHelper::staked_asset());
+
+		// stake first to get worth case benchmark.
+		assert_ok!(AssetRewards::<T>::stake(
+			RawOrigin::Signed(staker.clone()).into(),
+			0,
+			min_balance
+		));
+
+		#[extrinsic_call]
+		_(RawOrigin::Signed(staker.clone()), 0, min_balance);
+
+		assert_last_event::<T>(Event::Staked { staker, pool_id: 0, amount: min_balance }.into());
+
+		Ok(())
+	}
+
+	#[benchmark]
+	fn unstake() -> Result<(), BenchmarkError> {
+		create_reward_pool::<T>()?;
+
+		let staker: T::AccountId = whitelisted_caller();
+		let min_balance = mint_into::<T>(&staker, &T::BenchmarkHelper::staked_asset());
+
+		assert_ok!(AssetRewards::<T>::stake(
+			RawOrigin::Signed(staker.clone()).into(),
+			0,
+			min_balance,
+		));
+
+		#[extrinsic_call]
+		_(RawOrigin::Signed(staker.clone()), 0, min_balance, None);
+
+		assert_last_event::<T>(
+			Event::Unstaked { caller: staker.clone(), staker, pool_id: 0, amount: min_balance }
+				.into(),
+		);
+
+		Ok(())
+	}
+
+	#[benchmark]
+	fn harvest_rewards() -> Result<(), BenchmarkError> {
+		create_reward_pool::<T>()?;
+
+		let pool_acc = AssetRewards::<T>::pool_account_id(&0u32);
+		let min_reward_balance = mint_into::<T>(&pool_acc, &T::BenchmarkHelper::reward_asset());
+
+		let staker = whitelisted_caller();
+		let _ = mint_into::<T>(&staker, &T::BenchmarkHelper::staked_asset());
+		assert_ok!(AssetRewards::<T>::stake(
+			RawOrigin::Signed(staker.clone()).into(),
+			0,
+			T::Balance::one(),
+		));
+
+		System::<T>::set_block_number(System::<T>::block_number() + BlockNumberFor::<T>::one());
+
+		#[extrinsic_call]
+		_(RawOrigin::Signed(staker.clone()), 0, None);
+
+		assert_last_event::<T>(
+			Event::RewardsHarvested {
+				caller: staker.clone(),
+				staker,
+				pool_id: 0,
+				amount: min_reward_balance,
+			}
+			.into(),
+		);
+
+		Ok(())
+	}
+
+	#[benchmark]
+	fn set_pool_reward_rate_per_block() -> Result<(), BenchmarkError> {
+		let caller_origin = create_reward_pool::<T>()?;
+
+		// stake first to get worth case benchmark.
+		{
+			let staker: T::AccountId = whitelisted_caller();
+			let min_balance = mint_into::<T>(&staker, &T::BenchmarkHelper::staked_asset());
+
+			assert_ok!(AssetRewards::<T>::stake(RawOrigin::Signed(staker).into(), 0, min_balance));
+		}
+
+		let new_reward_rate_per_block =
+			T::Assets::minimum_balance(T::BenchmarkHelper::reward_asset()).max(T::Balance::one()) +
+				T::Balance::one();
+
+		#[extrinsic_call]
+		_(caller_origin as T::RuntimeOrigin, 0, new_reward_rate_per_block);
+
+		assert_last_event::<T>(
+			Event::PoolRewardRateModified { pool_id: 0, new_reward_rate_per_block }.into(),
+		);
+		Ok(())
+	}
+
+	#[benchmark]
+	fn set_pool_admin() -> Result<(), BenchmarkError> {
+		let caller_origin = create_reward_pool::<T>()?;
+		let new_admin: T::AccountId = whitelisted_caller();
+
+		#[extrinsic_call]
+		_(caller_origin as T::RuntimeOrigin, 0, new_admin.clone());
+
+		assert_last_event::<T>(Event::PoolAdminModified { pool_id: 0, new_admin }.into());
+
+		Ok(())
+	}
+
+	#[benchmark]
+	fn set_pool_expiry_block() -> Result<(), BenchmarkError> {
+		let create_origin = create_reward_pool::<T>()?;
+
+		// stake first to get worth case benchmark.
+		{
+			let staker: T::AccountId = whitelisted_caller();
+			let min_balance = mint_into::<T>(&staker, &T::BenchmarkHelper::staked_asset());
+
+			assert_ok!(AssetRewards::<T>::stake(RawOrigin::Signed(staker).into(), 0, min_balance));
+		}
+
+		let new_expiry_block =
+			pool_expire::<T>().evaluate(System::<T>::block_number()) + BlockNumberFor::<T>::one();
+
+		#[extrinsic_call]
+		_(create_origin as T::RuntimeOrigin, 0, DispatchTime::At(new_expiry_block));
+
+		assert_last_event::<T>(
+			Event::PoolExpiryBlockModified { pool_id: 0, new_expiry_block }.into(),
+		);
+
+		Ok(())
+	}
+
+	#[benchmark]
+	fn deposit_reward_tokens() -> Result<(), BenchmarkError> {
+		create_reward_pool::<T>()?;
+		let caller = whitelisted_caller();
+
+		let reward_asset = T::BenchmarkHelper::reward_asset();
+		let pool_acc = AssetRewards::<T>::pool_account_id(&0u32);
+		let min_balance = mint_into::<T>(&caller, &reward_asset);
+
+		let balance_before = T::Assets::balance(reward_asset.clone(), &pool_acc);
+
+		#[extrinsic_call]
+		_(RawOrigin::Signed(caller), 0, min_balance);
+
+		let balance_after = T::Assets::balance(reward_asset, &pool_acc);
+
+		assert_eq!(balance_after, balance_before + min_balance);
+
+		Ok(())
+	}
+
+	#[benchmark]
+	fn cleanup_pool() -> Result<(), BenchmarkError> {
+		let create_origin = create_reward_pool::<T>()?;
+		let caller = T::CreatePoolOrigin::ensure_origin(create_origin.clone()).unwrap();
+
+		// deposit rewards tokens to get worth case benchmark.
+		{
+			let caller = whitelisted_caller();
+			let reward_asset = T::BenchmarkHelper::reward_asset();
+			let min_balance = mint_into::<T>(&caller, &reward_asset);
+			assert_ok!(AssetRewards::<T>::deposit_reward_tokens(
+				RawOrigin::Signed(caller).into(),
+				0,
+				min_balance
+			));
+		}
+
+		#[extrinsic_call]
+		_(RawOrigin::Signed(caller), 0);
+
+		assert_last_event::<T>(Event::PoolCleanedUp { pool_id: 0 }.into());
+
+		Ok(())
+	}
+
+	impl_benchmark_test_suite!(AssetRewards, crate::mock::new_test_ext(), crate::mock::MockRuntime);
+}
diff --git a/substrate/frame/asset-rewards/src/lib.rs b/substrate/frame/asset-rewards/src/lib.rs
new file mode 100644
index 0000000000000000000000000000000000000000..4ce73e9febf96f8c363ffa9203d0209f3b599d88
--- /dev/null
+++ b/substrate/frame/asset-rewards/src/lib.rs
@@ -0,0 +1,905 @@
+// This file is part of Substrate.
+
+// Copyright (C) Parity Technologies (UK) Ltd.
+// SPDX-License-Identifier: Apache-2.0
+
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// 	http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+//! # FRAME Staking Rewards Pallet
+//!
+//! Allows accounts to be rewarded for holding `fungible` asset/s, for example LP tokens.
+//!
+//! ## Overview
+//!
+//! Initiate an incentive program for a fungible asset by creating a new pool.
+//!
+//! During pool creation, a 'staking asset', 'reward asset', 'reward rate per block', 'expiry
+//! block', and 'admin' are specified.
+//!
+//! Once created, holders of the 'staking asset' can 'stake' them in a corresponding pool, which
+//! creates a Freeze on the asset.
+//!
+//! Once staked, rewards denominated in 'reward asset' begin accumulating to the staker,
+//! proportional to their share of the total staked tokens in the pool.
+//!
+//! Reward assets pending distribution are held in an account unique to each pool.
+//!
+//! Care should be taken by the pool operator to keep pool accounts adequately funded with the
+//! reward asset.
+//!
+//! The pool admin may increase reward rate per block, increase expiry block, and change admin.
+//!
+//! ## Disambiguation
+//!
+//! While this pallet shares some terminology with the `staking-pool` and similar native staking
+//! related pallets, it is distinct and is entirely unrelated to native staking.
+//!
+//! ## Permissioning
+//!
+//! Currently, pool creation and management restricted to a configured Origin.
+//!
+//! Future iterations of this pallet may allow permissionless creation and management of pools.
+//!
+//! Note: The permissioned origin must return an AccountId. This can be achieved for any Origin by
+//! wrapping it with `EnsureSuccess`.
+//!
+//! ## Implementation Notes
+//!
+//! Internal logic functions such as `update_pool_and_staker_rewards` were deliberately written
+//! without side-effects.
+//!
+//! Storage interaction such as reads and writes are instead all performed in the top level
+//! pallet Call method, which while slightly more verbose, makes it easier to understand the
+//! code and reason about how storage reads and writes occur in the pallet.
+//!
+//! ## Rewards Algorithm
+//!
+//! The rewards algorithm is based on the Synthetix [StakingRewards.sol](https://github.com/Synthetixio/synthetix/blob/develop/contracts/StakingRewards.sol)
+//! smart contract.
+//!
+//! Rewards are calculated JIT (just-in-time), and all operations are O(1) making the approach
+//! scalable to many pools and stakers.
+//!
+//! ### Resources
+//!
+//! - [This video series](https://www.youtube.com/watch?v=6ZO5aYg1GI8), which walks through the math
+//!   of the algorithm.
+//! - [This dev.to article](https://dev.to/heymarkkop/understanding-sushiswaps-masterchef-staking-rewards-1m6f),
+//!   which explains the algorithm of the SushiSwap MasterChef staking. While not identical to the
+//!   Synthetix approach, they are quite similar.
+#![deny(missing_docs)]
+#![cfg_attr(not(feature = "std"), no_std)]
+
+pub use pallet::*;
+
+use codec::{Codec, Decode, Encode, MaxEncodedLen};
+use frame_support::{
+	traits::{
+		fungibles::{Inspect, Mutate},
+		schedule::DispatchTime,
+		tokens::Balance,
+	},
+	PalletId,
+};
+use frame_system::pallet_prelude::BlockNumberFor;
+use scale_info::TypeInfo;
+use sp_core::Get;
+use sp_runtime::{
+	traits::{MaybeDisplay, Zero},
+	DispatchError,
+};
+use sp_std::boxed::Box;
+
+#[cfg(feature = "runtime-benchmarks")]
+pub mod benchmarking;
+#[cfg(test)]
+mod mock;
+#[cfg(test)]
+mod tests;
+mod weights;
+
+pub use weights::WeightInfo;
+
+/// Unique id type for each pool.
+pub type PoolId = u32;
+
+/// Multiplier to maintain precision when calculating rewards.
+pub(crate) const PRECISION_SCALING_FACTOR: u16 = 4096;
+
+/// Convenience alias for `PoolInfo`.
+pub type PoolInfoFor<T> = PoolInfo<
+	<T as frame_system::Config>::AccountId,
+	<T as Config>::AssetId,
+	<T as Config>::Balance,
+	BlockNumberFor<T>,
+>;
+
+/// The state of a staker in a pool.
+#[derive(Debug, Default, Clone, Decode, Encode, MaxEncodedLen, TypeInfo)]
+pub struct PoolStakerInfo<Balance> {
+	/// Amount of tokens staked.
+	amount: Balance,
+	/// Accumulated, unpaid rewards.
+	rewards: Balance,
+	/// Reward per token value at the time of the staker's last interaction with the contract.
+	reward_per_token_paid: Balance,
+}
+
+/// The state and configuration of an incentive pool.
+#[derive(Debug, Clone, Decode, Encode, Default, PartialEq, Eq, MaxEncodedLen, TypeInfo)]
+pub struct PoolInfo<AccountId, AssetId, Balance, BlockNumber> {
+	/// The asset staked in this pool.
+	staked_asset_id: AssetId,
+	/// The asset distributed as rewards by this pool.
+	reward_asset_id: AssetId,
+	/// The amount of tokens rewarded per block.
+	reward_rate_per_block: Balance,
+	/// The block the pool will cease distributing rewards.
+	expiry_block: BlockNumber,
+	/// The account authorized to manage this pool.
+	admin: AccountId,
+	/// The total amount of tokens staked in this pool.
+	total_tokens_staked: Balance,
+	/// Total rewards accumulated per token, up to the `last_update_block`.
+	reward_per_token_stored: Balance,
+	/// Last block number the pool was updated.
+	last_update_block: BlockNumber,
+	/// The account that holds the pool's rewards.
+	account: AccountId,
+}
+
+sp_api::decl_runtime_apis! {
+	/// The runtime API for the asset rewards pallet.
+	pub trait AssetRewards<Cost: MaybeDisplay + Codec> {
+		/// Get the cost of creating a pool.
+		///
+		/// This is especially useful when the cost is dynamic.
+		fn pool_creation_cost() -> Cost;
+	}
+}
+
+#[frame_support::pallet]
+pub mod pallet {
+	use super::*;
+	use frame_support::{
+		pallet_prelude::*,
+		traits::{
+			fungibles::MutateFreeze,
+			tokens::{AssetId, Fortitude, Preservation},
+			Consideration, Footprint,
+		},
+	};
+	use frame_system::pallet_prelude::*;
+	use sp_runtime::{
+		traits::{
+			AccountIdConversion, BadOrigin, EnsureAdd, EnsureAddAssign, EnsureDiv, EnsureMul,
+			EnsureSub, EnsureSubAssign,
+		},
+		DispatchResult,
+	};
+
+	#[pallet::pallet]
+	pub struct Pallet<T>(_);
+
+	/// A reason for the pallet placing a hold on funds.
+	#[pallet::composite_enum]
+	pub enum FreezeReason {
+		/// Funds are staked in the pallet.
+		#[codec(index = 0)]
+		Staked,
+	}
+
+	/// A reason for the pallet placing a hold on funds.
+	#[pallet::composite_enum]
+	pub enum HoldReason {
+		/// Cost associated with storing pool information on-chain.
+		#[codec(index = 0)]
+		PoolCreation,
+	}
+
+	#[pallet::config]
+	pub trait Config: frame_system::Config {
+		/// Overarching event type.
+		type RuntimeEvent: From<Event<Self>> + IsType<<Self as frame_system::Config>::RuntimeEvent>;
+
+		/// The pallet's unique identifier, used to derive the pool's account ID.
+		///
+		/// The account ID is derived once during pool creation and stored in the storage.
+		#[pallet::constant]
+		type PalletId: Get<PalletId>;
+
+		/// Identifier for each type of asset.
+		type AssetId: AssetId + Member + Parameter;
+
+		/// The type in which the assets are measured.
+		type Balance: Balance + TypeInfo;
+
+		/// The origin with permission to create pools.
+		///
+		/// The Origin must return an AccountId.
+		type CreatePoolOrigin: EnsureOrigin<Self::RuntimeOrigin, Success = Self::AccountId>;
+
+		/// Registry of assets that can be configured to either stake for rewards, or be offered as
+		/// rewards for staking.
+		type Assets: Inspect<Self::AccountId, AssetId = Self::AssetId, Balance = Self::Balance>
+			+ Mutate<Self::AccountId>;
+
+		/// Freezer for the Assets.
+		type AssetsFreezer: MutateFreeze<
+			Self::AccountId,
+			Id = Self::RuntimeFreezeReason,
+			AssetId = Self::AssetId,
+			Balance = Self::Balance,
+		>;
+
+		/// The overarching freeze reason.
+		type RuntimeFreezeReason: From<FreezeReason>;
+
+		/// Means for associating a cost with the on-chain storage of pool information, which
+		/// is incurred by the pool creator.
+		///
+		/// The passed `Footprint` specifically accounts for the storage footprint of the pool's
+		/// information itself, excluding any potential storage footprint related to the stakers.
+		type Consideration: Consideration<Self::AccountId, Footprint>;
+
+		/// Weight information for extrinsics in this pallet.
+		type WeightInfo: WeightInfo;
+
+		/// Helper for benchmarking.
+		#[cfg(feature = "runtime-benchmarks")]
+		type BenchmarkHelper: benchmarking::BenchmarkHelper<Self::AssetId>;
+	}
+
+	/// State of pool stakers.
+	#[pallet::storage]
+	pub type PoolStakers<T: Config> = StorageDoubleMap<
+		_,
+		Blake2_128Concat,
+		PoolId,
+		Blake2_128Concat,
+		T::AccountId,
+		PoolStakerInfo<T::Balance>,
+	>;
+
+	/// State and configuration of each staking pool.
+	#[pallet::storage]
+	pub type Pools<T: Config> = StorageMap<_, Blake2_128Concat, PoolId, PoolInfoFor<T>>;
+
+	/// The cost associated with storing pool information on-chain which was incurred by the pool
+	/// creator.
+	///
+	/// This cost may be [`None`], as determined by [`Config::Consideration`].
+	#[pallet::storage]
+	pub type PoolCost<T: Config> =
+		StorageMap<_, Blake2_128Concat, PoolId, (T::AccountId, T::Consideration)>;
+
+	/// Stores the [`PoolId`] to use for the next pool.
+	///
+	/// Incremented when a new pool is created.
+	#[pallet::storage]
+	pub type NextPoolId<T: Config> = StorageValue<_, PoolId, ValueQuery>;
+
+	#[pallet::event]
+	#[pallet::generate_deposit(pub(super) fn deposit_event)]
+	pub enum Event<T: Config> {
+		/// An account staked some tokens in a pool.
+		Staked {
+			/// The account that staked assets.
+			staker: T::AccountId,
+			/// The pool.
+			pool_id: PoolId,
+			/// The staked asset amount.
+			amount: T::Balance,
+		},
+		/// An account unstaked some tokens from a pool.
+		Unstaked {
+			/// The account that signed transaction.
+			caller: T::AccountId,
+			/// The account that unstaked assets.
+			staker: T::AccountId,
+			/// The pool.
+			pool_id: PoolId,
+			/// The unstaked asset amount.
+			amount: T::Balance,
+		},
+		/// An account harvested some rewards.
+		RewardsHarvested {
+			/// The account that signed transaction.
+			caller: T::AccountId,
+			/// The staker whos rewards were harvested.
+			staker: T::AccountId,
+			/// The pool.
+			pool_id: PoolId,
+			/// The amount of harvested tokens.
+			amount: T::Balance,
+		},
+		/// A new reward pool was created.
+		PoolCreated {
+			/// The account that created the pool.
+			creator: T::AccountId,
+			/// The unique ID for the new pool.
+			pool_id: PoolId,
+			/// The staking asset.
+			staked_asset_id: T::AssetId,
+			/// The reward asset.
+			reward_asset_id: T::AssetId,
+			/// The initial reward rate per block.
+			reward_rate_per_block: T::Balance,
+			/// The block the pool will cease to accumulate rewards.
+			expiry_block: BlockNumberFor<T>,
+			/// The account allowed to modify the pool.
+			admin: T::AccountId,
+		},
+		/// A pool reward rate was modified by the admin.
+		PoolRewardRateModified {
+			/// The modified pool.
+			pool_id: PoolId,
+			/// The new reward rate per block.
+			new_reward_rate_per_block: T::Balance,
+		},
+		/// A pool admin was modified.
+		PoolAdminModified {
+			/// The modified pool.
+			pool_id: PoolId,
+			/// The new admin.
+			new_admin: T::AccountId,
+		},
+		/// A pool expiry block was modified by the admin.
+		PoolExpiryBlockModified {
+			/// The modified pool.
+			pool_id: PoolId,
+			/// The new expiry block.
+			new_expiry_block: BlockNumberFor<T>,
+		},
+		/// A pool information was cleared after it's completion.
+		PoolCleanedUp {
+			/// The cleared pool.
+			pool_id: PoolId,
+		},
+	}
+
+	#[pallet::error]
+	pub enum Error<T> {
+		/// The staker does not have enough tokens to perform the operation.
+		NotEnoughTokens,
+		/// An operation was attempted on a non-existent pool.
+		NonExistentPool,
+		/// An operation was attempted for a non-existent staker.
+		NonExistentStaker,
+		/// An operation was attempted with a non-existent asset.
+		NonExistentAsset,
+		/// There was an error converting a block number.
+		BlockNumberConversionError,
+		/// The expiry block must be in the future.
+		ExpiryBlockMustBeInTheFuture,
+		/// Insufficient funds to create the freeze.
+		InsufficientFunds,
+		/// The expiry block can be only extended.
+		ExpiryCut,
+		/// The reward rate per block can be only increased.
+		RewardRateCut,
+		/// The pool still has staked tokens or rewards.
+		NonEmptyPool,
+	}
+
+	#[pallet::hooks]
+	impl<T: Config> Hooks<BlockNumberFor<T>> for Pallet<T> {
+		fn integrity_test() {
+			// The AccountId is at least 16 bytes to contain the unique PalletId.
+			let pool_id: PoolId = 1;
+			assert!(
+				<frame_support::PalletId as AccountIdConversion<T::AccountId>>::try_into_sub_account(
+					&T::PalletId::get(), pool_id,
+				)
+				.is_some()
+			);
+		}
+	}
+
+	/// Pallet's callable functions.
+	#[pallet::call(weight(<T as Config>::WeightInfo))]
+	impl<T: Config> Pallet<T> {
+		/// Create a new reward pool.
+		///
+		/// Parameters:
+		/// - `origin`: must be `Config::CreatePoolOrigin`;
+		/// - `staked_asset_id`: the asset to be staked in the pool;
+		/// - `reward_asset_id`: the asset to be distributed as rewards;
+		/// - `reward_rate_per_block`: the amount of reward tokens distributed per block;
+		/// - `expiry`: the block number at which the pool will cease to accumulate rewards. The
+		///   [`DispatchTime::After`] variant evaluated at the execution time.
+		/// - `admin`: the account allowed to extend the pool expiration, increase the rewards rate
+		///   and receive the unutilized reward tokens back after the pool completion. If `None`,
+		///   the caller is set as an admin.
+		#[pallet::call_index(0)]
+		pub fn create_pool(
+			origin: OriginFor<T>,
+			staked_asset_id: Box<T::AssetId>,
+			reward_asset_id: Box<T::AssetId>,
+			reward_rate_per_block: T::Balance,
+			expiry: DispatchTime<BlockNumberFor<T>>,
+			admin: Option<T::AccountId>,
+		) -> DispatchResult {
+			// Check the origin.
+			let creator = T::CreatePoolOrigin::ensure_origin(origin)?;
+
+			// Ensure the assets exist.
+			ensure!(
+				T::Assets::asset_exists(*staked_asset_id.clone()),
+				Error::<T>::NonExistentAsset
+			);
+			ensure!(
+				T::Assets::asset_exists(*reward_asset_id.clone()),
+				Error::<T>::NonExistentAsset
+			);
+
+			// Check the expiry block.
+			let expiry_block = expiry.evaluate(frame_system::Pallet::<T>::block_number());
+			ensure!(
+				expiry_block > frame_system::Pallet::<T>::block_number(),
+				Error::<T>::ExpiryBlockMustBeInTheFuture
+			);
+
+			let pool_id = NextPoolId::<T>::get();
+
+			let footprint = Self::pool_creation_footprint();
+			let cost = T::Consideration::new(&creator, footprint)?;
+			PoolCost::<T>::insert(pool_id, (creator.clone(), cost));
+
+			let admin = admin.unwrap_or(creator.clone());
+
+			// Create the pool.
+			let pool = PoolInfoFor::<T> {
+				staked_asset_id: *staked_asset_id.clone(),
+				reward_asset_id: *reward_asset_id.clone(),
+				reward_rate_per_block,
+				total_tokens_staked: 0u32.into(),
+				reward_per_token_stored: 0u32.into(),
+				last_update_block: 0u32.into(),
+				expiry_block,
+				admin: admin.clone(),
+				account: Self::pool_account_id(&pool_id),
+			};
+
+			// Insert it into storage.
+			Pools::<T>::insert(pool_id, pool);
+
+			NextPoolId::<T>::put(pool_id.ensure_add(1)?);
+
+			// Emit created event.
+			Self::deposit_event(Event::PoolCreated {
+				creator,
+				pool_id,
+				staked_asset_id: *staked_asset_id,
+				reward_asset_id: *reward_asset_id,
+				reward_rate_per_block,
+				expiry_block,
+				admin,
+			});
+
+			Ok(())
+		}
+
+		/// Stake additional tokens in a pool.
+		///
+		/// A freeze is placed on the staked tokens.
+		#[pallet::call_index(1)]
+		pub fn stake(origin: OriginFor<T>, pool_id: PoolId, amount: T::Balance) -> DispatchResult {
+			let staker = ensure_signed(origin)?;
+
+			// Always start by updating staker and pool rewards.
+			let pool_info = Pools::<T>::get(pool_id).ok_or(Error::<T>::NonExistentPool)?;
+			let staker_info = PoolStakers::<T>::get(pool_id, &staker).unwrap_or_default();
+			let (mut pool_info, mut staker_info) =
+				Self::update_pool_and_staker_rewards(&pool_info, &staker_info)?;
+
+			T::AssetsFreezer::increase_frozen(
+				pool_info.staked_asset_id.clone(),
+				&FreezeReason::Staked.into(),
+				&staker,
+				amount,
+			)?;
+
+			// Update Pools.
+			pool_info.total_tokens_staked.ensure_add_assign(amount)?;
+
+			Pools::<T>::insert(pool_id, pool_info);
+
+			// Update PoolStakers.
+			staker_info.amount.ensure_add_assign(amount)?;
+			PoolStakers::<T>::insert(pool_id, &staker, staker_info);
+
+			// Emit event.
+			Self::deposit_event(Event::Staked { staker, pool_id, amount });
+
+			Ok(())
+		}
+
+		/// Unstake tokens from a pool.
+		///
+		/// Removes the freeze on the staked tokens.
+		///
+		/// Parameters:
+		/// - origin: must be the `staker` if the pool is still active. Otherwise, any account.
+		/// - pool_id: the pool to unstake from.
+		/// - amount: the amount of tokens to unstake.
+		/// - staker: the account to unstake from. If `None`, the caller is used.
+		#[pallet::call_index(2)]
+		pub fn unstake(
+			origin: OriginFor<T>,
+			pool_id: PoolId,
+			amount: T::Balance,
+			staker: Option<T::AccountId>,
+		) -> DispatchResult {
+			let caller = ensure_signed(origin)?;
+			let staker = staker.unwrap_or(caller.clone());
+
+			// Always start by updating the pool rewards.
+			let pool_info = Pools::<T>::get(pool_id).ok_or(Error::<T>::NonExistentPool)?;
+			let now = frame_system::Pallet::<T>::block_number();
+			ensure!(now > pool_info.expiry_block || caller == staker, BadOrigin);
+
+			let staker_info = PoolStakers::<T>::get(pool_id, &staker).unwrap_or_default();
+			let (mut pool_info, mut staker_info) =
+				Self::update_pool_and_staker_rewards(&pool_info, &staker_info)?;
+
+			// Check the staker has enough staked tokens.
+			ensure!(staker_info.amount >= amount, Error::<T>::NotEnoughTokens);
+
+			// Unfreeze staker assets.
+			T::AssetsFreezer::decrease_frozen(
+				pool_info.staked_asset_id.clone(),
+				&FreezeReason::Staked.into(),
+				&staker,
+				amount,
+			)?;
+
+			// Update Pools.
+			pool_info.total_tokens_staked.ensure_sub_assign(amount)?;
+			Pools::<T>::insert(pool_id, pool_info);
+
+			// Update PoolStakers.
+			staker_info.amount.ensure_sub_assign(amount)?;
+
+			if staker_info.amount.is_zero() && staker_info.rewards.is_zero() {
+				PoolStakers::<T>::remove(&pool_id, &staker);
+			} else {
+				PoolStakers::<T>::insert(&pool_id, &staker, staker_info);
+			}
+
+			// Emit event.
+			Self::deposit_event(Event::Unstaked { caller, staker, pool_id, amount });
+
+			Ok(())
+		}
+
+		/// Harvest unclaimed pool rewards.
+		///
+		/// Parameters:
+		/// - origin: must be the `staker` if the pool is still active. Otherwise, any account.
+		/// - pool_id: the pool to harvest from.
+		/// - staker: the account for which to harvest rewards. If `None`, the caller is used.
+		#[pallet::call_index(3)]
+		pub fn harvest_rewards(
+			origin: OriginFor<T>,
+			pool_id: PoolId,
+			staker: Option<T::AccountId>,
+		) -> DispatchResult {
+			let caller = ensure_signed(origin)?;
+			let staker = staker.unwrap_or(caller.clone());
+
+			// Always start by updating the pool and staker rewards.
+			let pool_info = Pools::<T>::get(pool_id).ok_or(Error::<T>::NonExistentPool)?;
+			let now = frame_system::Pallet::<T>::block_number();
+			ensure!(now > pool_info.expiry_block || caller == staker, BadOrigin);
+
+			let staker_info =
+				PoolStakers::<T>::get(pool_id, &staker).ok_or(Error::<T>::NonExistentStaker)?;
+			let (pool_info, mut staker_info) =
+				Self::update_pool_and_staker_rewards(&pool_info, &staker_info)?;
+
+			// Transfer unclaimed rewards from the pool to the staker.
+			T::Assets::transfer(
+				pool_info.reward_asset_id,
+				&pool_info.account,
+				&staker,
+				staker_info.rewards,
+				// Could kill the account, but only if the pool was already almost empty.
+				Preservation::Expendable,
+			)?;
+
+			// Emit event.
+			Self::deposit_event(Event::RewardsHarvested {
+				caller,
+				staker: staker.clone(),
+				pool_id,
+				amount: staker_info.rewards,
+			});
+
+			// Reset staker rewards.
+			staker_info.rewards = 0u32.into();
+
+			if staker_info.amount.is_zero() {
+				PoolStakers::<T>::remove(&pool_id, &staker);
+			} else {
+				PoolStakers::<T>::insert(&pool_id, &staker, staker_info);
+			}
+
+			Ok(())
+		}
+
+		/// Modify a pool reward rate.
+		///
+		/// Currently the reward rate can only be increased.
+		///
+		/// Only the pool admin may perform this operation.
+		#[pallet::call_index(4)]
+		pub fn set_pool_reward_rate_per_block(
+			origin: OriginFor<T>,
+			pool_id: PoolId,
+			new_reward_rate_per_block: T::Balance,
+		) -> DispatchResult {
+			let caller = T::CreatePoolOrigin::ensure_origin(origin.clone())
+				.or_else(|_| ensure_signed(origin))?;
+
+			let pool_info = Pools::<T>::get(pool_id).ok_or(Error::<T>::NonExistentPool)?;
+			ensure!(pool_info.admin == caller, BadOrigin);
+			ensure!(
+				new_reward_rate_per_block > pool_info.reward_rate_per_block,
+				Error::<T>::RewardRateCut
+			);
+
+			// Always start by updating the pool rewards.
+			let rewards_per_token = Self::reward_per_token(&pool_info)?;
+			let mut pool_info = Self::update_pool_rewards(&pool_info, rewards_per_token)?;
+
+			pool_info.reward_rate_per_block = new_reward_rate_per_block;
+			Pools::<T>::insert(pool_id, pool_info);
+
+			Self::deposit_event(Event::PoolRewardRateModified {
+				pool_id,
+				new_reward_rate_per_block,
+			});
+
+			Ok(())
+		}
+
+		/// Modify a pool admin.
+		///
+		/// Only the pool admin may perform this operation.
+		#[pallet::call_index(5)]
+		pub fn set_pool_admin(
+			origin: OriginFor<T>,
+			pool_id: PoolId,
+			new_admin: T::AccountId,
+		) -> DispatchResult {
+			let caller = T::CreatePoolOrigin::ensure_origin(origin.clone())
+				.or_else(|_| ensure_signed(origin))?;
+
+			let mut pool_info = Pools::<T>::get(pool_id).ok_or(Error::<T>::NonExistentPool)?;
+			ensure!(pool_info.admin == caller, BadOrigin);
+
+			pool_info.admin = new_admin.clone();
+			Pools::<T>::insert(pool_id, pool_info);
+
+			Self::deposit_event(Event::PoolAdminModified { pool_id, new_admin });
+
+			Ok(())
+		}
+
+		/// Set when the pool should expire.
+		///
+		/// Currently the expiry block can only be extended.
+		///
+		/// Only the pool admin may perform this operation.
+		#[pallet::call_index(6)]
+		pub fn set_pool_expiry_block(
+			origin: OriginFor<T>,
+			pool_id: PoolId,
+			new_expiry: DispatchTime<BlockNumberFor<T>>,
+		) -> DispatchResult {
+			let caller = T::CreatePoolOrigin::ensure_origin(origin.clone())
+				.or_else(|_| ensure_signed(origin))?;
+
+			let new_expiry = new_expiry.evaluate(frame_system::Pallet::<T>::block_number());
+			ensure!(
+				new_expiry > frame_system::Pallet::<T>::block_number(),
+				Error::<T>::ExpiryBlockMustBeInTheFuture
+			);
+
+			let pool_info = Pools::<T>::get(pool_id).ok_or(Error::<T>::NonExistentPool)?;
+			ensure!(pool_info.admin == caller, BadOrigin);
+			ensure!(new_expiry > pool_info.expiry_block, Error::<T>::ExpiryCut);
+
+			// Always start by updating the pool rewards.
+			let reward_per_token = Self::reward_per_token(&pool_info)?;
+			let mut pool_info = Self::update_pool_rewards(&pool_info, reward_per_token)?;
+
+			pool_info.expiry_block = new_expiry;
+			Pools::<T>::insert(pool_id, pool_info);
+
+			Self::deposit_event(Event::PoolExpiryBlockModified {
+				pool_id,
+				new_expiry_block: new_expiry,
+			});
+
+			Ok(())
+		}
+
+		/// Convenience method to deposit reward tokens into a pool.
+		///
+		/// This method is not strictly necessary (tokens could be transferred directly to the
+		/// pool pot address), but is provided for convenience so manual derivation of the
+		/// account id is not required.
+		#[pallet::call_index(7)]
+		pub fn deposit_reward_tokens(
+			origin: OriginFor<T>,
+			pool_id: PoolId,
+			amount: T::Balance,
+		) -> DispatchResult {
+			let caller = ensure_signed(origin)?;
+			let pool_info = Pools::<T>::get(pool_id).ok_or(Error::<T>::NonExistentPool)?;
+			T::Assets::transfer(
+				pool_info.reward_asset_id,
+				&caller,
+				&pool_info.account,
+				amount,
+				Preservation::Preserve,
+			)?;
+			Ok(())
+		}
+
+		/// Cleanup a pool.
+		///
+		/// Origin must be the pool admin.
+		///
+		/// Cleanup storage, release any associated storage cost and return the remaining reward
+		/// tokens to the admin.
+		#[pallet::call_index(8)]
+		pub fn cleanup_pool(origin: OriginFor<T>, pool_id: PoolId) -> DispatchResult {
+			let who = ensure_signed(origin)?;
+
+			let pool_info = Pools::<T>::get(pool_id).ok_or(Error::<T>::NonExistentPool)?;
+			ensure!(pool_info.admin == who, BadOrigin);
+
+			let stakers = PoolStakers::<T>::iter_key_prefix(pool_id).next();
+			ensure!(stakers.is_none(), Error::<T>::NonEmptyPool);
+
+			let pool_balance = T::Assets::reducible_balance(
+				pool_info.reward_asset_id.clone(),
+				&pool_info.account,
+				Preservation::Expendable,
+				Fortitude::Polite,
+			);
+			T::Assets::transfer(
+				pool_info.reward_asset_id,
+				&pool_info.account,
+				&pool_info.admin,
+				pool_balance,
+				Preservation::Expendable,
+			)?;
+
+			if let Some((who, cost)) = PoolCost::<T>::take(pool_id) {
+				T::Consideration::drop(cost, &who)?;
+			}
+
+			Pools::<T>::remove(pool_id);
+
+			Self::deposit_event(Event::PoolCleanedUp { pool_id });
+
+			Ok(())
+		}
+	}
+
+	impl<T: Config> Pallet<T> {
+		/// The pool creation footprint.
+		///
+		/// The footprint specifically accounts for the storage footprint of the pool's information
+		/// itself, excluding any potential storage footprint related to the stakers.
+		pub fn pool_creation_footprint() -> Footprint {
+			Footprint::from_mel::<(PoolId, PoolInfoFor<T>)>()
+		}
+
+		/// Derive a pool account ID from the pool's ID.
+		pub fn pool_account_id(id: &PoolId) -> T::AccountId {
+			T::PalletId::get().into_sub_account_truncating(id)
+		}
+
+		/// Computes update pool and staker reward state.
+		///
+		/// Should be called prior to any operation involving a staker.
+		///
+		/// Returns the updated pool and staker info.
+		///
+		/// NOTE: this function has no side-effects. Side-effects such as storage modifications are
+		/// the responsibility of the caller.
+		pub fn update_pool_and_staker_rewards(
+			pool_info: &PoolInfoFor<T>,
+			staker_info: &PoolStakerInfo<T::Balance>,
+		) -> Result<(PoolInfoFor<T>, PoolStakerInfo<T::Balance>), DispatchError> {
+			let reward_per_token = Self::reward_per_token(&pool_info)?;
+			let pool_info = Self::update_pool_rewards(pool_info, reward_per_token)?;
+
+			let mut new_staker_info = staker_info.clone();
+			new_staker_info.rewards = Self::derive_rewards(&staker_info, &reward_per_token)?;
+			new_staker_info.reward_per_token_paid = pool_info.reward_per_token_stored;
+			return Ok((pool_info, new_staker_info));
+		}
+
+		/// Computes update pool reward state.
+		///
+		/// Should be called every time the pool is adjusted, and a staker is not involved.
+		///
+		/// Returns the updated pool and staker info.
+		///
+		/// NOTE: this function has no side-effects. Side-effects such as storage modifications are
+		/// the responsibility of the caller.
+		pub fn update_pool_rewards(
+			pool_info: &PoolInfoFor<T>,
+			reward_per_token: T::Balance,
+		) -> Result<PoolInfoFor<T>, DispatchError> {
+			let mut new_pool_info = pool_info.clone();
+			new_pool_info.last_update_block = frame_system::Pallet::<T>::block_number();
+			new_pool_info.reward_per_token_stored = reward_per_token;
+
+			Ok(new_pool_info)
+		}
+
+		/// Derives the current reward per token for this pool.
+		fn reward_per_token(pool_info: &PoolInfoFor<T>) -> Result<T::Balance, DispatchError> {
+			if pool_info.total_tokens_staked.is_zero() {
+				return Ok(pool_info.reward_per_token_stored)
+			}
+
+			let rewardable_blocks_elapsed: u32 =
+				match Self::last_block_reward_applicable(pool_info.expiry_block)
+					.ensure_sub(pool_info.last_update_block)?
+					.try_into()
+				{
+					Ok(b) => b,
+					Err(_) => return Err(Error::<T>::BlockNumberConversionError.into()),
+				};
+
+			Ok(pool_info.reward_per_token_stored.ensure_add(
+				pool_info
+					.reward_rate_per_block
+					.ensure_mul(rewardable_blocks_elapsed.into())?
+					.ensure_mul(PRECISION_SCALING_FACTOR.into())?
+					.ensure_div(pool_info.total_tokens_staked)?,
+			)?)
+		}
+
+		/// Derives the amount of rewards earned by a staker.
+		///
+		/// This is a helper function for `update_pool_rewards` and should not be called directly.
+		fn derive_rewards(
+			staker_info: &PoolStakerInfo<T::Balance>,
+			reward_per_token: &T::Balance,
+		) -> Result<T::Balance, DispatchError> {
+			Ok(staker_info
+				.amount
+				.ensure_mul(reward_per_token.ensure_sub(staker_info.reward_per_token_paid)?)?
+				.ensure_div(PRECISION_SCALING_FACTOR.into())?
+				.ensure_add(staker_info.rewards)?)
+		}
+
+		fn last_block_reward_applicable(pool_expiry_block: BlockNumberFor<T>) -> BlockNumberFor<T> {
+			let now = frame_system::Pallet::<T>::block_number();
+			if now < pool_expiry_block {
+				now
+			} else {
+				pool_expiry_block
+			}
+		}
+	}
+}
diff --git a/substrate/frame/asset-rewards/src/mock.rs b/substrate/frame/asset-rewards/src/mock.rs
new file mode 100644
index 0000000000000000000000000000000000000000..87c8a8a0dea0b44c23baa1ed2fdfabd9b85eb999
--- /dev/null
+++ b/substrate/frame/asset-rewards/src/mock.rs
@@ -0,0 +1,221 @@
+// This file is part of Substrate.
+
+// Copyright (C) Parity Technologies (UK) Ltd.
+// SPDX-License-Identifier: Apache-2.0
+
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// 	http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+//! Test environment for Asset Rewards pallet.
+
+use super::*;
+use crate as pallet_asset_rewards;
+use core::default::Default;
+use frame_support::{
+	construct_runtime, derive_impl,
+	instances::Instance1,
+	parameter_types,
+	traits::{
+		tokens::fungible::{HoldConsideration, NativeFromLeft, NativeOrWithId, UnionOf},
+		AsEnsureOriginWithArg, ConstU128, ConstU32, EnsureOrigin, LinearStoragePrice,
+	},
+	PalletId,
+};
+use frame_system::EnsureSigned;
+use sp_runtime::{traits::IdentityLookup, BuildStorage};
+
+#[cfg(feature = "runtime-benchmarks")]
+use self::benchmarking::BenchmarkHelper;
+
+type Block = frame_system::mocking::MockBlock<MockRuntime>;
+
+construct_runtime!(
+	pub enum MockRuntime
+	{
+		System: frame_system,
+		Balances: pallet_balances,
+		Assets: pallet_assets::<Instance1>,
+		AssetsFreezer: pallet_assets_freezer::<Instance1>,
+		StakingRewards: pallet_asset_rewards,
+	}
+);
+
+#[derive_impl(frame_system::config_preludes::TestDefaultConfig)]
+impl frame_system::Config for MockRuntime {
+	type AccountId = u128;
+	type Lookup = IdentityLookup<Self::AccountId>;
+	type Block = Block;
+	type AccountData = pallet_balances::AccountData<u128>;
+}
+
+impl pallet_balances::Config for MockRuntime {
+	type Balance = u128;
+	type DustRemoval = ();
+	type RuntimeEvent = RuntimeEvent;
+	type ExistentialDeposit = ConstU128<100>;
+	type AccountStore = System;
+	type WeightInfo = ();
+	type MaxLocks = ();
+	type MaxReserves = ConstU32<50>;
+	type ReserveIdentifier = [u8; 8];
+	type FreezeIdentifier = RuntimeFreezeReason;
+	type MaxFreezes = ConstU32<50>;
+	type RuntimeHoldReason = RuntimeHoldReason;
+	type RuntimeFreezeReason = RuntimeFreezeReason;
+	type DoneSlashHandler = ();
+}
+
+impl pallet_assets::Config<Instance1> for MockRuntime {
+	type RuntimeEvent = RuntimeEvent;
+	type Balance = u128;
+	type RemoveItemsLimit = ConstU32<1000>;
+	type AssetId = u32;
+	type AssetIdParameter = u32;
+	type Currency = Balances;
+	type CreateOrigin = AsEnsureOriginWithArg<EnsureSigned<Self::AccountId>>;
+	type ForceOrigin = frame_system::EnsureRoot<Self::AccountId>;
+	type AssetDeposit = ConstU128<1>;
+	type AssetAccountDeposit = ConstU128<10>;
+	type MetadataDepositBase = ConstU128<1>;
+	type MetadataDepositPerByte = ConstU128<1>;
+	type ApprovalDeposit = ConstU128<1>;
+	type StringLimit = ConstU32<50>;
+	type Freezer = AssetsFreezer;
+	type Extra = ();
+	type WeightInfo = ();
+	type CallbackHandle = ();
+	pallet_assets::runtime_benchmarks_enabled! {
+		type BenchmarkHelper = ();
+	}
+}
+
+parameter_types! {
+	pub const StakingRewardsPalletId: PalletId = PalletId(*b"py/stkrd");
+	pub const Native: NativeOrWithId<u32> = NativeOrWithId::Native;
+	pub const PermissionedAccountId: u128 = 0;
+}
+
+/// Give Root Origin permission to create pools.
+pub struct MockPermissionedOrigin;
+impl EnsureOrigin<RuntimeOrigin> for MockPermissionedOrigin {
+	type Success = <MockRuntime as frame_system::Config>::AccountId;
+
+	fn try_origin(origin: RuntimeOrigin) -> Result<Self::Success, RuntimeOrigin> {
+		match origin.clone().into() {
+			Ok(frame_system::RawOrigin::Root) => Ok(PermissionedAccountId::get()),
+			_ => Err(origin),
+		}
+	}
+
+	#[cfg(feature = "runtime-benchmarks")]
+	fn try_successful_origin() -> Result<RuntimeOrigin, ()> {
+		Ok(RuntimeOrigin::root())
+	}
+}
+
+/// Allow Freezes for the `Assets` pallet
+impl pallet_assets_freezer::Config<pallet_assets_freezer::Instance1> for MockRuntime {
+	type RuntimeFreezeReason = RuntimeFreezeReason;
+	type RuntimeEvent = RuntimeEvent;
+}
+
+pub type NativeAndAssets = UnionOf<Balances, Assets, NativeFromLeft, NativeOrWithId<u32>, u128>;
+
+pub type NativeAndAssetsFreezer =
+	UnionOf<Balances, AssetsFreezer, NativeFromLeft, NativeOrWithId<u32>, u128>;
+
+#[cfg(feature = "runtime-benchmarks")]
+pub struct AssetRewardsBenchmarkHelper;
+#[cfg(feature = "runtime-benchmarks")]
+impl BenchmarkHelper<NativeOrWithId<u32>> for AssetRewardsBenchmarkHelper {
+	fn staked_asset() -> NativeOrWithId<u32> {
+		NativeOrWithId::<u32>::WithId(101)
+	}
+	fn reward_asset() -> NativeOrWithId<u32> {
+		NativeOrWithId::<u32>::WithId(102)
+	}
+}
+
+parameter_types! {
+	pub const CreationHoldReason: RuntimeHoldReason =
+		RuntimeHoldReason::StakingRewards(pallet_asset_rewards::HoldReason::PoolCreation);
+}
+
+impl Config for MockRuntime {
+	type RuntimeEvent = RuntimeEvent;
+	type AssetId = NativeOrWithId<u32>;
+	type Balance = <Self as pallet_balances::Config>::Balance;
+	type Assets = NativeAndAssets;
+	type AssetsFreezer = NativeAndAssetsFreezer;
+	type PalletId = StakingRewardsPalletId;
+	type CreatePoolOrigin = MockPermissionedOrigin;
+	type WeightInfo = ();
+	type RuntimeFreezeReason = RuntimeFreezeReason;
+	type Consideration = HoldConsideration<
+		u128,
+		Balances,
+		CreationHoldReason,
+		LinearStoragePrice<ConstU128<100>, ConstU128<0>, u128>,
+	>;
+	#[cfg(feature = "runtime-benchmarks")]
+	type BenchmarkHelper = AssetRewardsBenchmarkHelper;
+}
+
+pub(crate) fn new_test_ext() -> sp_io::TestExternalities {
+	let mut t = frame_system::GenesisConfig::<MockRuntime>::default().build_storage().unwrap();
+
+	pallet_assets::GenesisConfig::<MockRuntime, Instance1> {
+		// Genesis assets: id, owner, is_sufficient, min_balance
+		// pub assets: Vec<(T::AssetId, T::AccountId, bool, T::Balance)>,
+		assets: vec![(1, 1, true, 1), (10, 1, true, 1), (20, 1, true, 1)],
+		// Genesis metadata: id, name, symbol, decimals
+		// pub metadata: Vec<(T::AssetId, Vec<u8>, Vec<u8>, u8)>,
+		metadata: vec![
+			(1, b"test".to_vec(), b"TST".to_vec(), 18),
+			(10, b"test10".to_vec(), b"T10".to_vec(), 18),
+			(20, b"test20".to_vec(), b"T20".to_vec(), 18),
+		],
+		// Genesis accounts: id, account_id, balance
+		// pub accounts: Vec<(T::AssetId, T::AccountId, T::Balance)>,
+		accounts: vec![
+			(1, 1, 10000),
+			(1, 2, 20000),
+			(1, 3, 30000),
+			(1, 4, 40000),
+			(1, 10, 40000),
+			(1, 20, 40000),
+		],
+		next_asset_id: None,
+	}
+	.assimilate_storage(&mut t)
+	.unwrap();
+
+	let pool_zero_account_id = 31086825966906540362769395565;
+	pallet_balances::GenesisConfig::<MockRuntime> {
+		balances: vec![
+			(0, 10000),
+			(1, 10000),
+			(2, 20000),
+			(3, 30000),
+			(4, 40000),
+			(10, 40000),
+			(20, 40000),
+			(pool_zero_account_id, 100_000), // Top up the default pool account id
+		],
+	}
+	.assimilate_storage(&mut t)
+	.unwrap();
+
+	let mut ext = sp_io::TestExternalities::new(t);
+	ext.execute_with(|| System::set_block_number(1));
+	ext
+}
diff --git a/substrate/frame/asset-rewards/src/tests.rs b/substrate/frame/asset-rewards/src/tests.rs
new file mode 100644
index 0000000000000000000000000000000000000000..399d6a54c939293e4fdc2c87118d85596e701374
--- /dev/null
+++ b/substrate/frame/asset-rewards/src/tests.rs
@@ -0,0 +1,1457 @@
+// This file is part of Substrate.
+
+// Copyright (C) Parity Technologies (UK) Ltd.
+// SPDX-License-Identifier: Apache-2.0
+
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// 	http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+use crate::{mock::*, *};
+use frame_support::{
+	assert_err, assert_noop, assert_ok, hypothetically,
+	traits::{
+		fungible,
+		fungible::NativeOrWithId,
+		fungibles,
+		tokens::{Fortitude, Preservation},
+	},
+};
+use sp_runtime::{traits::BadOrigin, ArithmeticError, TokenError};
+
+const DEFAULT_STAKED_ASSET_ID: NativeOrWithId<u32> = NativeOrWithId::<u32>::WithId(1);
+const DEFAULT_REWARD_ASSET_ID: NativeOrWithId<u32> = NativeOrWithId::<u32>::Native;
+const DEFAULT_REWARD_RATE_PER_BLOCK: u128 = 100;
+const DEFAULT_EXPIRE_AFTER: u64 = 200;
+const DEFAULT_ADMIN: u128 = 1;
+
+/// Creates a basic pool with values:
+/// - Staking asset: 1
+/// - Reward asset: Native
+/// - Reward rate per block: 100
+/// - Lifetime: 100
+/// - Admin: 1
+///
+/// Useful to reduce boilerplate in tests when it's not important to customise or reuse pool
+/// params.
+pub fn create_default_pool() {
+	assert_ok!(StakingRewards::create_pool(
+		RuntimeOrigin::root(),
+		Box::new(DEFAULT_STAKED_ASSET_ID.clone()),
+		Box::new(DEFAULT_REWARD_ASSET_ID.clone()),
+		DEFAULT_REWARD_RATE_PER_BLOCK,
+		DispatchTime::After(DEFAULT_EXPIRE_AFTER),
+		Some(DEFAULT_ADMIN)
+	));
+}
+
+/// The same as [`create_default_pool`], but with the admin parameter set to the creator.
+pub fn create_default_pool_permissioned_admin() {
+	assert_ok!(StakingRewards::create_pool(
+		RuntimeOrigin::root(),
+		Box::new(DEFAULT_STAKED_ASSET_ID.clone()),
+		Box::new(DEFAULT_REWARD_ASSET_ID.clone()),
+		DEFAULT_REWARD_RATE_PER_BLOCK,
+		DispatchTime::After(DEFAULT_EXPIRE_AFTER),
+		Some(PermissionedAccountId::get()),
+	));
+}
+
+fn assert_hypothetically_earned(
+	staker: u128,
+	expected_earned: u128,
+	pool_id: u32,
+	reward_asset_id: NativeOrWithId<u32>,
+) {
+	hypothetically!({
+		// Get the pre-harvest balance.
+		let balance_before: <MockRuntime as Config>::Balance =
+			<<MockRuntime as Config>::Assets>::balance(reward_asset_id.clone(), &staker);
+
+		// Harvest the rewards.
+		assert_ok!(StakingRewards::harvest_rewards(RuntimeOrigin::signed(staker), pool_id, None),);
+
+		// Sanity check: staker rewards are reset to 0 if some `amount` is still staked, otherwise
+		// the storage item removed.
+		if let Some(staker_pool) = PoolStakers::<MockRuntime>::get(pool_id, staker) {
+			assert!(staker_pool.rewards == 0);
+			assert!(staker_pool.amount > 0);
+		}
+
+		// Check that the staker has earned the expected amount.
+		let balance_after =
+			<<MockRuntime as Config>::Assets>::balance(reward_asset_id.clone(), &staker);
+		assert_eq!(balance_after - balance_before, expected_earned);
+	});
+}
+
+fn events() -> Vec<Event<MockRuntime>> {
+	let result = System::events()
+		.into_iter()
+		.map(|r| r.event)
+		.filter_map(|e| {
+			if let mock::RuntimeEvent::StakingRewards(inner) = e {
+				Some(inner)
+			} else {
+				None
+			}
+		})
+		.collect();
+
+	System::reset_events();
+
+	result
+}
+
+fn pools() -> Vec<(u32, PoolInfo<u128, NativeOrWithId<u32>, u128, u64>)> {
+	Pools::<MockRuntime>::iter().collect()
+}
+
+mod create_pool {
+	use super::*;
+
+	#[test]
+	fn success() {
+		new_test_ext().execute_with(|| {
+			assert_eq!(NextPoolId::<MockRuntime>::get(), 0);
+
+			System::set_block_number(10);
+			let expected_expiry_block = DEFAULT_EXPIRE_AFTER + 10;
+
+			// Create a pool with default values, and no admin override so [`PermissionedAccountId`]
+			// is admin.
+			assert_ok!(StakingRewards::create_pool(
+				RuntimeOrigin::root(),
+				Box::new(DEFAULT_STAKED_ASSET_ID),
+				Box::new(DEFAULT_REWARD_ASSET_ID),
+				DEFAULT_REWARD_RATE_PER_BLOCK,
+				DispatchTime::After(DEFAULT_EXPIRE_AFTER),
+				Some(PermissionedAccountId::get())
+			));
+
+			// Event is emitted.
+			assert_eq!(
+				events(),
+				[Event::<MockRuntime>::PoolCreated {
+					creator: PermissionedAccountId::get(),
+					pool_id: 0,
+					staked_asset_id: DEFAULT_STAKED_ASSET_ID,
+					reward_asset_id: DEFAULT_REWARD_ASSET_ID,
+					reward_rate_per_block: DEFAULT_REWARD_RATE_PER_BLOCK,
+					expiry_block: expected_expiry_block,
+					admin: PermissionedAccountId::get(),
+				}]
+			);
+
+			// State is updated correctly.
+			assert_eq!(NextPoolId::<MockRuntime>::get(), 1);
+			assert_eq!(
+				pools(),
+				vec![(
+					0,
+					PoolInfo {
+						staked_asset_id: DEFAULT_STAKED_ASSET_ID,
+						reward_asset_id: DEFAULT_REWARD_ASSET_ID,
+						reward_rate_per_block: DEFAULT_REWARD_RATE_PER_BLOCK,
+						expiry_block: expected_expiry_block,
+						admin: PermissionedAccountId::get(),
+						total_tokens_staked: 0,
+						reward_per_token_stored: 0,
+						last_update_block: 0,
+						account: StakingRewards::pool_account_id(&0),
+					}
+				)]
+			);
+
+			// Create another pool with explicit admin and other overrides.
+			let admin = 2;
+			let staked_asset_id = NativeOrWithId::<u32>::WithId(10);
+			let reward_asset_id = NativeOrWithId::<u32>::WithId(20);
+			let reward_rate_per_block = 250;
+			let expiry_block = 500;
+			let expected_expiry_block = expiry_block + 10;
+			assert_ok!(StakingRewards::create_pool(
+				RuntimeOrigin::root(),
+				Box::new(staked_asset_id.clone()),
+				Box::new(reward_asset_id.clone()),
+				reward_rate_per_block,
+				DispatchTime::After(expiry_block),
+				Some(admin)
+			));
+
+			// Event is emitted.
+			assert_eq!(
+				events(),
+				[Event::<MockRuntime>::PoolCreated {
+					creator: PermissionedAccountId::get(),
+					pool_id: 1,
+					staked_asset_id: staked_asset_id.clone(),
+					reward_asset_id: reward_asset_id.clone(),
+					reward_rate_per_block,
+					admin,
+					expiry_block: expected_expiry_block,
+				}]
+			);
+
+			// State is updated correctly.
+			assert_eq!(NextPoolId::<MockRuntime>::get(), 2);
+			assert_eq!(
+				pools(),
+				vec![
+					(
+						0,
+						PoolInfo {
+							staked_asset_id: DEFAULT_STAKED_ASSET_ID,
+							reward_asset_id: DEFAULT_REWARD_ASSET_ID,
+							reward_rate_per_block: DEFAULT_REWARD_RATE_PER_BLOCK,
+							admin: PermissionedAccountId::get(),
+							expiry_block: DEFAULT_EXPIRE_AFTER + 10,
+							total_tokens_staked: 0,
+							reward_per_token_stored: 0,
+							last_update_block: 0,
+							account: StakingRewards::pool_account_id(&0),
+						}
+					),
+					(
+						1,
+						PoolInfo {
+							staked_asset_id,
+							reward_asset_id,
+							reward_rate_per_block,
+							admin,
+							total_tokens_staked: 0,
+							expiry_block: expected_expiry_block,
+							reward_per_token_stored: 0,
+							last_update_block: 0,
+							account: StakingRewards::pool_account_id(&1),
+						}
+					)
+				]
+			);
+		});
+	}
+
+	#[test]
+	fn success_same_assets() {
+		new_test_ext().execute_with(|| {
+			assert_eq!(NextPoolId::<MockRuntime>::get(), 0);
+
+			System::set_block_number(10);
+			let expected_expiry_block = DEFAULT_EXPIRE_AFTER + 10;
+
+			// Create a pool with the same staking and reward asset.
+			let asset = NativeOrWithId::<u32>::Native;
+			assert_ok!(StakingRewards::create_pool(
+				RuntimeOrigin::root(),
+				Box::new(asset.clone()),
+				Box::new(asset.clone()),
+				DEFAULT_REWARD_RATE_PER_BLOCK,
+				DispatchTime::After(DEFAULT_EXPIRE_AFTER),
+				Some(PermissionedAccountId::get())
+			));
+
+			// Event is emitted.
+			assert_eq!(
+				events(),
+				[Event::<MockRuntime>::PoolCreated {
+					creator: PermissionedAccountId::get(),
+					pool_id: 0,
+					staked_asset_id: asset.clone(),
+					reward_asset_id: asset.clone(),
+					reward_rate_per_block: DEFAULT_REWARD_RATE_PER_BLOCK,
+					expiry_block: expected_expiry_block,
+					admin: PermissionedAccountId::get(),
+				}]
+			);
+
+			// State is updated correctly.
+			assert_eq!(NextPoolId::<MockRuntime>::get(), 1);
+			assert_eq!(
+				pools(),
+				vec![(
+					0,
+					PoolInfo {
+						staked_asset_id: asset.clone(),
+						reward_asset_id: asset,
+						reward_rate_per_block: DEFAULT_REWARD_RATE_PER_BLOCK,
+						expiry_block: expected_expiry_block,
+						admin: PermissionedAccountId::get(),
+						total_tokens_staked: 0,
+						reward_per_token_stored: 0,
+						last_update_block: 0,
+						account: StakingRewards::pool_account_id(&0),
+					}
+				)]
+			);
+		})
+	}
+
+	#[test]
+	fn fails_for_non_existent_asset() {
+		new_test_ext().execute_with(|| {
+			let valid_asset = NativeOrWithId::<u32>::WithId(1);
+			let invalid_asset = NativeOrWithId::<u32>::WithId(200);
+
+			assert_err!(
+				StakingRewards::create_pool(
+					RuntimeOrigin::root(),
+					Box::new(valid_asset.clone()),
+					Box::new(invalid_asset.clone()),
+					10,
+					DispatchTime::After(10u64),
+					None
+				),
+				Error::<MockRuntime>::NonExistentAsset
+			);
+
+			assert_err!(
+				StakingRewards::create_pool(
+					RuntimeOrigin::root(),
+					Box::new(invalid_asset.clone()),
+					Box::new(valid_asset.clone()),
+					10,
+					DispatchTime::After(10u64),
+					None
+				),
+				Error::<MockRuntime>::NonExistentAsset
+			);
+
+			assert_err!(
+				StakingRewards::create_pool(
+					RuntimeOrigin::root(),
+					Box::new(invalid_asset.clone()),
+					Box::new(invalid_asset.clone()),
+					10,
+					DispatchTime::After(10u64),
+					None
+				),
+				Error::<MockRuntime>::NonExistentAsset
+			);
+		})
+	}
+
+	#[test]
+	fn fails_for_not_permissioned() {
+		new_test_ext().execute_with(|| {
+			let user = 100;
+			let staked_asset_id = NativeOrWithId::<u32>::Native;
+			let reward_asset_id = NativeOrWithId::<u32>::WithId(1);
+			let reward_rate_per_block = 100;
+			let expiry_block = 100u64;
+			assert_err!(
+				StakingRewards::create_pool(
+					RuntimeOrigin::signed(user),
+					Box::new(staked_asset_id.clone()),
+					Box::new(reward_asset_id.clone()),
+					reward_rate_per_block,
+					DispatchTime::After(expiry_block),
+					None
+				),
+				BadOrigin
+			);
+		});
+	}
+
+	#[test]
+	fn create_pool_with_caller_admin() {
+		new_test_ext().execute_with(|| {
+			assert_eq!(NextPoolId::<MockRuntime>::get(), 0);
+
+			System::set_block_number(10);
+			let expected_expiry_block = DEFAULT_EXPIRE_AFTER + 10;
+
+			assert_ok!(StakingRewards::create_pool(
+				RuntimeOrigin::root(),
+				Box::new(DEFAULT_STAKED_ASSET_ID),
+				Box::new(DEFAULT_REWARD_ASSET_ID),
+				DEFAULT_REWARD_RATE_PER_BLOCK,
+				DispatchTime::After(DEFAULT_EXPIRE_AFTER),
+				None,
+			));
+
+			assert_eq!(
+				events(),
+				[Event::<MockRuntime>::PoolCreated {
+					creator: PermissionedAccountId::get(),
+					pool_id: 0,
+					staked_asset_id: DEFAULT_STAKED_ASSET_ID,
+					reward_asset_id: DEFAULT_REWARD_ASSET_ID,
+					reward_rate_per_block: DEFAULT_REWARD_RATE_PER_BLOCK,
+					expiry_block: expected_expiry_block,
+					admin: PermissionedAccountId::get(),
+				}]
+			);
+
+			assert_eq!(Pools::<MockRuntime>::get(0).unwrap().admin, PermissionedAccountId::get());
+		});
+	}
+}
+
+mod stake {
+	use super::*;
+
+	#[test]
+	fn success() {
+		new_test_ext().execute_with(|| {
+			let user = 1;
+			create_default_pool();
+			let pool_id = 0;
+			let initial_balance = <Assets as fungibles::Inspect<u128>>::reducible_balance(
+				1,
+				&user,
+				Preservation::Expendable,
+				Fortitude::Force,
+			);
+
+			// User stakes tokens
+			assert_ok!(StakingRewards::stake(RuntimeOrigin::signed(user), pool_id, 1000));
+
+			// Check that the user's staked amount is updated
+			assert_eq!(PoolStakers::<MockRuntime>::get(pool_id, user).unwrap().amount, 1000);
+
+			// Event is emitted.
+			assert_eq!(
+				*events().last().unwrap(),
+				Event::<MockRuntime>::Staked { staker: user, amount: 1000, pool_id: 0 }
+			);
+
+			// Check that the pool's total tokens staked is updated
+			assert_eq!(Pools::<MockRuntime>::get(pool_id).unwrap().total_tokens_staked, 1000);
+
+			// Check user's frozen balance is updated
+			assert_eq!(
+				<Assets as fungibles::Inspect<u128>>::reducible_balance(
+					1,
+					&user,
+					Preservation::Expendable,
+					Fortitude::Force,
+				),
+				// - extra 1 for ed
+				initial_balance - 1000 - 1
+			);
+
+			// User stakes more tokens
+			assert_ok!(StakingRewards::stake(RuntimeOrigin::signed(user), pool_id, 500));
+
+			// Event is emitted.
+			assert_eq!(
+				*events().last().unwrap(),
+				Event::<MockRuntime>::Staked { staker: user, amount: 500, pool_id: 0 }
+			);
+
+			// Check that the user's staked amount is updated
+			assert_eq!(PoolStakers::<MockRuntime>::get(pool_id, user).unwrap().amount, 1000 + 500);
+
+			// Check that the pool's total tokens staked is updated
+			assert_eq!(Pools::<MockRuntime>::get(pool_id).unwrap().total_tokens_staked, 1000 + 500);
+
+			assert_eq!(
+				<Assets as fungibles::Inspect<u128>>::reducible_balance(
+					1,
+					&user,
+					Preservation::Expendable,
+					Fortitude::Force,
+				),
+				// - extra 1 for ed
+				initial_balance - 1500 - 1
+			);
+
+			// Event is emitted.
+			assert_eq!(events(), []);
+		});
+	}
+
+	#[test]
+	fn fails_for_non_existent_pool() {
+		new_test_ext().execute_with(|| {
+			let user = 1;
+			assert_err!(
+				StakingRewards::stake(RuntimeOrigin::signed(user), 999, 1000),
+				Error::<MockRuntime>::NonExistentPool
+			);
+		});
+	}
+
+	#[test]
+	fn fails_for_insufficient_balance() {
+		new_test_ext().execute_with(|| {
+			let user = 1;
+			create_default_pool();
+			let pool_id = 0;
+			let initial_balance = <Assets as fungibles::Inspect<u128>>::reducible_balance(
+				1,
+				&user,
+				Preservation::Expendable,
+				Fortitude::Force,
+			);
+			assert_err!(
+				StakingRewards::stake(RuntimeOrigin::signed(user), pool_id, initial_balance + 1),
+				TokenError::FundsUnavailable,
+			);
+		})
+	}
+}
+
+mod unstake {
+	use super::*;
+
+	#[test]
+	fn success() {
+		new_test_ext().execute_with(|| {
+			let user = 1;
+			create_default_pool();
+			let pool_id = 0;
+
+			// User stakes tokens
+			assert_ok!(StakingRewards::stake(RuntimeOrigin::signed(user), pool_id, 1000));
+
+			// User unstakes tokens
+			assert_ok!(StakingRewards::unstake(RuntimeOrigin::signed(user), pool_id, 500, None));
+
+			// Event is emitted.
+			assert_eq!(
+				*events().last().unwrap(),
+				Event::<MockRuntime>::Unstaked {
+					caller: user,
+					staker: user,
+					amount: 500,
+					pool_id: 0
+				}
+			);
+
+			// Check that the user's staked amount is updated
+			assert_eq!(PoolStakers::<MockRuntime>::get(pool_id, user).unwrap().amount, 500);
+
+			// Check that the pool's total tokens staked is updated
+			assert_eq!(Pools::<MockRuntime>::get(pool_id).unwrap().total_tokens_staked, 500);
+
+			// User unstakes remaining tokens
+			assert_ok!(StakingRewards::unstake(RuntimeOrigin::signed(user), pool_id, 500, None));
+
+			// Check that the storage items is removed since stake amount and rewards are zero.
+			assert!(PoolStakers::<MockRuntime>::get(pool_id, user).is_none());
+
+			// Check that the pool's total tokens staked is zero
+			assert_eq!(Pools::<MockRuntime>::get(pool_id).unwrap().total_tokens_staked, 0);
+		});
+	}
+
+	#[test]
+	fn unstake_for_other() {
+		new_test_ext().execute_with(|| {
+			let staker = 1;
+			let caller = 2;
+			let pool_id = 0;
+			let init_block = System::block_number();
+
+			create_default_pool();
+
+			// User stakes tokens
+			assert_ok!(StakingRewards::stake(RuntimeOrigin::signed(staker), pool_id, 1000));
+
+			// Fails to unstake for other since pool is still active
+			assert_noop!(
+				StakingRewards::unstake(RuntimeOrigin::signed(caller), pool_id, 500, Some(staker)),
+				BadOrigin,
+			);
+
+			System::set_block_number(init_block + DEFAULT_EXPIRE_AFTER + 1);
+
+			assert_ok!(StakingRewards::unstake(
+				RuntimeOrigin::signed(caller),
+				pool_id,
+				500,
+				Some(staker)
+			));
+
+			// Event is emitted.
+			assert_eq!(
+				*events().last().unwrap(),
+				Event::<MockRuntime>::Unstaked { caller, staker, amount: 500, pool_id: 0 }
+			);
+		});
+	}
+
+	#[test]
+	fn fails_for_non_existent_pool() {
+		new_test_ext().execute_with(|| {
+			let user = 1;
+			let non_existent_pool_id = 999;
+
+			// User tries to unstake tokens from a non-existent pool
+			assert_err!(
+				StakingRewards::unstake(
+					RuntimeOrigin::signed(user),
+					non_existent_pool_id,
+					500,
+					None
+				),
+				Error::<MockRuntime>::NonExistentPool
+			);
+		});
+	}
+
+	#[test]
+	fn fails_for_insufficient_staked_amount() {
+		new_test_ext().execute_with(|| {
+			let user = 1;
+			create_default_pool();
+			let pool_id = 0;
+
+			// User stakes tokens
+			assert_ok!(StakingRewards::stake(RuntimeOrigin::signed(user), pool_id, 1000));
+
+			// User tries to unstake more tokens than they have staked
+			assert_err!(
+				StakingRewards::unstake(RuntimeOrigin::signed(user), pool_id, 1500, None),
+				Error::<MockRuntime>::NotEnoughTokens
+			);
+		});
+	}
+}
+
+mod harvest_rewards {
+	use super::*;
+
+	#[test]
+	fn success() {
+		new_test_ext().execute_with(|| {
+			let staker = 1;
+			let pool_id = 0;
+			let reward_asset_id = NativeOrWithId::<u32>::Native;
+			create_default_pool();
+
+			// Stake
+			System::set_block_number(10);
+			assert_ok!(StakingRewards::stake(RuntimeOrigin::signed(staker), pool_id, 1000));
+
+			// Harvest
+			System::set_block_number(20);
+			let balance_before: <MockRuntime as Config>::Balance =
+				<<MockRuntime as Config>::Assets>::balance(reward_asset_id.clone(), &staker);
+			assert_ok!(StakingRewards::harvest_rewards(
+				RuntimeOrigin::signed(staker),
+				pool_id,
+				None
+			));
+			let balance_after =
+				<<MockRuntime as Config>::Assets>::balance(reward_asset_id.clone(), &staker);
+
+			// Assert
+			assert_eq!(
+				balance_after - balance_before,
+				10 * Pools::<MockRuntime>::get(pool_id).unwrap().reward_rate_per_block
+			);
+			assert_eq!(
+				*events().last().unwrap(),
+				Event::<MockRuntime>::RewardsHarvested {
+					caller: staker,
+					staker,
+					pool_id,
+					amount: 10 * Pools::<MockRuntime>::get(pool_id).unwrap().reward_rate_per_block
+				}
+			);
+		});
+	}
+
+	#[test]
+	fn harvest_for_other() {
+		new_test_ext().execute_with(|| {
+			let caller = 2;
+			let staker = 1;
+			let pool_id = 0;
+			let init_block = System::block_number();
+
+			create_default_pool();
+
+			// Stake
+			System::set_block_number(10);
+			assert_ok!(StakingRewards::stake(RuntimeOrigin::signed(staker), pool_id, 1000));
+
+			System::set_block_number(20);
+
+			// Fails to harvest for staker since pool is still active
+			assert_noop!(
+				StakingRewards::harvest_rewards(
+					RuntimeOrigin::signed(caller),
+					pool_id,
+					Some(staker)
+				),
+				BadOrigin
+			);
+
+			System::set_block_number(init_block + DEFAULT_EXPIRE_AFTER + 1);
+
+			// Harvest for staker
+			assert_ok!(StakingRewards::harvest_rewards(
+				RuntimeOrigin::signed(caller),
+				pool_id,
+				Some(staker),
+			));
+
+			assert!(matches!(
+				events().last().unwrap(),
+				Event::<MockRuntime>::RewardsHarvested {
+					caller,
+					staker,
+					pool_id,
+					..
+				} if caller == caller && staker == staker && pool_id == pool_id
+			));
+		});
+	}
+
+	#[test]
+	fn fails_for_non_existent_staker() {
+		new_test_ext().execute_with(|| {
+			let non_existent_staker = 999;
+
+			create_default_pool();
+			assert_err!(
+				StakingRewards::harvest_rewards(
+					RuntimeOrigin::signed(non_existent_staker),
+					0,
+					None
+				),
+				Error::<MockRuntime>::NonExistentStaker
+			);
+		});
+	}
+
+	#[test]
+	fn fails_for_non_existent_pool() {
+		new_test_ext().execute_with(|| {
+			let staker = 1;
+			let non_existent_pool_id = 999;
+
+			assert_err!(
+				StakingRewards::harvest_rewards(
+					RuntimeOrigin::signed(staker),
+					non_existent_pool_id,
+					None,
+				),
+				Error::<MockRuntime>::NonExistentPool
+			);
+		});
+	}
+}
+
+mod set_pool_admin {
+	use super::*;
+
+	#[test]
+	fn success_signed_admin() {
+		new_test_ext().execute_with(|| {
+			let admin = 1;
+			let new_admin = 2;
+			let pool_id = 0;
+			create_default_pool();
+
+			// Modify the pool admin
+			assert_ok!(StakingRewards::set_pool_admin(
+				RuntimeOrigin::signed(admin),
+				pool_id,
+				new_admin,
+			));
+
+			// Check state
+			assert_eq!(
+				*events().last().unwrap(),
+				Event::<MockRuntime>::PoolAdminModified { pool_id, new_admin }
+			);
+			assert_eq!(Pools::<MockRuntime>::get(pool_id).unwrap().admin, new_admin);
+		});
+	}
+
+	#[test]
+	fn success_permissioned_admin() {
+		new_test_ext().execute_with(|| {
+			let new_admin = 2;
+			let pool_id = 0;
+			create_default_pool_permissioned_admin();
+
+			// Modify the pool admin
+			assert_ok!(StakingRewards::set_pool_admin(RuntimeOrigin::root(), pool_id, new_admin));
+
+			// Check state
+			assert_eq!(
+				*events().last().unwrap(),
+				Event::<MockRuntime>::PoolAdminModified { pool_id, new_admin }
+			);
+			assert_eq!(Pools::<MockRuntime>::get(pool_id).unwrap().admin, new_admin);
+		});
+	}
+
+	#[test]
+	fn fails_for_non_existent_pool() {
+		new_test_ext().execute_with(|| {
+			let admin = 1;
+			let new_admin = 2;
+			let non_existent_pool_id = 999;
+
+			assert_err!(
+				StakingRewards::set_pool_admin(
+					RuntimeOrigin::signed(admin),
+					non_existent_pool_id,
+					new_admin
+				),
+				Error::<MockRuntime>::NonExistentPool
+			);
+		});
+	}
+
+	#[test]
+	fn fails_for_non_admin() {
+		new_test_ext().execute_with(|| {
+			let new_admin = 2;
+			let non_admin = 3;
+			let pool_id = 0;
+			create_default_pool();
+
+			assert_err!(
+				StakingRewards::set_pool_admin(
+					RuntimeOrigin::signed(non_admin),
+					pool_id,
+					new_admin
+				),
+				BadOrigin
+			);
+		});
+	}
+}
+
+mod set_pool_expiry_block {
+	use super::*;
+
+	#[test]
+	fn success_permissioned_admin() {
+		new_test_ext().execute_with(|| {
+			let pool_id = 0;
+			let new_expiry_block = System::block_number() + DEFAULT_EXPIRE_AFTER + 1u64;
+			create_default_pool_permissioned_admin();
+
+			assert_ok!(StakingRewards::set_pool_expiry_block(
+				RuntimeOrigin::root(),
+				pool_id,
+				DispatchTime::At(new_expiry_block),
+			));
+
+			// Check state
+			assert_eq!(Pools::<MockRuntime>::get(pool_id).unwrap().expiry_block, new_expiry_block);
+			assert_eq!(
+				*events().last().unwrap(),
+				Event::<MockRuntime>::PoolExpiryBlockModified { pool_id, new_expiry_block }
+			);
+		});
+	}
+
+	#[test]
+	fn success_signed_admin() {
+		new_test_ext().execute_with(|| {
+			let admin = 1;
+			let pool_id = 0;
+			let new_expiry_block = System::block_number() + DEFAULT_EXPIRE_AFTER + 1u64;
+			create_default_pool();
+
+			assert_ok!(StakingRewards::set_pool_expiry_block(
+				RuntimeOrigin::signed(admin),
+				pool_id,
+				DispatchTime::At(new_expiry_block)
+			));
+
+			// Check state
+			assert_eq!(Pools::<MockRuntime>::get(pool_id).unwrap().expiry_block, new_expiry_block);
+			assert_eq!(
+				*events().last().unwrap(),
+				Event::<MockRuntime>::PoolExpiryBlockModified { pool_id, new_expiry_block }
+			);
+		});
+	}
+
+	#[test]
+	fn extends_reward_accumulation() {
+		new_test_ext().execute_with(|| {
+			let admin = 1;
+			let staker = 2;
+			let pool_id = 0;
+			let new_expiry_block = 300u64;
+			System::set_block_number(10);
+			create_default_pool();
+
+			// Regular reward accumulation
+			assert_ok!(StakingRewards::stake(RuntimeOrigin::signed(staker), pool_id, 1000));
+			System::set_block_number(20);
+			assert_hypothetically_earned(
+				staker,
+				DEFAULT_REWARD_RATE_PER_BLOCK * 10,
+				pool_id,
+				NativeOrWithId::<u32>::Native,
+			);
+
+			// Expiry was block 210, so earned 200 at block 250
+			System::set_block_number(250);
+			assert_hypothetically_earned(
+				staker,
+				DEFAULT_REWARD_RATE_PER_BLOCK * 200,
+				pool_id,
+				NativeOrWithId::<u32>::Native,
+			);
+
+			// Extend expiry 50 more blocks
+			assert_ok!(StakingRewards::set_pool_expiry_block(
+				RuntimeOrigin::signed(admin),
+				pool_id,
+				DispatchTime::At(new_expiry_block)
+			));
+			System::set_block_number(350);
+
+			// Staker has been in pool with rewards active for 250 blocks total
+			assert_hypothetically_earned(
+				staker,
+				DEFAULT_REWARD_RATE_PER_BLOCK * 250,
+				pool_id,
+				NativeOrWithId::<u32>::Native,
+			);
+		});
+	}
+
+	#[test]
+	fn fails_to_cutback_expiration() {
+		new_test_ext().execute_with(|| {
+			let admin = 1;
+			let pool_id = 0;
+			create_default_pool();
+
+			assert_noop!(
+				StakingRewards::set_pool_expiry_block(
+					RuntimeOrigin::signed(admin),
+					pool_id,
+					DispatchTime::After(30)
+				),
+				Error::<MockRuntime>::ExpiryCut
+			);
+		});
+	}
+
+	#[test]
+	fn fails_for_non_existent_pool() {
+		new_test_ext().execute_with(|| {
+			let admin = 1;
+			let non_existent_pool_id = 999;
+			let new_expiry_block = 200u64;
+
+			assert_err!(
+				StakingRewards::set_pool_expiry_block(
+					RuntimeOrigin::signed(admin),
+					non_existent_pool_id,
+					DispatchTime::After(new_expiry_block)
+				),
+				Error::<MockRuntime>::NonExistentPool
+			);
+		});
+	}
+
+	#[test]
+	fn fails_for_non_admin() {
+		new_test_ext().execute_with(|| {
+			let non_admin = 2;
+			let pool_id = 0;
+			let new_expiry_block = 200u64;
+			create_default_pool();
+
+			assert_err!(
+				StakingRewards::set_pool_expiry_block(
+					RuntimeOrigin::signed(non_admin),
+					pool_id,
+					DispatchTime::After(new_expiry_block)
+				),
+				BadOrigin
+			);
+		});
+	}
+
+	#[test]
+	fn fails_for_expiry_block_in_the_past() {
+		new_test_ext().execute_with(|| {
+			let admin = 1;
+			let pool_id = 0;
+			create_default_pool();
+			System::set_block_number(50);
+			assert_err!(
+				StakingRewards::set_pool_expiry_block(
+					RuntimeOrigin::signed(admin),
+					pool_id,
+					DispatchTime::At(40u64)
+				),
+				Error::<MockRuntime>::ExpiryBlockMustBeInTheFuture
+			);
+		});
+	}
+}
+
+mod set_pool_reward_rate_per_block {
+	use super::*;
+
+	#[test]
+	fn success_signed_admin() {
+		new_test_ext().execute_with(|| {
+			let pool_id = 0;
+			let new_reward_rate = 200;
+			create_default_pool();
+
+			// Pool Admin can modify
+			assert_ok!(StakingRewards::set_pool_reward_rate_per_block(
+				RuntimeOrigin::signed(DEFAULT_ADMIN),
+				pool_id,
+				new_reward_rate
+			));
+
+			// Check state
+			assert_eq!(
+				Pools::<MockRuntime>::get(pool_id).unwrap().reward_rate_per_block,
+				new_reward_rate
+			);
+
+			// Check event
+			assert_eq!(
+				*events().last().unwrap(),
+				Event::<MockRuntime>::PoolRewardRateModified {
+					pool_id,
+					new_reward_rate_per_block: new_reward_rate
+				}
+			);
+		});
+	}
+
+	#[test]
+	fn success_permissioned_admin() {
+		new_test_ext().execute_with(|| {
+			let pool_id = 0;
+			let new_reward_rate = 200;
+			create_default_pool_permissioned_admin();
+
+			// Root can modify
+			assert_ok!(StakingRewards::set_pool_reward_rate_per_block(
+				RuntimeOrigin::root(),
+				pool_id,
+				new_reward_rate
+			));
+
+			// Check state
+			assert_eq!(
+				Pools::<MockRuntime>::get(pool_id).unwrap().reward_rate_per_block,
+				new_reward_rate
+			);
+
+			// Check event
+			assert_eq!(
+				*events().last().unwrap(),
+				Event::<MockRuntime>::PoolRewardRateModified {
+					pool_id,
+					new_reward_rate_per_block: new_reward_rate
+				}
+			);
+		});
+	}
+
+	#[test]
+	fn staker_rewards_are_affected_correctly() {
+		new_test_ext().execute_with(|| {
+			let admin = 1;
+			let staker = 2;
+			let pool_id = 0;
+			let new_reward_rate = 150;
+			create_default_pool();
+
+			// Stake some tokens, and accumulate 10 blocks of rewards at the default pool rate (100)
+			System::set_block_number(10);
+			assert_ok!(StakingRewards::stake(RuntimeOrigin::signed(staker), pool_id, 1000));
+			System::set_block_number(20);
+
+			// Increase the reward rate
+			assert_ok!(StakingRewards::set_pool_reward_rate_per_block(
+				RuntimeOrigin::signed(admin),
+				pool_id,
+				new_reward_rate
+			));
+
+			// Accumulate 10 blocks of rewards at the new rate
+			System::set_block_number(30);
+
+			// Check that rewards are calculated correctly with the updated rate
+			assert_hypothetically_earned(
+				staker,
+				10 * 100 + 10 * new_reward_rate,
+				pool_id,
+				NativeOrWithId::<u32>::Native,
+			);
+		});
+	}
+
+	#[test]
+	fn fails_for_non_existent_pool() {
+		new_test_ext().execute_with(|| {
+			let admin = 1;
+			let non_existent_pool_id = 999;
+			let new_reward_rate = 200;
+
+			assert_err!(
+				StakingRewards::set_pool_reward_rate_per_block(
+					RuntimeOrigin::signed(admin),
+					non_existent_pool_id,
+					new_reward_rate
+				),
+				Error::<MockRuntime>::NonExistentPool
+			);
+		});
+	}
+
+	#[test]
+	fn fails_for_non_admin() {
+		new_test_ext().execute_with(|| {
+			let non_admin = 2;
+			let pool_id = 0;
+			let new_reward_rate = 200;
+			create_default_pool();
+
+			assert_err!(
+				StakingRewards::set_pool_reward_rate_per_block(
+					RuntimeOrigin::signed(non_admin),
+					pool_id,
+					new_reward_rate
+				),
+				BadOrigin
+			);
+		});
+	}
+
+	#[test]
+	fn fails_to_decrease() {
+		new_test_ext().execute_with(|| {
+			create_default_pool_permissioned_admin();
+
+			assert_noop!(
+				StakingRewards::set_pool_reward_rate_per_block(
+					RuntimeOrigin::root(),
+					0,
+					DEFAULT_REWARD_RATE_PER_BLOCK - 1
+				),
+				Error::<MockRuntime>::RewardRateCut
+			);
+		});
+	}
+}
+
+mod deposit_reward_tokens {
+	use super::*;
+
+	#[test]
+	fn success() {
+		new_test_ext().execute_with(|| {
+			let depositor = 1;
+			let pool_id = 0;
+			let amount = 1000;
+			let reward_asset_id = NativeOrWithId::<u32>::Native;
+			create_default_pool();
+			let pool_account_id = StakingRewards::pool_account_id(&pool_id);
+
+			let depositor_balance_before =
+				<<MockRuntime as Config>::Assets>::balance(reward_asset_id.clone(), &depositor);
+			let pool_balance_before = <<MockRuntime as Config>::Assets>::balance(
+				reward_asset_id.clone(),
+				&pool_account_id,
+			);
+			assert_ok!(StakingRewards::deposit_reward_tokens(
+				RuntimeOrigin::signed(depositor),
+				pool_id,
+				amount
+			));
+			let depositor_balance_after =
+				<<MockRuntime as Config>::Assets>::balance(reward_asset_id.clone(), &depositor);
+			let pool_balance_after =
+				<<MockRuntime as Config>::Assets>::balance(reward_asset_id, &pool_account_id);
+
+			assert_eq!(pool_balance_after - pool_balance_before, amount);
+			assert_eq!(depositor_balance_before - depositor_balance_after, amount);
+		});
+	}
+
+	#[test]
+	fn fails_for_non_existent_pool() {
+		new_test_ext().execute_with(|| {
+			assert_err!(
+				StakingRewards::deposit_reward_tokens(RuntimeOrigin::signed(1), 999, 100),
+				Error::<MockRuntime>::NonExistentPool
+			);
+		});
+	}
+
+	#[test]
+	fn fails_for_insufficient_balance() {
+		new_test_ext().execute_with(|| {
+			create_default_pool();
+			assert_err!(
+				StakingRewards::deposit_reward_tokens(RuntimeOrigin::signed(1), 0, 100_000_000),
+				ArithmeticError::Underflow
+			);
+		});
+	}
+}
+
+mod cleanup_pool {
+	use super::*;
+
+	#[test]
+	fn success() {
+		new_test_ext().execute_with(|| {
+			let pool_id = 0;
+			let admin = DEFAULT_ADMIN;
+			let admin_balance_before = <Balances as fungible::Inspect<u128>>::balance(&admin);
+
+			create_default_pool();
+			assert!(Pools::<MockRuntime>::get(pool_id).is_some());
+
+			assert_ok!(StakingRewards::cleanup_pool(RuntimeOrigin::signed(admin), pool_id));
+
+			assert_eq!(
+				<Balances as fungible::Inspect<u128>>::balance(&admin),
+				// `100_000` initial pool account balance from Genesis config
+				admin_balance_before + 100_000,
+			);
+			assert_eq!(Pools::<MockRuntime>::get(pool_id), None);
+			assert_eq!(PoolStakers::<MockRuntime>::iter_prefix_values(pool_id).count(), 0);
+			assert_eq!(PoolCost::<MockRuntime>::get(pool_id), None);
+		});
+	}
+
+	#[test]
+	fn success_only_when_pool_empty() {
+		new_test_ext().execute_with(|| {
+			let pool_id = 0;
+			let staker = 20;
+			let admin = DEFAULT_ADMIN;
+
+			create_default_pool();
+
+			// stake to prevent pool cleanup
+			assert_ok!(StakingRewards::stake(RuntimeOrigin::signed(staker), pool_id, 100));
+
+			assert_noop!(
+				StakingRewards::cleanup_pool(RuntimeOrigin::signed(admin), pool_id),
+				Error::<MockRuntime>::NonEmptyPool
+			);
+
+			// unstake partially
+			assert_ok!(StakingRewards::unstake(RuntimeOrigin::signed(staker), pool_id, 50, None));
+
+			assert_noop!(
+				StakingRewards::cleanup_pool(RuntimeOrigin::signed(admin), pool_id),
+				Error::<MockRuntime>::NonEmptyPool
+			);
+
+			// unstake all
+			assert_ok!(StakingRewards::unstake(RuntimeOrigin::signed(staker), pool_id, 50, None));
+
+			assert_ok!(StakingRewards::cleanup_pool(RuntimeOrigin::signed(admin), pool_id),);
+
+			assert_eq!(Pools::<MockRuntime>::get(pool_id), None);
+			assert_eq!(PoolStakers::<MockRuntime>::iter_prefix_values(pool_id).count(), 0);
+			assert_eq!(PoolCost::<MockRuntime>::get(pool_id), None);
+		});
+	}
+
+	#[test]
+	fn fails_on_wrong_origin() {
+		new_test_ext().execute_with(|| {
+			let caller = 888;
+			let pool_id = 0;
+			create_default_pool();
+
+			assert_noop!(
+				StakingRewards::cleanup_pool(RuntimeOrigin::signed(caller), pool_id),
+				BadOrigin
+			);
+		});
+	}
+}
+
+/// This integration test
+/// 1. Considers 2 stakers each staking and unstaking at different intervals, asserts their
+///    claimable rewards are adjusted as expected, and that harvesting works.
+/// 2. Checks that rewards are correctly halted after the pool's expiry block, and resume when the
+///    pool is extended.
+/// 3. Checks that reward rates adjustment works correctly.
+///
+/// Note: There are occasionally off by 1 errors due to rounding. In practice this is
+/// insignificant.
+#[test]
+fn integration() {
+	new_test_ext().execute_with(|| {
+		let admin = 1;
+		let staker1 = 10u128;
+		let staker2 = 20;
+		let staked_asset_id = NativeOrWithId::<u32>::WithId(1);
+		let reward_asset_id = NativeOrWithId::<u32>::Native;
+		let reward_rate_per_block = 100;
+		let lifetime = 24u64.into();
+		System::set_block_number(1);
+		assert_ok!(StakingRewards::create_pool(
+			RuntimeOrigin::root(),
+			Box::new(staked_asset_id.clone()),
+			Box::new(reward_asset_id.clone()),
+			reward_rate_per_block,
+			DispatchTime::After(lifetime),
+			Some(admin)
+		));
+		let pool_id = 0;
+
+		// Block 7: Staker 1 stakes 100 tokens.
+		System::set_block_number(7);
+		assert_ok!(StakingRewards::stake(RuntimeOrigin::signed(staker1), pool_id, 100));
+		// At this point
+		// - Staker 1 has earned 0 tokens.
+		// - Staker 1 is earning 100 tokens per block.
+
+		// Check that Staker 1 has earned 0 tokens.
+		assert_hypothetically_earned(staker1, 0, pool_id, reward_asset_id.clone());
+
+		// Block 9: Staker 2 stakes 100 tokens.
+		System::set_block_number(9);
+		assert_ok!(StakingRewards::stake(RuntimeOrigin::signed(staker2), pool_id, 100));
+		// At this point
+		// - Staker 1 has earned 200 (100*2) tokens.
+		// - Staker 2 has earned 0 tokens.
+		// - Staker 1 is earning 50 tokens per block.
+		// - Staker 2 is earning 50 tokens per block.
+
+		// Check that Staker 1 has earned 200 tokens and Staker 2 has earned 0 tokens.
+		assert_hypothetically_earned(staker1, 200, pool_id, reward_asset_id.clone());
+		assert_hypothetically_earned(staker2, 0, pool_id, reward_asset_id.clone());
+
+		// Block 12: Staker 1 stakes an additional 100 tokens.
+		System::set_block_number(12);
+		assert_ok!(StakingRewards::stake(RuntimeOrigin::signed(staker1), pool_id, 100));
+		// At this point
+		// - Staker 1 has earned 350 (200 + (50 * 3)) tokens.
+		// - Staker 2 has earned 150 (50 * 3) tokens.
+		// - Staker 1 is earning 66.66 tokens per block.
+		// - Staker 2 is earning 33.33 tokens per block.
+
+		// Check that Staker 1 has earned 350 tokens and Staker 2 has earned 150 tokens.
+		assert_hypothetically_earned(staker1, 350, pool_id, reward_asset_id.clone());
+		assert_hypothetically_earned(staker2, 150, pool_id, reward_asset_id.clone());
+
+		// Block 22: Staker 1 unstakes 100 tokens.
+		System::set_block_number(22);
+		assert_ok!(StakingRewards::unstake(RuntimeOrigin::signed(staker1), pool_id, 100, None));
+		// - Staker 1 has earned 1016 (350 + 66.66 * 10) tokens.
+		// - Staker 2 has earned 483 (150 + 33.33 * 10) tokens.
+		// - Staker 1 is earning 50 tokens per block.
+		// - Staker 2 is earning 50 tokens per block.
+		assert_hypothetically_earned(staker1, 1016, pool_id, reward_asset_id.clone());
+		assert_hypothetically_earned(staker2, 483, pool_id, reward_asset_id.clone());
+
+		// Block 23: Staker 1 unstakes 100 tokens.
+		System::set_block_number(23);
+		assert_ok!(StakingRewards::unstake(RuntimeOrigin::signed(staker1), pool_id, 100, None));
+		// - Staker 1 has earned 1065 (1015 + 50) tokens.
+		// - Staker 2 has earned 533 (483 + 50) tokens.
+		// - Staker 1 is earning 0 tokens per block.
+		// - Staker 2 is earning 100 tokens per block.
+		assert_hypothetically_earned(staker1, 1066, pool_id, reward_asset_id.clone());
+		assert_hypothetically_earned(staker2, 533, pool_id, reward_asset_id.clone());
+
+		// Block 50: Stakers should only have earned 2 blocks worth of tokens (expiry is 25).
+		System::set_block_number(50);
+		// - Staker 1 has earned 1065 tokens.
+		// - Staker 2 has earned 733 (533 + 2 * 100) tokens.
+		// - Staker 1 is earning 0 tokens per block.
+		// - Staker 2 is earning 0 tokens per block.
+		assert_hypothetically_earned(staker1, 1066, pool_id, reward_asset_id.clone());
+		assert_hypothetically_earned(staker2, 733, pool_id, reward_asset_id.clone());
+
+		// Block 51: Extend the pool expiry block to 60.
+		System::set_block_number(51);
+		// - Staker 1 is earning 0 tokens per block.
+		// - Staker 2 is earning 100 tokens per block.
+		assert_ok!(StakingRewards::set_pool_expiry_block(
+			RuntimeOrigin::signed(admin),
+			pool_id,
+			DispatchTime::At(60u64),
+		));
+		assert_hypothetically_earned(staker1, 1066, pool_id, reward_asset_id.clone());
+		assert_hypothetically_earned(staker2, 733, pool_id, reward_asset_id.clone());
+
+		// Block 53: Check rewards are resumed.
+		// - Staker 1 has earned 1065 tokens.
+		// - Staker 2 has earned 933 (733 + 2 * 100) tokens.
+		// - Staker 2 is earning 100 tokens per block.
+		System::set_block_number(53);
+		assert_hypothetically_earned(staker1, 1066, pool_id, reward_asset_id.clone());
+		assert_hypothetically_earned(staker2, 933, pool_id, reward_asset_id.clone());
+
+		// Block 55: Increase the block reward.
+		// - Staker 1 has earned 1065 tokens.
+		// - Staker 2 has earned 1133 (933 + 2 * 100) tokens.
+		// - Staker 2 is earning 50 tokens per block.
+		System::set_block_number(55);
+		assert_ok!(StakingRewards::set_pool_reward_rate_per_block(
+			RuntimeOrigin::signed(admin),
+			pool_id,
+			150
+		));
+		assert_hypothetically_earned(staker1, 1066, pool_id, reward_asset_id.clone());
+		assert_hypothetically_earned(staker2, 1133, pool_id, reward_asset_id.clone());
+
+		// Block 57: Staker2 harvests their rewards.
+		System::set_block_number(57);
+		// - Staker 2 has earned 1433 (1133 + 2 * 150) tokens.
+		assert_hypothetically_earned(staker2, 1433, pool_id, reward_asset_id.clone());
+		// Get the pre-harvest balance.
+		let balance_before: <MockRuntime as Config>::Balance =
+			<<MockRuntime as Config>::Assets>::balance(reward_asset_id.clone(), &staker2);
+		assert_ok!(StakingRewards::harvest_rewards(RuntimeOrigin::signed(staker2), pool_id, None));
+		let balance_after =
+			<<MockRuntime as Config>::Assets>::balance(reward_asset_id.clone(), &staker2);
+		assert_eq!(balance_after - balance_before, 1433u128);
+
+		// Block 60: Check rewards were adjusted correctly.
+		// - Staker 1 has earned 1065 tokens.
+		// - Staker 2 has earned 450 (3 * 150) tokens.
+		System::set_block_number(60);
+		assert_hypothetically_earned(staker1, 1066, pool_id, reward_asset_id.clone());
+		assert_hypothetically_earned(staker2, 450, pool_id, reward_asset_id.clone());
+
+		// Finally, check events.
+		assert_eq!(
+			events(),
+			[
+				Event::PoolCreated {
+					creator: PermissionedAccountId::get(),
+					pool_id,
+					staked_asset_id,
+					reward_asset_id,
+					reward_rate_per_block: 100,
+					expiry_block: 25,
+					admin,
+				},
+				Event::Staked { staker: staker1, pool_id, amount: 100 },
+				Event::Staked { staker: staker2, pool_id, amount: 100 },
+				Event::Staked { staker: staker1, pool_id, amount: 100 },
+				Event::Unstaked { caller: staker1, staker: staker1, pool_id, amount: 100 },
+				Event::Unstaked { caller: staker1, staker: staker1, pool_id, amount: 100 },
+				Event::PoolExpiryBlockModified { pool_id, new_expiry_block: 60 },
+				Event::PoolRewardRateModified { pool_id, new_reward_rate_per_block: 150 },
+				Event::RewardsHarvested { caller: staker2, staker: staker2, pool_id, amount: 1433 }
+			]
+		);
+	});
+}
diff --git a/substrate/frame/asset-rewards/src/weights.rs b/substrate/frame/asset-rewards/src/weights.rs
new file mode 100644
index 0000000000000000000000000000000000000000..c9e2d0fd251aa0f09b9d9da54b2307327a752380
--- /dev/null
+++ b/substrate/frame/asset-rewards/src/weights.rs
@@ -0,0 +1,368 @@
+// This file is part of Substrate.
+
+// Copyright (C) Parity Technologies (UK) Ltd.
+// SPDX-License-Identifier: Apache-2.0
+
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// 	http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+//! Autogenerated weights for `pallet_asset_rewards`
+//!
+//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 32.0.0
+//! DATE: 2025-01-14, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]`
+//! WORST CASE MAP SIZE: `1000000`
+//! HOSTNAME: `runner-ys-ssygq-project-674-concurrent-0`, CPU: `Intel(R) Xeon(R) CPU @ 2.60GHz`
+//! WASM-EXECUTION: `Compiled`, CHAIN: `Some("dev")`, DB CACHE: `1024`
+
+// Executed Command:
+// target/production/substrate-node
+// benchmark
+// pallet
+// --steps=50
+// --repeat=20
+// --extrinsic=*
+// --wasm-execution=compiled
+// --heap-pages=4096
+// --json-file=/builds/parity/mirrors/polkadot-sdk/.git/.artifacts/bench.json
+// --pallet=pallet_asset_rewards
+// --chain=dev
+// --header=./substrate/HEADER-APACHE2
+// --output=./substrate/frame/asset-rewards/src/weights.rs
+// --template=./substrate/.maintain/frame-weight-template.hbs
+
+#![cfg_attr(rustfmt, rustfmt_skip)]
+#![allow(unused_parens)]
+#![allow(unused_imports)]
+#![allow(missing_docs)]
+
+use frame_support::{traits::Get, weights::{Weight, constants::RocksDbWeight}};
+use core::marker::PhantomData;
+
+/// Weight functions needed for `pallet_asset_rewards`.
+pub trait WeightInfo {
+	fn create_pool() -> Weight;
+	fn stake() -> Weight;
+	fn unstake() -> Weight;
+	fn harvest_rewards() -> Weight;
+	fn set_pool_reward_rate_per_block() -> Weight;
+	fn set_pool_admin() -> Weight;
+	fn set_pool_expiry_block() -> Weight;
+	fn deposit_reward_tokens() -> Weight;
+	fn cleanup_pool() -> Weight;
+}
+
+/// Weights for `pallet_asset_rewards` using the Substrate node and recommended hardware.
+pub struct SubstrateWeight<T>(PhantomData<T>);
+impl<T: frame_system::Config> WeightInfo for SubstrateWeight<T> {
+	/// Storage: `Assets::Asset` (r:2 w:0)
+	/// Proof: `Assets::Asset` (`max_values`: None, `max_size`: Some(210), added: 2685, mode: `MaxEncodedLen`)
+	/// Storage: `AssetRewards::NextPoolId` (r:1 w:1)
+	/// Proof: `AssetRewards::NextPoolId` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`)
+	/// Storage: `System::Account` (r:1 w:1)
+	/// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`)
+	/// Storage: `Balances::Holds` (r:1 w:1)
+	/// Proof: `Balances::Holds` (`max_values`: None, `max_size`: Some(373), added: 2848, mode: `MaxEncodedLen`)
+	/// Storage: `AssetRewards::PoolCost` (r:0 w:1)
+	/// Proof: `AssetRewards::PoolCost` (`max_values`: None, `max_size`: Some(68), added: 2543, mode: `MaxEncodedLen`)
+	/// Storage: `AssetRewards::Pools` (r:0 w:1)
+	/// Proof: `AssetRewards::Pools` (`max_values`: None, `max_size`: Some(150), added: 2625, mode: `MaxEncodedLen`)
+	fn create_pool() -> Weight {
+		// Proof Size summary in bytes:
+		//  Measured:  `495`
+		//  Estimated: `6360`
+		// Minimum execution time: 62_655_000 picoseconds.
+		Weight::from_parts(63_723_000, 6360)
+			.saturating_add(T::DbWeight::get().reads(5_u64))
+			.saturating_add(T::DbWeight::get().writes(5_u64))
+	}
+	/// Storage: `AssetRewards::Pools` (r:1 w:1)
+	/// Proof: `AssetRewards::Pools` (`max_values`: None, `max_size`: Some(150), added: 2625, mode: `MaxEncodedLen`)
+	/// Storage: `AssetRewards::PoolStakers` (r:1 w:1)
+	/// Proof: `AssetRewards::PoolStakers` (`max_values`: None, `max_size`: Some(116), added: 2591, mode: `MaxEncodedLen`)
+	/// Storage: `AssetsFreezer::Freezes` (r:1 w:1)
+	/// Proof: `AssetsFreezer::Freezes` (`max_values`: None, `max_size`: Some(105), added: 2580, mode: `MaxEncodedLen`)
+	/// Storage: `Assets::Account` (r:1 w:0)
+	/// Proof: `Assets::Account` (`max_values`: None, `max_size`: Some(134), added: 2609, mode: `MaxEncodedLen`)
+	/// Storage: `AssetsFreezer::FrozenBalances` (r:1 w:1)
+	/// Proof: `AssetsFreezer::FrozenBalances` (`max_values`: None, `max_size`: Some(84), added: 2559, mode: `MaxEncodedLen`)
+	fn stake() -> Weight {
+		// Proof Size summary in bytes:
+		//  Measured:  `935`
+		//  Estimated: `3615`
+		// Minimum execution time: 54_463_000 picoseconds.
+		Weight::from_parts(55_974_000, 3615)
+			.saturating_add(T::DbWeight::get().reads(5_u64))
+			.saturating_add(T::DbWeight::get().writes(4_u64))
+	}
+	/// Storage: `AssetRewards::Pools` (r:1 w:1)
+	/// Proof: `AssetRewards::Pools` (`max_values`: None, `max_size`: Some(150), added: 2625, mode: `MaxEncodedLen`)
+	/// Storage: `AssetRewards::PoolStakers` (r:1 w:1)
+	/// Proof: `AssetRewards::PoolStakers` (`max_values`: None, `max_size`: Some(116), added: 2591, mode: `MaxEncodedLen`)
+	/// Storage: `AssetsFreezer::Freezes` (r:1 w:1)
+	/// Proof: `AssetsFreezer::Freezes` (`max_values`: None, `max_size`: Some(105), added: 2580, mode: `MaxEncodedLen`)
+	/// Storage: `Assets::Account` (r:1 w:0)
+	/// Proof: `Assets::Account` (`max_values`: None, `max_size`: Some(134), added: 2609, mode: `MaxEncodedLen`)
+	/// Storage: `AssetsFreezer::FrozenBalances` (r:1 w:1)
+	/// Proof: `AssetsFreezer::FrozenBalances` (`max_values`: None, `max_size`: Some(84), added: 2559, mode: `MaxEncodedLen`)
+	fn unstake() -> Weight {
+		// Proof Size summary in bytes:
+		//  Measured:  `935`
+		//  Estimated: `3615`
+		// Minimum execution time: 55_749_000 picoseconds.
+		Weight::from_parts(57_652_000, 3615)
+			.saturating_add(T::DbWeight::get().reads(5_u64))
+			.saturating_add(T::DbWeight::get().writes(4_u64))
+	}
+	/// Storage: `AssetRewards::Pools` (r:1 w:0)
+	/// Proof: `AssetRewards::Pools` (`max_values`: None, `max_size`: Some(150), added: 2625, mode: `MaxEncodedLen`)
+	/// Storage: `AssetRewards::PoolStakers` (r:1 w:1)
+	/// Proof: `AssetRewards::PoolStakers` (`max_values`: None, `max_size`: Some(116), added: 2591, mode: `MaxEncodedLen`)
+	/// Storage: `Assets::Asset` (r:1 w:1)
+	/// Proof: `Assets::Asset` (`max_values`: None, `max_size`: Some(210), added: 2685, mode: `MaxEncodedLen`)
+	/// Storage: `Assets::Account` (r:2 w:2)
+	/// Proof: `Assets::Account` (`max_values`: None, `max_size`: Some(134), added: 2609, mode: `MaxEncodedLen`)
+	fn harvest_rewards() -> Weight {
+		// Proof Size summary in bytes:
+		//  Measured:  `1021`
+		//  Estimated: `6208`
+		// Minimum execution time: 69_372_000 picoseconds.
+		Weight::from_parts(70_278_000, 6208)
+			.saturating_add(T::DbWeight::get().reads(5_u64))
+			.saturating_add(T::DbWeight::get().writes(4_u64))
+	}
+	/// Storage: `AssetRewards::Pools` (r:1 w:1)
+	/// Proof: `AssetRewards::Pools` (`max_values`: None, `max_size`: Some(150), added: 2625, mode: `MaxEncodedLen`)
+	fn set_pool_reward_rate_per_block() -> Weight {
+		// Proof Size summary in bytes:
+		//  Measured:  `347`
+		//  Estimated: `3615`
+		// Minimum execution time: 19_284_000 picoseconds.
+		Weight::from_parts(19_791_000, 3615)
+			.saturating_add(T::DbWeight::get().reads(1_u64))
+			.saturating_add(T::DbWeight::get().writes(1_u64))
+	}
+	/// Storage: `AssetRewards::Pools` (r:1 w:1)
+	/// Proof: `AssetRewards::Pools` (`max_values`: None, `max_size`: Some(150), added: 2625, mode: `MaxEncodedLen`)
+	fn set_pool_admin() -> Weight {
+		// Proof Size summary in bytes:
+		//  Measured:  `347`
+		//  Estimated: `3615`
+		// Minimum execution time: 17_388_000 picoseconds.
+		Weight::from_parts(18_390_000, 3615)
+			.saturating_add(T::DbWeight::get().reads(1_u64))
+			.saturating_add(T::DbWeight::get().writes(1_u64))
+	}
+	/// Storage: `AssetRewards::Pools` (r:1 w:1)
+	/// Proof: `AssetRewards::Pools` (`max_values`: None, `max_size`: Some(150), added: 2625, mode: `MaxEncodedLen`)
+	fn set_pool_expiry_block() -> Weight {
+		// Proof Size summary in bytes:
+		//  Measured:  `347`
+		//  Estimated: `3615`
+		// Minimum execution time: 19_780_000 picoseconds.
+		Weight::from_parts(20_676_000, 3615)
+			.saturating_add(T::DbWeight::get().reads(1_u64))
+			.saturating_add(T::DbWeight::get().writes(1_u64))
+	}
+	/// Storage: `AssetRewards::Pools` (r:1 w:0)
+	/// Proof: `AssetRewards::Pools` (`max_values`: None, `max_size`: Some(150), added: 2625, mode: `MaxEncodedLen`)
+	/// Storage: `Assets::Asset` (r:1 w:1)
+	/// Proof: `Assets::Asset` (`max_values`: None, `max_size`: Some(210), added: 2685, mode: `MaxEncodedLen`)
+	/// Storage: `Assets::Account` (r:2 w:2)
+	/// Proof: `Assets::Account` (`max_values`: None, `max_size`: Some(134), added: 2609, mode: `MaxEncodedLen`)
+	/// Storage: `System::Account` (r:1 w:1)
+	/// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`)
+	fn deposit_reward_tokens() -> Weight {
+		// Proof Size summary in bytes:
+		//  Measured:  `840`
+		//  Estimated: `6208`
+		// Minimum execution time: 57_746_000 picoseconds.
+		Weight::from_parts(59_669_000, 6208)
+			.saturating_add(T::DbWeight::get().reads(5_u64))
+			.saturating_add(T::DbWeight::get().writes(4_u64))
+	}
+	/// Storage: `AssetRewards::Pools` (r:1 w:1)
+	/// Proof: `AssetRewards::Pools` (`max_values`: None, `max_size`: Some(150), added: 2625, mode: `MaxEncodedLen`)
+	/// Storage: `AssetRewards::PoolStakers` (r:1 w:0)
+	/// Proof: `AssetRewards::PoolStakers` (`max_values`: None, `max_size`: Some(116), added: 2591, mode: `MaxEncodedLen`)
+	/// Storage: `Assets::Asset` (r:1 w:1)
+	/// Proof: `Assets::Asset` (`max_values`: None, `max_size`: Some(210), added: 2685, mode: `MaxEncodedLen`)
+	/// Storage: `Assets::Account` (r:2 w:2)
+	/// Proof: `Assets::Account` (`max_values`: None, `max_size`: Some(134), added: 2609, mode: `MaxEncodedLen`)
+	/// Storage: `System::Account` (r:2 w:2)
+	/// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`)
+	/// Storage: `AssetRewards::PoolCost` (r:1 w:1)
+	/// Proof: `AssetRewards::PoolCost` (`max_values`: None, `max_size`: Some(68), added: 2543, mode: `MaxEncodedLen`)
+	/// Storage: `Balances::Holds` (r:1 w:1)
+	/// Proof: `Balances::Holds` (`max_values`: None, `max_size`: Some(373), added: 2848, mode: `MaxEncodedLen`)
+	fn cleanup_pool() -> Weight {
+		// Proof Size summary in bytes:
+		//  Measured:  `1236`
+		//  Estimated: `6208`
+		// Minimum execution time: 110_443_000 picoseconds.
+		Weight::from_parts(113_149_000, 6208)
+			.saturating_add(T::DbWeight::get().reads(9_u64))
+			.saturating_add(T::DbWeight::get().writes(8_u64))
+	}
+}
+
+// For backwards compatibility and tests.
+impl WeightInfo for () {
+	/// Storage: `Assets::Asset` (r:2 w:0)
+	/// Proof: `Assets::Asset` (`max_values`: None, `max_size`: Some(210), added: 2685, mode: `MaxEncodedLen`)
+	/// Storage: `AssetRewards::NextPoolId` (r:1 w:1)
+	/// Proof: `AssetRewards::NextPoolId` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`)
+	/// Storage: `System::Account` (r:1 w:1)
+	/// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`)
+	/// Storage: `Balances::Holds` (r:1 w:1)
+	/// Proof: `Balances::Holds` (`max_values`: None, `max_size`: Some(373), added: 2848, mode: `MaxEncodedLen`)
+	/// Storage: `AssetRewards::PoolCost` (r:0 w:1)
+	/// Proof: `AssetRewards::PoolCost` (`max_values`: None, `max_size`: Some(68), added: 2543, mode: `MaxEncodedLen`)
+	/// Storage: `AssetRewards::Pools` (r:0 w:1)
+	/// Proof: `AssetRewards::Pools` (`max_values`: None, `max_size`: Some(150), added: 2625, mode: `MaxEncodedLen`)
+	fn create_pool() -> Weight {
+		// Proof Size summary in bytes:
+		//  Measured:  `495`
+		//  Estimated: `6360`
+		// Minimum execution time: 62_655_000 picoseconds.
+		Weight::from_parts(63_723_000, 6360)
+			.saturating_add(RocksDbWeight::get().reads(5_u64))
+			.saturating_add(RocksDbWeight::get().writes(5_u64))
+	}
+	/// Storage: `AssetRewards::Pools` (r:1 w:1)
+	/// Proof: `AssetRewards::Pools` (`max_values`: None, `max_size`: Some(150), added: 2625, mode: `MaxEncodedLen`)
+	/// Storage: `AssetRewards::PoolStakers` (r:1 w:1)
+	/// Proof: `AssetRewards::PoolStakers` (`max_values`: None, `max_size`: Some(116), added: 2591, mode: `MaxEncodedLen`)
+	/// Storage: `AssetsFreezer::Freezes` (r:1 w:1)
+	/// Proof: `AssetsFreezer::Freezes` (`max_values`: None, `max_size`: Some(105), added: 2580, mode: `MaxEncodedLen`)
+	/// Storage: `Assets::Account` (r:1 w:0)
+	/// Proof: `Assets::Account` (`max_values`: None, `max_size`: Some(134), added: 2609, mode: `MaxEncodedLen`)
+	/// Storage: `AssetsFreezer::FrozenBalances` (r:1 w:1)
+	/// Proof: `AssetsFreezer::FrozenBalances` (`max_values`: None, `max_size`: Some(84), added: 2559, mode: `MaxEncodedLen`)
+	fn stake() -> Weight {
+		// Proof Size summary in bytes:
+		//  Measured:  `935`
+		//  Estimated: `3615`
+		// Minimum execution time: 54_463_000 picoseconds.
+		Weight::from_parts(55_974_000, 3615)
+			.saturating_add(RocksDbWeight::get().reads(5_u64))
+			.saturating_add(RocksDbWeight::get().writes(4_u64))
+	}
+	/// Storage: `AssetRewards::Pools` (r:1 w:1)
+	/// Proof: `AssetRewards::Pools` (`max_values`: None, `max_size`: Some(150), added: 2625, mode: `MaxEncodedLen`)
+	/// Storage: `AssetRewards::PoolStakers` (r:1 w:1)
+	/// Proof: `AssetRewards::PoolStakers` (`max_values`: None, `max_size`: Some(116), added: 2591, mode: `MaxEncodedLen`)
+	/// Storage: `AssetsFreezer::Freezes` (r:1 w:1)
+	/// Proof: `AssetsFreezer::Freezes` (`max_values`: None, `max_size`: Some(105), added: 2580, mode: `MaxEncodedLen`)
+	/// Storage: `Assets::Account` (r:1 w:0)
+	/// Proof: `Assets::Account` (`max_values`: None, `max_size`: Some(134), added: 2609, mode: `MaxEncodedLen`)
+	/// Storage: `AssetsFreezer::FrozenBalances` (r:1 w:1)
+	/// Proof: `AssetsFreezer::FrozenBalances` (`max_values`: None, `max_size`: Some(84), added: 2559, mode: `MaxEncodedLen`)
+	fn unstake() -> Weight {
+		// Proof Size summary in bytes:
+		//  Measured:  `935`
+		//  Estimated: `3615`
+		// Minimum execution time: 55_749_000 picoseconds.
+		Weight::from_parts(57_652_000, 3615)
+			.saturating_add(RocksDbWeight::get().reads(5_u64))
+			.saturating_add(RocksDbWeight::get().writes(4_u64))
+	}
+	/// Storage: `AssetRewards::Pools` (r:1 w:0)
+	/// Proof: `AssetRewards::Pools` (`max_values`: None, `max_size`: Some(150), added: 2625, mode: `MaxEncodedLen`)
+	/// Storage: `AssetRewards::PoolStakers` (r:1 w:1)
+	/// Proof: `AssetRewards::PoolStakers` (`max_values`: None, `max_size`: Some(116), added: 2591, mode: `MaxEncodedLen`)
+	/// Storage: `Assets::Asset` (r:1 w:1)
+	/// Proof: `Assets::Asset` (`max_values`: None, `max_size`: Some(210), added: 2685, mode: `MaxEncodedLen`)
+	/// Storage: `Assets::Account` (r:2 w:2)
+	/// Proof: `Assets::Account` (`max_values`: None, `max_size`: Some(134), added: 2609, mode: `MaxEncodedLen`)
+	fn harvest_rewards() -> Weight {
+		// Proof Size summary in bytes:
+		//  Measured:  `1021`
+		//  Estimated: `6208`
+		// Minimum execution time: 69_372_000 picoseconds.
+		Weight::from_parts(70_278_000, 6208)
+			.saturating_add(RocksDbWeight::get().reads(5_u64))
+			.saturating_add(RocksDbWeight::get().writes(4_u64))
+	}
+	/// Storage: `AssetRewards::Pools` (r:1 w:1)
+	/// Proof: `AssetRewards::Pools` (`max_values`: None, `max_size`: Some(150), added: 2625, mode: `MaxEncodedLen`)
+	fn set_pool_reward_rate_per_block() -> Weight {
+		// Proof Size summary in bytes:
+		//  Measured:  `347`
+		//  Estimated: `3615`
+		// Minimum execution time: 19_284_000 picoseconds.
+		Weight::from_parts(19_791_000, 3615)
+			.saturating_add(RocksDbWeight::get().reads(1_u64))
+			.saturating_add(RocksDbWeight::get().writes(1_u64))
+	}
+	/// Storage: `AssetRewards::Pools` (r:1 w:1)
+	/// Proof: `AssetRewards::Pools` (`max_values`: None, `max_size`: Some(150), added: 2625, mode: `MaxEncodedLen`)
+	fn set_pool_admin() -> Weight {
+		// Proof Size summary in bytes:
+		//  Measured:  `347`
+		//  Estimated: `3615`
+		// Minimum execution time: 17_388_000 picoseconds.
+		Weight::from_parts(18_390_000, 3615)
+			.saturating_add(RocksDbWeight::get().reads(1_u64))
+			.saturating_add(RocksDbWeight::get().writes(1_u64))
+	}
+	/// Storage: `AssetRewards::Pools` (r:1 w:1)
+	/// Proof: `AssetRewards::Pools` (`max_values`: None, `max_size`: Some(150), added: 2625, mode: `MaxEncodedLen`)
+	fn set_pool_expiry_block() -> Weight {
+		// Proof Size summary in bytes:
+		//  Measured:  `347`
+		//  Estimated: `3615`
+		// Minimum execution time: 19_780_000 picoseconds.
+		Weight::from_parts(20_676_000, 3615)
+			.saturating_add(RocksDbWeight::get().reads(1_u64))
+			.saturating_add(RocksDbWeight::get().writes(1_u64))
+	}
+	/// Storage: `AssetRewards::Pools` (r:1 w:0)
+	/// Proof: `AssetRewards::Pools` (`max_values`: None, `max_size`: Some(150), added: 2625, mode: `MaxEncodedLen`)
+	/// Storage: `Assets::Asset` (r:1 w:1)
+	/// Proof: `Assets::Asset` (`max_values`: None, `max_size`: Some(210), added: 2685, mode: `MaxEncodedLen`)
+	/// Storage: `Assets::Account` (r:2 w:2)
+	/// Proof: `Assets::Account` (`max_values`: None, `max_size`: Some(134), added: 2609, mode: `MaxEncodedLen`)
+	/// Storage: `System::Account` (r:1 w:1)
+	/// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`)
+	fn deposit_reward_tokens() -> Weight {
+		// Proof Size summary in bytes:
+		//  Measured:  `840`
+		//  Estimated: `6208`
+		// Minimum execution time: 57_746_000 picoseconds.
+		Weight::from_parts(59_669_000, 6208)
+			.saturating_add(RocksDbWeight::get().reads(5_u64))
+			.saturating_add(RocksDbWeight::get().writes(4_u64))
+	}
+	/// Storage: `AssetRewards::Pools` (r:1 w:1)
+	/// Proof: `AssetRewards::Pools` (`max_values`: None, `max_size`: Some(150), added: 2625, mode: `MaxEncodedLen`)
+	/// Storage: `AssetRewards::PoolStakers` (r:1 w:0)
+	/// Proof: `AssetRewards::PoolStakers` (`max_values`: None, `max_size`: Some(116), added: 2591, mode: `MaxEncodedLen`)
+	/// Storage: `Assets::Asset` (r:1 w:1)
+	/// Proof: `Assets::Asset` (`max_values`: None, `max_size`: Some(210), added: 2685, mode: `MaxEncodedLen`)
+	/// Storage: `Assets::Account` (r:2 w:2)
+	/// Proof: `Assets::Account` (`max_values`: None, `max_size`: Some(134), added: 2609, mode: `MaxEncodedLen`)
+	/// Storage: `System::Account` (r:2 w:2)
+	/// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`)
+	/// Storage: `AssetRewards::PoolCost` (r:1 w:1)
+	/// Proof: `AssetRewards::PoolCost` (`max_values`: None, `max_size`: Some(68), added: 2543, mode: `MaxEncodedLen`)
+	/// Storage: `Balances::Holds` (r:1 w:1)
+	/// Proof: `Balances::Holds` (`max_values`: None, `max_size`: Some(373), added: 2848, mode: `MaxEncodedLen`)
+	fn cleanup_pool() -> Weight {
+		// Proof Size summary in bytes:
+		//  Measured:  `1236`
+		//  Estimated: `6208`
+		// Minimum execution time: 110_443_000 picoseconds.
+		Weight::from_parts(113_149_000, 6208)
+			.saturating_add(RocksDbWeight::get().reads(9_u64))
+			.saturating_add(RocksDbWeight::get().writes(8_u64))
+	}
+}
diff --git a/substrate/frame/atomic-swap/Cargo.toml b/substrate/frame/atomic-swap/Cargo.toml
index 785bfee71b683653fba9f3f9c8be1e531927e01f..05a38ded91c516ac995d90cc54d92d688619d2f4 100644
--- a/substrate/frame/atomic-swap/Cargo.toml
+++ b/substrate/frame/atomic-swap/Cargo.toml
@@ -17,7 +17,7 @@ targets = ["x86_64-unknown-linux-gnu"]
 
 [dependencies]
 codec = { workspace = true }
-frame = { workspace = true, features = ["experimental", "runtime"] }
+frame = { workspace = true, features = ["runtime"] }
 scale-info = { features = ["derive"], workspace = true }
 
 [dev-dependencies]
diff --git a/substrate/frame/babe/src/mock.rs b/substrate/frame/babe/src/mock.rs
index 23857470adc4a5c751ef276a4c2d72b34f078490..8d00509e800b6368696bdea8ca686ef51f6bc71a 100644
--- a/substrate/frame/babe/src/mock.rs
+++ b/substrate/frame/babe/src/mock.rs
@@ -157,6 +157,7 @@ impl onchain::Config for OnChainSeqPhragmen {
 
 #[derive_impl(pallet_staking::config_preludes::TestDefaultConfig)]
 impl pallet_staking::Config for Test {
+	type OldCurrency = Balances;
 	type Currency = Balances;
 	type SessionsPerEra = SessionsPerEra;
 	type BondingDuration = BondingDuration;
diff --git a/substrate/frame/beefy/src/mock.rs b/substrate/frame/beefy/src/mock.rs
index 7ae41c609180e4741c668372c2c2392bbb140808..38e0cc4cfc26641063af61a33b93cd8e996a2a84 100644
--- a/substrate/frame/beefy/src/mock.rs
+++ b/substrate/frame/beefy/src/mock.rs
@@ -235,6 +235,7 @@ impl onchain::Config for OnChainSeqPhragmen {
 #[derive_impl(pallet_staking::config_preludes::TestDefaultConfig)]
 impl pallet_staking::Config for Test {
 	type RuntimeEvent = RuntimeEvent;
+	type OldCurrency = Balances;
 	type Currency = Balances;
 	type AdminOrigin = frame_system::EnsureRoot<Self::AccountId>;
 	type SessionInterface = Self;
diff --git a/substrate/frame/delegated-staking/src/lib.rs b/substrate/frame/delegated-staking/src/lib.rs
index 1d181eb29cab7321d94b083149a8e58a17ecd924..0dacfe9c55792f53e07fe708323ed6e0f8d5c784 100644
--- a/substrate/frame/delegated-staking/src/lib.rs
+++ b/substrate/frame/delegated-staking/src/lib.rs
@@ -520,7 +520,7 @@ impl<T: Config> Pallet<T> {
 		let stake = T::CoreStaking::stake(who)?;
 
 		// release funds from core staking.
-		T::CoreStaking::migrate_to_virtual_staker(who);
+		T::CoreStaking::migrate_to_virtual_staker(who)?;
 
 		// transfer just released staked amount plus any free amount.
 		let amount_to_transfer =
diff --git a/substrate/frame/delegated-staking/src/mock.rs b/substrate/frame/delegated-staking/src/mock.rs
index 811d5739f4e98f24c30c7514455dac049fbbc421..875279864f7ab3794777b1f8bf1756a4c15b703b 100644
--- a/substrate/frame/delegated-staking/src/mock.rs
+++ b/substrate/frame/delegated-staking/src/mock.rs
@@ -102,6 +102,7 @@ impl onchain::Config for OnChainSeqPhragmen {
 
 #[derive_impl(pallet_staking::config_preludes::TestDefaultConfig)]
 impl pallet_staking::Config for Runtime {
+	type OldCurrency = Balances;
 	type Currency = Balances;
 	type UnixTime = pallet_timestamp::Pallet<Self>;
 	type AdminOrigin = frame_system::EnsureRoot<Self::AccountId>;
diff --git a/substrate/frame/delegated-staking/src/tests.rs b/substrate/frame/delegated-staking/src/tests.rs
index b7b82a43771eb388ee1a5e0e11145e614f56930f..c764e2741a2a4a0149853f2279bd8b556ba4b20b 100644
--- a/substrate/frame/delegated-staking/src/tests.rs
+++ b/substrate/frame/delegated-staking/src/tests.rs
@@ -671,12 +671,14 @@ mod staking_integration {
 			));
 			assert_ok!(Staking::nominate(RuntimeOrigin::signed(agent), vec![GENESIS_VALIDATOR],));
 			let init_stake = Staking::stake(&agent).unwrap();
+			// no extra provider added.
+			assert_eq!(System::providers(&agent), 1);
 
 			// scenario: 200 is a pool account, and the stake comes from its 4 delegators (300..304)
 			// in equal parts. lets try to migrate this nominator into delegate based stake.
 
 			// all balance currently is in 200
-			assert_eq!(pallet_staking::asset::stakeable_balance::<T>(&agent), agent_amount);
+			assert_eq!(pallet_staking::asset::total_balance::<T>(&agent), agent_amount);
 
 			// to migrate, nominator needs to set an account as a proxy delegator where staked funds
 			// will be moved and delegated back to this old nominator account. This should be funded
@@ -685,8 +687,9 @@ mod staking_integration {
 				DelegatedStaking::generate_proxy_delegator(Agent::from(agent)).get();
 
 			assert_ok!(DelegatedStaking::migrate_to_agent(RawOrigin::Signed(agent).into(), 201));
-			// after migration, funds are moved to proxy delegator, still a provider exists.
-			assert_eq!(System::providers(&agent), 1);
+			// after migration, no provider left since free balance is 0 and staking pallet released
+			// all funds.
+			assert_eq!(System::providers(&agent), 0);
 			assert_eq!(Balances::free_balance(agent), 0);
 			// proxy delegator has one provider as well with no free balance.
 			assert_eq!(System::providers(&proxy_delegator), 1);
@@ -798,8 +801,6 @@ mod staking_integration {
 				RawOrigin::Signed(agent).into(),
 				reward_acc
 			));
-			// becoming an agent adds another provider.
-			assert_eq!(System::providers(&agent), 2);
 
 			// delegate to this account
 			fund(&delegator, 1000);
diff --git a/substrate/frame/delegated-staking/src/types.rs b/substrate/frame/delegated-staking/src/types.rs
index a78aa3f559060080344b84002d099ca8cb18caaa..14f49466f0e2867fec4da4a360a53fe816d72d22 100644
--- a/substrate/frame/delegated-staking/src/types.rs
+++ b/substrate/frame/delegated-staking/src/types.rs
@@ -131,10 +131,6 @@ impl<T: Config> AgentLedger<T> {
 	///
 	/// Increments provider count if this is a new agent.
 	pub(crate) fn update(self, key: &T::AccountId) {
-		if !<Agents<T>>::contains_key(key) {
-			// This is a new agent. Provide for this account.
-			frame_system::Pallet::<T>::inc_providers(key);
-		}
 		<Agents<T>>::insert(key, self)
 	}
 
@@ -142,8 +138,6 @@ impl<T: Config> AgentLedger<T> {
 	pub(crate) fn remove(key: &T::AccountId) {
 		debug_assert!(<Agents<T>>::contains_key(key), "Agent should exist in storage");
 		<Agents<T>>::remove(key);
-		// Remove provider reference.
-		let _ = frame_system::Pallet::<T>::dec_providers(key).defensive();
 	}
 
 	/// Effective total balance of the `Agent`.
diff --git a/substrate/frame/election-provider-multi-phase/test-staking-e2e/Cargo.toml b/substrate/frame/election-provider-multi-phase/test-staking-e2e/Cargo.toml
index 7a48ae868a5a2e63d6b0467753cf5ca5f8b56f2c..f11f9c04dbf4a6b5da7a04ebb17a1af89826df36 100644
--- a/substrate/frame/election-provider-multi-phase/test-staking-e2e/Cargo.toml
+++ b/substrate/frame/election-provider-multi-phase/test-staking-e2e/Cargo.toml
@@ -34,6 +34,7 @@ frame-system = { workspace = true, default-features = true }
 
 pallet-bags-list = { workspace = true, default-features = true }
 pallet-balances = { workspace = true, default-features = true }
+pallet-delegated-staking = { workspace = true, default-features = true }
 pallet-election-provider-multi-phase = { workspace = true, default-features = true }
 pallet-nomination-pools = { workspace = true, default-features = true }
 pallet-session = { workspace = true, default-features = true }
@@ -47,6 +48,7 @@ try-runtime = [
 	"frame-system/try-runtime",
 	"pallet-bags-list/try-runtime",
 	"pallet-balances/try-runtime",
+	"pallet-delegated-staking/try-runtime",
 	"pallet-election-provider-multi-phase/try-runtime",
 	"pallet-nomination-pools/try-runtime",
 	"pallet-session/try-runtime",
diff --git a/substrate/frame/election-provider-multi-phase/test-staking-e2e/src/lib.rs b/substrate/frame/election-provider-multi-phase/test-staking-e2e/src/lib.rs
index 26a6345e145ff1050581e32534d8b6fa09ba450d..b1029e89fe85f65650fb5406314241c220cd2b28 100644
--- a/substrate/frame/election-provider-multi-phase/test-staking-e2e/src/lib.rs
+++ b/substrate/frame/election-provider-multi-phase/test-staking-e2e/src/lib.rs
@@ -327,8 +327,8 @@ fn automatic_unbonding_pools() {
 		assert_eq!(<Runtime as pallet_nomination_pools::Config>::MaxUnbonding::get(), 1);
 
 		// init state of pool members.
-		let init_stakeable_balance_2 = pallet_staking::asset::stakeable_balance::<Runtime>(&2);
-		let init_stakeable_balance_3 = pallet_staking::asset::stakeable_balance::<Runtime>(&3);
+		let init_free_balance_2 = Balances::free_balance(2);
+		let init_free_balance_3 = Balances::free_balance(3);
 
 		let pool_bonded_account = Pools::generate_bonded_account(1);
 
@@ -378,7 +378,7 @@ fn automatic_unbonding_pools() {
 		System::reset_events();
 
 		let staked_before_withdraw_pool = staked_amount_for(pool_bonded_account);
-		assert_eq!(pallet_staking::asset::stakeable_balance::<Runtime>(&pool_bonded_account), 26);
+		assert_eq!(delegated_balance_for(pool_bonded_account), 5 + 10 + 10);
 
 		// now unbonding 3 will work, although the pool's ledger still has the unlocking chunks
 		// filled up.
@@ -390,13 +390,13 @@ fn automatic_unbonding_pools() {
 			[
 				// auto-withdraw happened as expected to release 2's unbonding funds, but the funds
 				// were not transferred to 2 and stay in the pool's transferrable balance instead.
-				pallet_staking::Event::Withdrawn { stash: 7939698191839293293, amount: 10 },
-				pallet_staking::Event::Unbonded { stash: 7939698191839293293, amount: 10 }
+				pallet_staking::Event::Withdrawn { stash: pool_bonded_account, amount: 10 },
+				pallet_staking::Event::Unbonded { stash: pool_bonded_account, amount: 10 }
 			]
 		);
 
 		// balance of the pool remains the same, it hasn't withdraw explicitly from the pool yet.
-		assert_eq!(pallet_staking::asset::stakeable_balance::<Runtime>(&pool_bonded_account), 26);
+		assert_eq!(delegated_balance_for(pool_bonded_account), 25);
 		// but the locked amount in the pool's account decreases due to the auto-withdraw:
 		assert_eq!(staked_before_withdraw_pool - 10, staked_amount_for(pool_bonded_account));
 
@@ -405,12 +405,12 @@ fn automatic_unbonding_pools() {
 
 		// however, note that the withdrawing from the pool still works for 2, the funds are taken
 		// from the pool's non staked balance.
-		assert_eq!(pallet_staking::asset::stakeable_balance::<Runtime>(&pool_bonded_account), 26);
-		assert_eq!(pallet_staking::asset::staked::<Runtime>(&pool_bonded_account), 15);
+		assert_eq!(delegated_balance_for(pool_bonded_account), 25);
+		assert_eq!(staked_amount_for(pool_bonded_account), 15);
 		assert_ok!(Pools::withdraw_unbonded(RuntimeOrigin::signed(2), 2, 10));
-		assert_eq!(pallet_staking::asset::stakeable_balance::<Runtime>(&pool_bonded_account), 16);
+		assert_eq!(delegated_balance_for(pool_bonded_account), 15);
 
-		assert_eq!(pallet_staking::asset::stakeable_balance::<Runtime>(&2), 20);
+		assert_eq!(Balances::free_balance(2), 20);
 		assert_eq!(TotalValueLocked::<Runtime>::get(), 15);
 
 		// 3 cannot withdraw yet.
@@ -429,15 +429,9 @@ fn automatic_unbonding_pools() {
 		assert_ok!(Pools::withdraw_unbonded(RuntimeOrigin::signed(3), 3, 10));
 
 		// final conditions are the expected.
-		assert_eq!(pallet_staking::asset::stakeable_balance::<Runtime>(&pool_bonded_account), 6); // 5 init bonded + ED
-		assert_eq!(
-			pallet_staking::asset::stakeable_balance::<Runtime>(&2),
-			init_stakeable_balance_2
-		);
-		assert_eq!(
-			pallet_staking::asset::stakeable_balance::<Runtime>(&3),
-			init_stakeable_balance_3
-		);
+		assert_eq!(delegated_balance_for(pool_bonded_account), 5); // 5 init bonded
+		assert_eq!(Balances::free_balance(2), init_free_balance_2);
+		assert_eq!(Balances::free_balance(3), init_free_balance_3);
 
 		assert_eq!(TotalValueLocked::<Runtime>::get(), init_tvl);
 	});
diff --git a/substrate/frame/election-provider-multi-phase/test-staking-e2e/src/mock.rs b/substrate/frame/election-provider-multi-phase/test-staking-e2e/src/mock.rs
index eaab848c1694485c47e4274ea83f223be53cd183..bcb25f8287b35e07dd7a690362df7d0aca75677b 100644
--- a/substrate/frame/election-provider-multi-phase/test-staking-e2e/src/mock.rs
+++ b/substrate/frame/election-provider-multi-phase/test-staking-e2e/src/mock.rs
@@ -21,6 +21,7 @@ use frame_support::{
 	assert_ok, parameter_types, traits,
 	traits::{Hooks, UnfilteredDispatchable, VariantCountOf},
 	weights::constants,
+	PalletId,
 };
 use frame_system::EnsureRoot;
 use sp_core::{ConstU32, Get};
@@ -36,7 +37,7 @@ use sp_runtime::{
 };
 use sp_staking::{
 	offence::{OffenceDetails, OnOffenceHandler},
-	EraIndex, SessionIndex,
+	Agent, DelegationInterface, EraIndex, SessionIndex, StakingInterface,
 };
 use std::collections::BTreeMap;
 
@@ -68,6 +69,7 @@ frame_support::construct_runtime!(
 		System: frame_system,
 		ElectionProviderMultiPhase: pallet_election_provider_multi_phase,
 		Staking: pallet_staking,
+		DelegatedStaking: pallet_delegated_staking,
 		Pools: pallet_nomination_pools,
 		Balances: pallet_balances,
 		BagsList: pallet_bags_list,
@@ -77,7 +79,7 @@ frame_support::construct_runtime!(
 	}
 );
 
-pub(crate) type AccountId = u64;
+pub(crate) type AccountId = u128;
 pub(crate) type AccountIndex = u32;
 pub(crate) type BlockNumber = u32;
 pub(crate) type Balance = u64;
@@ -87,8 +89,10 @@ pub(crate) type Moment = u32;
 
 #[derive_impl(frame_system::config_preludes::TestDefaultConfig)]
 impl frame_system::Config for Runtime {
+	type AccountId = AccountId;
 	type Block = Block;
 	type AccountData = pallet_balances::AccountData<Balance>;
+	type Lookup = sp_runtime::traits::IdentityLookup<Self::AccountId>;
 }
 
 const NORMAL_DISPATCH_RATIO: Perbill = Perbill::from_percent(75);
@@ -265,7 +269,8 @@ impl pallet_nomination_pools::Config for Runtime {
 	type RewardCounter = sp_runtime::FixedU128;
 	type BalanceToU256 = BalanceToU256;
 	type U256ToBalance = U256ToBalance;
-	type StakeAdapter = pallet_nomination_pools::adapter::TransferStake<Self, Staking>;
+	type StakeAdapter =
+		pallet_nomination_pools::adapter::DelegateStake<Self, Staking, DelegatedStaking>;
 	type PostUnbondingPoolsWindow = ConstU32<2>;
 	type PalletId = PoolsPalletId;
 	type MaxMetadataLen = ConstU32<256>;
@@ -274,6 +279,21 @@ impl pallet_nomination_pools::Config for Runtime {
 	type AdminOrigin = frame_system::EnsureRoot<Self::AccountId>;
 }
 
+parameter_types! {
+	pub const DelegatedStakingPalletId: PalletId = PalletId(*b"py/dlstk");
+	pub const SlashRewardFraction: Perbill = Perbill::from_percent(1);
+}
+
+impl pallet_delegated_staking::Config for Runtime {
+	type RuntimeEvent = RuntimeEvent;
+	type PalletId = DelegatedStakingPalletId;
+	type Currency = Balances;
+	type OnSlash = ();
+	type SlashRewardFraction = SlashRewardFraction;
+	type RuntimeHoldReason = RuntimeHoldReason;
+	type CoreStaking = Staking;
+}
+
 parameter_types! {
 	pub static MaxUnlockingChunks: u32 = 32;
 }
@@ -285,6 +305,7 @@ pub(crate) const SLASHING_DISABLING_FACTOR: usize = 3;
 
 #[derive_impl(pallet_staking::config_preludes::TestDefaultConfig)]
 impl pallet_staking::Config for Runtime {
+	type OldCurrency = Balances;
 	type Currency = Balances;
 	type CurrencyBalance = Balance;
 	type UnixTime = Timestamp;
@@ -302,7 +323,7 @@ impl pallet_staking::Config for Runtime {
 	type NominationsQuota = pallet_staking::FixedNominationsQuota<MAX_QUOTA_NOMINATIONS>;
 	type TargetList = pallet_staking::UseValidatorsMap<Self>;
 	type MaxUnlockingChunks = MaxUnlockingChunks;
-	type EventListeners = Pools;
+	type EventListeners = (Pools, DelegatedStaking);
 	type WeightInfo = pallet_staking::weights::SubstrateWeight<Runtime>;
 	type DisablingStrategy =
 		pallet_staking::UpToLimitWithReEnablingDisablingStrategy<SLASHING_DISABLING_FACTOR>;
@@ -502,7 +523,7 @@ impl Default for BalancesExtBuilder {
 			(100, 100),
 			(200, 100),
 			// stashes
-			(11, 1000),
+			(11, 1100),
 			(21, 2000),
 			(31, 3000),
 			(41, 4000),
@@ -581,7 +602,7 @@ impl ExtBuilder {
 			// set the keys for the first session.
 			keys: stakers
 				.into_iter()
-				.map(|(id, ..)| (id, id, SessionKeys { other: (id as u64).into() }))
+				.map(|(id, ..)| (id, id, SessionKeys { other: (id as AccountId as u64).into() }))
 				.collect(),
 			..Default::default()
 		}
@@ -926,7 +947,11 @@ pub(crate) fn set_minimum_election_score(
 }
 
 pub(crate) fn staked_amount_for(account_id: AccountId) -> Balance {
-	pallet_staking::asset::staked::<Runtime>(&account_id)
+	Staking::total_stake(&account_id).expect("account must be staker")
+}
+
+pub(crate) fn delegated_balance_for(account_id: AccountId) -> Balance {
+	DelegatedStaking::agent_balance(Agent::from(account_id)).unwrap_or_default()
 }
 
 pub(crate) fn staking_events() -> Vec<pallet_staking::Event<Runtime>> {
diff --git a/substrate/frame/examples/frame-crate/Cargo.toml b/substrate/frame/examples/frame-crate/Cargo.toml
index f174c6b9054b52d1f88d5d9853ffecd2df6f8014..46db1afc34643cef8b90a639e39aa2070a16919e 100644
--- a/substrate/frame/examples/frame-crate/Cargo.toml
+++ b/substrate/frame/examples/frame-crate/Cargo.toml
@@ -19,7 +19,7 @@ targets = ["x86_64-unknown-linux-gnu"]
 codec = { workspace = true }
 scale-info = { features = ["derive"], workspace = true }
 
-frame = { features = ["experimental", "runtime"], workspace = true }
+frame = { features = ["runtime"], workspace = true }
 
 
 [features]
diff --git a/substrate/frame/fast-unstake/src/mock.rs b/substrate/frame/fast-unstake/src/mock.rs
index f044fc610187578afd9276d48fb0ad5f2c6569ad..cf4f5f49240e98eaf42a944687b3fd4df81fe431 100644
--- a/substrate/frame/fast-unstake/src/mock.rs
+++ b/substrate/frame/fast-unstake/src/mock.rs
@@ -105,6 +105,7 @@ impl frame_election_provider_support::ElectionProvider for MockElection {
 
 #[derive_impl(pallet_staking::config_preludes::TestDefaultConfig)]
 impl pallet_staking::Config for Runtime {
+	type OldCurrency = Balances;
 	type Currency = Balances;
 	type UnixTime = pallet_timestamp::Pallet<Self>;
 	type AdminOrigin = frame_system::EnsureRoot<Self::AccountId>;
@@ -223,8 +224,9 @@ impl ExtBuilder {
 				.clone()
 				.into_iter()
 				.map(|(stash, _, balance)| (stash, balance * 2))
-				.chain(validators_range.clone().map(|x| (x, 7 + 100)))
-				.chain(nominators_range.clone().map(|x| (x, 7 + 100)))
+				// give stakers enough balance for stake, ed and fast unstake deposit.
+				.chain(validators_range.clone().map(|x| (x, 7 + 1 + 100)))
+				.chain(nominators_range.clone().map(|x| (x, 7 + 1 + 100)))
 				.collect::<Vec<_>>(),
 		}
 		.assimilate_storage(&mut storage);
diff --git a/substrate/frame/fast-unstake/src/tests.rs b/substrate/frame/fast-unstake/src/tests.rs
index 7c11f381ca102927e10a3b8241d87dfa28a07d5b..0fddb88e02b7bef231d5f9bb3961318c8656dc24 100644
--- a/substrate/frame/fast-unstake/src/tests.rs
+++ b/substrate/frame/fast-unstake/src/tests.rs
@@ -19,7 +19,15 @@
 
 use super::*;
 use crate::{mock::*, types::*, Event};
-use frame_support::{pallet_prelude::*, testing_prelude::*, traits::Currency};
+use frame_support::{
+	pallet_prelude::*,
+	testing_prelude::*,
+	traits::{
+		fungible::Inspect,
+		tokens::{Fortitude::Polite, Preservation::Expendable},
+		Currency,
+	},
+};
 use pallet_staking::{CurrentEra, RewardDestination};
 
 use sp_runtime::traits::BadOrigin;
@@ -146,7 +154,7 @@ fn deregister_works() {
 
 		// Controller then changes mind and deregisters.
 		assert_ok!(FastUnstake::deregister(RuntimeOrigin::signed(1)));
-		assert_eq!(<T as Config>::Currency::reserved_balance(&1) - pre_reserved, 0);
+		assert_eq!(<T as Config>::Currency::reserved_balance(&1), pre_reserved);
 
 		// Ensure stash no longer exists in the queue.
 		assert_eq!(Queue::<T>::get(1), None);
@@ -297,7 +305,7 @@ mod on_idle {
 			);
 			assert_eq!(Queue::<T>::count(), 3);
 
-			assert_eq!(<T as Config>::Currency::reserved_balance(&1) - pre_reserved, 0);
+			assert_eq!(<T as Config>::Currency::reserved_balance(&1), pre_reserved);
 
 			assert_eq!(
 				fast_unstake_events_since_last_call(),
@@ -793,6 +801,8 @@ mod on_idle {
 				RuntimeOrigin::signed(VALIDATOR_PREFIX),
 				vec![VALIDATOR_PREFIX]
 			));
+
+			assert_eq!(Balances::reducible_balance(&VALIDATOR_PREFIX, Expendable, Polite), 7);
 			assert_ok!(FastUnstake::register_fast_unstake(RuntimeOrigin::signed(VALIDATOR_PREFIX)));
 
 			// but they indeed are exposed!
diff --git a/substrate/frame/grandpa/src/mock.rs b/substrate/frame/grandpa/src/mock.rs
index 87369c23948ca0993fc986f555782c48397549b2..0a85d9ffd2b08c4668b874685d9547a8a20785ee 100644
--- a/substrate/frame/grandpa/src/mock.rs
+++ b/substrate/frame/grandpa/src/mock.rs
@@ -161,6 +161,7 @@ impl onchain::Config for OnChainSeqPhragmen {
 
 #[derive_impl(pallet_staking::config_preludes::TestDefaultConfig)]
 impl pallet_staking::Config for Test {
+	type OldCurrency = Balances;
 	type Currency = Balances;
 	type CurrencyBalance = <Self as pallet_balances::Config>::Balance;
 	type SessionsPerEra = SessionsPerEra;
diff --git a/substrate/frame/mixnet/Cargo.toml b/substrate/frame/mixnet/Cargo.toml
index 0ae3b3938c608b6ad10a0b6a06f256b463c50564..33bf7146980d5e32a46093483494b73279f012e4 100644
--- a/substrate/frame/mixnet/Cargo.toml
+++ b/substrate/frame/mixnet/Cargo.toml
@@ -17,7 +17,7 @@ targets = ["x86_64-unknown-linux-gnu"]
 
 [dependencies]
 codec = { features = ["derive", "max-encoded-len"], workspace = true }
-frame = { workspace = true, features = ["experimental", "runtime"] }
+frame = { workspace = true, features = ["runtime"] }
 log = { workspace = true }
 scale-info = { features = ["derive"], workspace = true }
 serde = { features = ["derive"], workspace = true }
diff --git a/substrate/frame/multisig/Cargo.toml b/substrate/frame/multisig/Cargo.toml
index 0d175617c9c23dedd5f2e72e806f187dc1e5e119..e18e14f2626bfcae1ed509005e1108913e42568e 100644
--- a/substrate/frame/multisig/Cargo.toml
+++ b/substrate/frame/multisig/Cargo.toml
@@ -17,7 +17,7 @@ targets = ["x86_64-unknown-linux-gnu"]
 
 [dependencies]
 codec = { workspace = true }
-frame = { workspace = true, features = ["experimental", "runtime"] }
+frame = { workspace = true, features = ["runtime"] }
 scale-info = { features = ["derive"], workspace = true }
 
 # third party
diff --git a/substrate/frame/node-authorization/Cargo.toml b/substrate/frame/node-authorization/Cargo.toml
index 7e55ad178091ffc42d858c80d4729a6f6d86d362..86a78e6e361535ef4556527e6a84890e50cd3a4f 100644
--- a/substrate/frame/node-authorization/Cargo.toml
+++ b/substrate/frame/node-authorization/Cargo.toml
@@ -16,7 +16,7 @@ targets = ["x86_64-unknown-linux-gnu"]
 
 [dependencies]
 codec = { features = ["derive"], workspace = true }
-frame = { workspace = true, features = ["experimental", "runtime"] }
+frame = { workspace = true, features = ["runtime"] }
 log = { workspace = true }
 scale-info = { features = ["derive"], workspace = true }
 
diff --git a/substrate/frame/nomination-pools/benchmarking/src/inner.rs b/substrate/frame/nomination-pools/benchmarking/src/inner.rs
index 7ddb78cca3f9b8d78293f37c2e894ed805b84ed3..20c5eafbcfc59dee4d2460224e83a70458804d6d 100644
--- a/substrate/frame/nomination-pools/benchmarking/src/inner.rs
+++ b/substrate/frame/nomination-pools/benchmarking/src/inner.rs
@@ -132,6 +132,10 @@ fn migrate_to_transfer_stake<T: Config>(pool_id: PoolId) {
 			.expect("member should have enough balance to transfer");
 		});
 
+	// Pool needs to have ED balance free to stake so give it some.
+	// Note: we didn't require ED until pallet-staking migrated from locks to holds.
+	let _ = CurrencyOf::<T>::mint_into(&pool_acc, CurrencyOf::<T>::minimum_balance());
+
 	pallet_staking::Pallet::<T>::migrate_to_direct_staker(&pool_acc);
 }
 
@@ -141,14 +145,6 @@ fn vote_to_balance<T: pallet_nomination_pools::Config>(
 	vote.try_into().map_err(|_| "could not convert u64 to Balance")
 }
 
-/// `assertion` should strictly be true if the adapter is using `Delegate` strategy and strictly
-/// false if the adapter is not using `Delegate` strategy.
-fn assert_if_delegate<T: pallet_nomination_pools::Config>(assertion: bool) {
-	let legacy_adapter_used = T::StakeAdapter::strategy_type() != StakeStrategyType::Delegate;
-	// one and only one of the two should be true.
-	assert!(assertion ^ legacy_adapter_used);
-}
-
 #[allow(unused)]
 struct ListScenario<T: pallet_nomination_pools::Config> {
 	/// Stash/Controller that is expected to be moved.
@@ -981,9 +977,6 @@ mod benchmarks {
 
 	#[benchmark]
 	fn apply_slash() {
-		// Note: With older `TransferStake` strategy, slashing is greedy and apply_slash should
-		// always fail.
-
 		// We want to fill member's unbonding pools. So let's bond with big enough amount.
 		let deposit_amount =
 			Pools::<T>::depositor_min_bond() * T::MaxUnbonding::get().into() * 4u32.into();
@@ -993,7 +986,7 @@ mod benchmarks {
 		// verify user balance in the pool.
 		assert_eq!(PoolMembers::<T>::get(&depositor).unwrap().total_balance(), deposit_amount);
 		// verify delegated balance.
-		assert_if_delegate::<T>(
+		assert!(
 			T::StakeAdapter::member_delegation_balance(Member::from(depositor.clone())) ==
 				Some(deposit_amount),
 		);
@@ -1017,7 +1010,7 @@ mod benchmarks {
 			deposit_amount / 2u32.into()
 		);
 		// verify delegated balance are not yet slashed.
-		assert_if_delegate::<T>(
+		assert!(
 			T::StakeAdapter::member_delegation_balance(Member::from(depositor.clone())) ==
 				Some(deposit_amount),
 		);
@@ -1041,13 +1034,11 @@ mod benchmarks {
 
 		#[block]
 		{
-			assert_if_delegate::<T>(
-				Pools::<T>::apply_slash(
-					RuntimeOrigin::Signed(slash_reporter.clone()).into(),
-					depositor_lookup.clone(),
-				)
-				.is_ok(),
-			);
+			assert!(Pools::<T>::apply_slash(
+				RuntimeOrigin::Signed(slash_reporter.clone()).into(),
+				depositor_lookup.clone(),
+			)
+			.is_ok(),);
 		}
 
 		// verify balances are correct and slash applied.
@@ -1055,7 +1046,7 @@ mod benchmarks {
 			PoolMembers::<T>::get(&depositor).unwrap().total_balance(),
 			deposit_amount / 2u32.into()
 		);
-		assert_if_delegate::<T>(
+		assert!(
 			T::StakeAdapter::member_delegation_balance(Member::from(depositor.clone())) ==
 				Some(deposit_amount / 2u32.into()),
 		);
@@ -1126,18 +1117,16 @@ mod benchmarks {
 		let _ = migrate_to_transfer_stake::<T>(1);
 		#[block]
 		{
-			assert_if_delegate::<T>(
-				Pools::<T>::migrate_pool_to_delegate_stake(
-					RuntimeOrigin::Signed(depositor.clone()).into(),
-					1u32.into(),
-				)
-				.is_ok(),
-			);
+			assert!(Pools::<T>::migrate_pool_to_delegate_stake(
+				RuntimeOrigin::Signed(depositor.clone()).into(),
+				1u32.into(),
+			)
+			.is_ok(),);
 		}
-		// this queries agent balance if `DelegateStake` strategy.
+		// this queries agent balance.
 		assert_eq!(
 			T::StakeAdapter::total_balance(Pool::from(pool_account.clone())),
-			Some(deposit_amount)
+			Some(deposit_amount + CurrencyOf::<T>::minimum_balance())
 		);
 	}
 
@@ -1152,13 +1141,11 @@ mod benchmarks {
 		let _ = migrate_to_transfer_stake::<T>(1);
 
 		// Now migrate pool to delegate stake keeping delegators unmigrated.
-		assert_if_delegate::<T>(
-			Pools::<T>::migrate_pool_to_delegate_stake(
-				RuntimeOrigin::Signed(depositor.clone()).into(),
-				1u32.into(),
-			)
-			.is_ok(),
-		);
+		assert!(Pools::<T>::migrate_pool_to_delegate_stake(
+			RuntimeOrigin::Signed(depositor.clone()).into(),
+			1u32.into(),
+		)
+		.is_ok(),);
 
 		// delegation does not exist.
 		assert!(
@@ -1171,16 +1158,14 @@ mod benchmarks {
 
 		#[block]
 		{
-			assert_if_delegate::<T>(
-				Pools::<T>::migrate_delegation(
-					RuntimeOrigin::Signed(depositor.clone()).into(),
-					depositor_lookup.clone(),
-				)
-				.is_ok(),
-			);
+			assert!(Pools::<T>::migrate_delegation(
+				RuntimeOrigin::Signed(depositor.clone()).into(),
+				depositor_lookup.clone(),
+			)
+			.is_ok(),);
 		}
 		// verify balances once more.
-		assert_if_delegate::<T>(
+		assert!(
 			T::StakeAdapter::member_delegation_balance(Member::from(depositor.clone())) ==
 				Some(deposit_amount),
 		);
diff --git a/substrate/frame/nomination-pools/benchmarking/src/mock.rs b/substrate/frame/nomination-pools/benchmarking/src/mock.rs
index 15d9e2c56031fe1e47b7b0cef3139ca177ddeb67..7c09cf22ad51e9575780525718d4fdd830bb6ecd 100644
--- a/substrate/frame/nomination-pools/benchmarking/src/mock.rs
+++ b/substrate/frame/nomination-pools/benchmarking/src/mock.rs
@@ -78,6 +78,7 @@ parameter_types! {
 }
 #[derive_impl(pallet_staking::config_preludes::TestDefaultConfig)]
 impl pallet_staking::Config for Runtime {
+	type OldCurrency = Balances;
 	type Currency = Balances;
 	type CurrencyBalance = Balance;
 	type UnixTime = pallet_timestamp::Pallet<Self>;
diff --git a/substrate/frame/nomination-pools/src/adapter.rs b/substrate/frame/nomination-pools/src/adapter.rs
index f125919dabfa6bce68dea67dd4fd60ed26ec0e3c..f1c68af4ea6ad170bbd54af728cdfd68dcef3688 100644
--- a/substrate/frame/nomination-pools/src/adapter.rs
+++ b/substrate/frame/nomination-pools/src/adapter.rs
@@ -16,6 +16,7 @@
 // limitations under the License.
 
 use crate::*;
+use frame_support::traits::tokens::{Fortitude::Polite, Preservation::Expendable};
 use sp_staking::{Agent, DelegationInterface, DelegationMigrator, Delegator};
 
 /// Types of stake strategies.
@@ -245,8 +246,10 @@ pub trait StakeStrategy {
 /// strategy in an existing runtime, storage migration is required. See
 /// [`migration::unversioned::DelegationStakeMigration`]. For new runtimes, it is highly recommended
 /// to use the [`DelegateStake`] strategy.
+#[deprecated = "consider migrating to DelegateStake"]
 pub struct TransferStake<T: Config, Staking: StakingInterface>(PhantomData<(T, Staking)>);
 
+#[allow(deprecated)]
 impl<T: Config, Staking: StakingInterface<Balance = BalanceOf<T>, AccountId = T::AccountId>>
 	StakeStrategy for TransferStake<T, Staking>
 {
@@ -262,7 +265,8 @@ impl<T: Config, Staking: StakingInterface<Balance = BalanceOf<T>, AccountId = T:
 		pool_account: Pool<Self::AccountId>,
 		_: Member<Self::AccountId>,
 	) -> BalanceOf<T> {
-		T::Currency::balance(&pool_account.0).saturating_sub(Self::active_stake(pool_account))
+		// free/liquid balance of the pool account.
+		T::Currency::reducible_balance(&pool_account.get(), Expendable, Polite)
 	}
 
 	fn total_balance(pool_account: Pool<Self::AccountId>) -> Option<BalanceOf<T>> {
diff --git a/substrate/frame/nomination-pools/src/mock.rs b/substrate/frame/nomination-pools/src/mock.rs
index f544e79ec48192b77718c7ab2ef0b40d6cea3bd0..f4552389a267abaacb459ed3b4fea0a82daa1a80 100644
--- a/substrate/frame/nomination-pools/src/mock.rs
+++ b/substrate/frame/nomination-pools/src/mock.rs
@@ -23,8 +23,10 @@ use frame_support::{
 	PalletId,
 };
 use frame_system::{EnsureSignedBy, RawOrigin};
-use sp_runtime::{BuildStorage, FixedU128};
-use sp_staking::{OnStakingUpdate, Stake};
+use sp_runtime::{BuildStorage, DispatchResult, FixedU128};
+use sp_staking::{
+	Agent, DelegationInterface, DelegationMigrator, Delegator, OnStakingUpdate, Stake,
+};
 
 pub type BlockNumber = u64;
 pub type AccountId = u128;
@@ -76,6 +78,7 @@ impl StakingMock {
 		let bonded = BondedBalanceMap::get();
 		let pre_total = bonded.get(&acc).unwrap();
 		Self::set_bonded_balance(acc, pre_total - amount);
+		DelegateMock::on_slash(acc, amount);
 		Pools::on_slash(&acc, pre_total - amount, &Default::default(), amount);
 	}
 }
@@ -112,8 +115,8 @@ impl sp_staking::StakingInterface for StakingMock {
 			.ok_or(DispatchError::Other("NotStash"))
 	}
 
-	fn is_virtual_staker(_who: &Self::AccountId) -> bool {
-		false
+	fn is_virtual_staker(who: &Self::AccountId) -> bool {
+		AgentBalanceMap::get().contains_key(who)
 	}
 
 	fn bond_extra(who: &Self::AccountId, extra: Self::Balance) -> DispatchResult {
@@ -162,7 +165,9 @@ impl sp_staking::StakingInterface for StakingMock {
 		staker_map.retain(|(unlocking_at, _amount)| *unlocking_at > current_era);
 
 		// if there was a withdrawal, notify the pallet.
-		Pools::on_withdraw(&who, unlocking_before.saturating_sub(unlocking(&staker_map)));
+		let withdraw_amount = unlocking_before.saturating_sub(unlocking(&staker_map));
+		Pools::on_withdraw(&who, withdraw_amount);
+		DelegateMock::on_withdraw(who, withdraw_amount);
 
 		UnbondingBalanceMap::set(&unbonding_map);
 		Ok(UnbondingBalanceMap::get().get(&who).unwrap().is_empty() &&
@@ -239,6 +244,176 @@ impl sp_staking::StakingInterface for StakingMock {
 	}
 }
 
+parameter_types! {
+	// Map of agent to their (delegated balance, unclaimed withdrawal, pending slash).
+	pub storage AgentBalanceMap: BTreeMap<AccountId, (Balance, Balance, Balance)> = Default::default();
+	pub storage DelegatorBalanceMap: BTreeMap<AccountId, Balance> = Default::default();
+}
+pub struct DelegateMock;
+impl DelegationInterface for DelegateMock {
+	type Balance = Balance;
+	type AccountId = AccountId;
+	fn agent_balance(agent: Agent<Self::AccountId>) -> Option<Self::Balance> {
+		AgentBalanceMap::get()
+			.get(&agent.get())
+			.copied()
+			.map(|(delegated, _, pending)| delegated - pending)
+	}
+
+	fn agent_transferable_balance(agent: Agent<Self::AccountId>) -> Option<Self::Balance> {
+		AgentBalanceMap::get()
+			.get(&agent.get())
+			.copied()
+			.map(|(_, unclaimed_withdrawals, _)| unclaimed_withdrawals)
+	}
+
+	fn delegator_balance(delegator: Delegator<Self::AccountId>) -> Option<Self::Balance> {
+		DelegatorBalanceMap::get().get(&delegator.get()).copied()
+	}
+
+	fn register_agent(
+		agent: Agent<Self::AccountId>,
+		_reward_account: &Self::AccountId,
+	) -> DispatchResult {
+		let mut agents = AgentBalanceMap::get();
+		agents.insert(agent.get(), (0, 0, 0));
+		AgentBalanceMap::set(&agents);
+		Ok(())
+	}
+
+	fn remove_agent(agent: Agent<Self::AccountId>) -> DispatchResult {
+		let mut agents = AgentBalanceMap::get();
+		let agent = agent.get();
+		assert!(agents.contains_key(&agent));
+		agents.remove(&agent);
+		AgentBalanceMap::set(&agents);
+		Ok(())
+	}
+
+	fn delegate(
+		delegator: Delegator<Self::AccountId>,
+		agent: Agent<Self::AccountId>,
+		amount: Self::Balance,
+	) -> DispatchResult {
+		let delegator = delegator.get();
+		let mut delegators = DelegatorBalanceMap::get();
+		delegators.entry(delegator).and_modify(|b| *b += amount).or_insert(amount);
+		DelegatorBalanceMap::set(&delegators);
+
+		let agent = agent.get();
+		let mut agents = AgentBalanceMap::get();
+		agents
+			.get_mut(&agent)
+			.map(|(d, _, _)| *d += amount)
+			.ok_or(DispatchError::Other("agent not registered"))?;
+		AgentBalanceMap::set(&agents);
+
+		if BondedBalanceMap::get().contains_key(&agent) {
+			StakingMock::bond_extra(&agent, amount)
+		} else {
+			// reward account does not matter in this context.
+			StakingMock::bond(&agent, amount, &999)
+		}
+	}
+
+	fn withdraw_delegation(
+		delegator: Delegator<Self::AccountId>,
+		agent: Agent<Self::AccountId>,
+		amount: Self::Balance,
+		_num_slashing_spans: u32,
+	) -> DispatchResult {
+		let mut delegators = DelegatorBalanceMap::get();
+		delegators.get_mut(&delegator.get()).map(|b| *b -= amount);
+		DelegatorBalanceMap::set(&delegators);
+
+		let mut agents = AgentBalanceMap::get();
+		agents.get_mut(&agent.get()).map(|(d, u, _)| {
+			*d -= amount;
+			*u -= amount;
+		});
+		AgentBalanceMap::set(&agents);
+
+		Ok(())
+	}
+
+	fn pending_slash(agent: Agent<Self::AccountId>) -> Option<Self::Balance> {
+		AgentBalanceMap::get()
+			.get(&agent.get())
+			.copied()
+			.map(|(_, _, pending_slash)| pending_slash)
+	}
+
+	fn delegator_slash(
+		agent: Agent<Self::AccountId>,
+		delegator: Delegator<Self::AccountId>,
+		value: Self::Balance,
+		_maybe_reporter: Option<Self::AccountId>,
+	) -> DispatchResult {
+		let mut delegators = DelegatorBalanceMap::get();
+		delegators.get_mut(&delegator.get()).map(|b| *b -= value);
+		DelegatorBalanceMap::set(&delegators);
+
+		let mut agents = AgentBalanceMap::get();
+		agents.get_mut(&agent.get()).map(|(_, _, p)| {
+			p.saturating_reduce(value);
+		});
+		AgentBalanceMap::set(&agents);
+
+		Ok(())
+	}
+}
+
+impl DelegateMock {
+	pub fn set_agent_balance(who: AccountId, delegated: Balance) {
+		let mut agents = AgentBalanceMap::get();
+		agents.insert(who, (delegated, 0, 0));
+		AgentBalanceMap::set(&agents);
+	}
+
+	pub fn set_delegator_balance(who: AccountId, amount: Balance) {
+		let mut delegators = DelegatorBalanceMap::get();
+		delegators.insert(who, amount);
+		DelegatorBalanceMap::set(&delegators);
+	}
+
+	pub fn on_slash(agent: AccountId, amount: Balance) {
+		let mut agents = AgentBalanceMap::get();
+		agents.get_mut(&agent).map(|(_, _, p)| *p += amount);
+		AgentBalanceMap::set(&agents);
+	}
+
+	fn on_withdraw(agent: AccountId, amount: Balance) {
+		let mut agents = AgentBalanceMap::get();
+		// if agent exists, add the amount to unclaimed withdrawals.
+		agents.get_mut(&agent).map(|(_, u, _)| *u += amount);
+		AgentBalanceMap::set(&agents);
+	}
+}
+
+impl DelegationMigrator for DelegateMock {
+	type Balance = Balance;
+	type AccountId = AccountId;
+	fn migrate_nominator_to_agent(
+		_agent: Agent<Self::AccountId>,
+		_reward_account: &Self::AccountId,
+	) -> DispatchResult {
+		unimplemented!("not used in current unit tests")
+	}
+
+	fn migrate_delegation(
+		_agent: Agent<Self::AccountId>,
+		_delegator: Delegator<Self::AccountId>,
+		_value: Self::Balance,
+	) -> DispatchResult {
+		unimplemented!("not used in current unit tests")
+	}
+
+	#[cfg(feature = "runtime-benchmarks")]
+	fn force_kill_agent(_agent: Agent<Self::AccountId>) {
+		unimplemented!("not used in current unit tests")
+	}
+}
+
 #[derive_impl(frame_system::config_preludes::TestDefaultConfig)]
 impl frame_system::Config for Runtime {
 	type Nonce = u64;
@@ -295,7 +470,7 @@ impl pools::Config for Runtime {
 	type RewardCounter = RewardCounter;
 	type BalanceToU256 = BalanceToU256;
 	type U256ToBalance = U256ToBalance;
-	type StakeAdapter = adapter::TransferStake<Self, StakingMock>;
+	type StakeAdapter = adapter::DelegateStake<Self, StakingMock, DelegateMock>;
 	type PostUnbondingPoolsWindow = PostUnbondingPoolsWindow;
 	type PalletId = PoolsPalletId;
 	type MaxMetadataLen = MaxMetadataLen;
@@ -522,6 +697,21 @@ pub fn reward_imbalance(pool: PoolId) -> RewardImbalance {
 	}
 }
 
+pub fn set_pool_balance(who: AccountId, amount: Balance) {
+	StakingMock::set_bonded_balance(who, amount);
+	DelegateMock::set_agent_balance(who, amount);
+}
+
+pub fn member_delegation(who: AccountId) -> Balance {
+	<T as Config>::StakeAdapter::member_delegation_balance(Member::from(who))
+		.expect("who must be a pool member")
+}
+
+pub fn pool_balance(id: PoolId) -> Balance {
+	<T as Config>::StakeAdapter::total_balance(Pool::from(Pools::generate_bonded_account(id)))
+		.expect("who must be a bonded pool account")
+}
+
 #[cfg(test)]
 mod test {
 	use super::*;
diff --git a/substrate/frame/nomination-pools/src/tests.rs b/substrate/frame/nomination-pools/src/tests.rs
index 06261699a5b23ff58c227875b143a1eaba08e439..c46638d2f8f7bb474088b967e491c54d12bc3105 100644
--- a/substrate/frame/nomination-pools/src/tests.rs
+++ b/substrate/frame/nomination-pools/src/tests.rs
@@ -24,6 +24,7 @@ use sp_runtime::{
 	traits::{BadOrigin, Dispatchable},
 	FixedU128,
 };
+use sp_staking::{Agent, DelegationInterface};
 
 macro_rules! unbonding_pools_with_era {
 	($($k:expr => $v:expr),* $(,)?) => {{
@@ -127,41 +128,41 @@ mod bonded_pool {
 			};
 
 			// 1 points : 1 balance ratio
-			StakingMock::set_bonded_balance(bonded_pool.bonded_account(), 100);
+			set_pool_balance(bonded_pool.bonded_account(), 100);
 			assert_eq!(bonded_pool.balance_to_point(10), 10);
 			assert_eq!(bonded_pool.balance_to_point(0), 0);
 
 			// 2 points : 1 balance ratio
-			StakingMock::set_bonded_balance(bonded_pool.bonded_account(), 50);
+			set_pool_balance(bonded_pool.bonded_account(), 50);
 			assert_eq!(bonded_pool.balance_to_point(10), 20);
 
 			// 1 points : 2 balance ratio
-			StakingMock::set_bonded_balance(bonded_pool.bonded_account(), 100);
+			set_pool_balance(bonded_pool.bonded_account(), 100);
 			bonded_pool.points = 50;
 			assert_eq!(bonded_pool.balance_to_point(10), 5);
 
 			// 100 points : 0 balance ratio
-			StakingMock::set_bonded_balance(bonded_pool.bonded_account(), 0);
+			set_pool_balance(bonded_pool.bonded_account(), 0);
 			bonded_pool.points = 100;
 			assert_eq!(bonded_pool.balance_to_point(10), 100 * 10);
 
 			// 0 points : 100 balance
-			StakingMock::set_bonded_balance(bonded_pool.bonded_account(), 100);
+			set_pool_balance(bonded_pool.bonded_account(), 100);
 			bonded_pool.points = 0;
 			assert_eq!(bonded_pool.balance_to_point(10), 10);
 
 			// 10 points : 3 balance ratio
-			StakingMock::set_bonded_balance(bonded_pool.bonded_account(), 30);
+			set_pool_balance(bonded_pool.bonded_account(), 30);
 			bonded_pool.points = 100;
 			assert_eq!(bonded_pool.balance_to_point(10), 33);
 
 			// 2 points : 3 balance ratio
-			StakingMock::set_bonded_balance(bonded_pool.bonded_account(), 300);
+			set_pool_balance(bonded_pool.bonded_account(), 300);
 			bonded_pool.points = 200;
 			assert_eq!(bonded_pool.balance_to_point(10), 6);
 
 			// 4 points : 9 balance ratio
-			StakingMock::set_bonded_balance(bonded_pool.bonded_account(), 900);
+			set_pool_balance(bonded_pool.bonded_account(), 900);
 			bonded_pool.points = 400;
 			assert_eq!(bonded_pool.balance_to_point(90), 40);
 		})
@@ -182,7 +183,7 @@ mod bonded_pool {
 				},
 			};
 
-			StakingMock::set_bonded_balance(bonded_pool.bonded_account(), 100);
+			set_pool_balance(bonded_pool.bonded_account(), 100);
 			assert_eq!(bonded_pool.points_to_balance(10), 10);
 			assert_eq!(bonded_pool.points_to_balance(0), 0);
 
@@ -191,27 +192,27 @@ mod bonded_pool {
 			assert_eq!(bonded_pool.points_to_balance(10), 20);
 
 			// 100 balance : 0 points ratio
-			StakingMock::set_bonded_balance(bonded_pool.bonded_account(), 100);
+			set_pool_balance(bonded_pool.bonded_account(), 100);
 			bonded_pool.points = 0;
 			assert_eq!(bonded_pool.points_to_balance(10), 0);
 
 			// 0 balance : 100 points ratio
-			StakingMock::set_bonded_balance(bonded_pool.bonded_account(), 0);
+			set_pool_balance(bonded_pool.bonded_account(), 0);
 			bonded_pool.points = 100;
 			assert_eq!(bonded_pool.points_to_balance(10), 0);
 
 			// 10 balance : 3 points ratio
-			StakingMock::set_bonded_balance(bonded_pool.bonded_account(), 100);
+			set_pool_balance(bonded_pool.bonded_account(), 100);
 			bonded_pool.points = 30;
 			assert_eq!(bonded_pool.points_to_balance(10), 33);
 
 			// 2 balance : 3 points ratio
-			StakingMock::set_bonded_balance(bonded_pool.bonded_account(), 200);
+			set_pool_balance(bonded_pool.bonded_account(), 200);
 			bonded_pool.points = 300;
 			assert_eq!(bonded_pool.points_to_balance(10), 6);
 
 			// 4 balance : 9 points ratio
-			StakingMock::set_bonded_balance(bonded_pool.bonded_account(), 400);
+			set_pool_balance(bonded_pool.bonded_account(), 400);
 			bonded_pool.points = 900;
 			assert_eq!(bonded_pool.points_to_balance(90), 40);
 		})
@@ -269,30 +270,21 @@ mod bonded_pool {
 				<<Runtime as Config>::MaxPointsToBalance as Get<u8>>::get().into();
 
 			// Simulate a 100% slashed pool
-			StakingMock::set_bonded_balance(pool.bonded_account(), 0);
+			set_pool_balance(pool.bonded_account(), 0);
 			assert_noop!(pool.ok_to_join(), Error::<Runtime>::OverflowRisk);
 
 			// Simulate a slashed pool at `MaxPointsToBalance` + 1 slashed pool
-			StakingMock::set_bonded_balance(
-				pool.bonded_account(),
-				max_points_to_balance.saturating_add(1),
-			);
+			set_pool_balance(pool.bonded_account(), max_points_to_balance.saturating_add(1));
 			assert_ok!(pool.ok_to_join());
 
 			// Simulate a slashed pool at `MaxPointsToBalance`
-			StakingMock::set_bonded_balance(pool.bonded_account(), max_points_to_balance);
+			set_pool_balance(pool.bonded_account(), max_points_to_balance);
 			assert_noop!(pool.ok_to_join(), Error::<Runtime>::OverflowRisk);
 
-			StakingMock::set_bonded_balance(
-				pool.bonded_account(),
-				Balance::MAX / max_points_to_balance,
-			);
+			set_pool_balance(pool.bonded_account(), Balance::MAX / max_points_to_balance);
 
 			// and a sanity check
-			StakingMock::set_bonded_balance(
-				pool.bonded_account(),
-				Balance::MAX / max_points_to_balance - 1,
-			);
+			set_pool_balance(pool.bonded_account(), Balance::MAX / max_points_to_balance - 1);
 			assert_ok!(pool.ok_to_join());
 		});
 	}
@@ -310,7 +302,7 @@ mod bonded_pool {
 					state: PoolState::Open,
 				},
 			};
-			StakingMock::set_bonded_balance(bonded_pool.bonded_account(), u128::MAX);
+			set_pool_balance(bonded_pool.bonded_account(), u128::MAX);
 
 			// Max out the points and balance of the pool and make sure the conversion works as
 			// expected and does not overflow.
@@ -640,8 +632,6 @@ mod sub_pools {
 }
 
 mod join {
-	use sp_runtime::TokenError;
-
 	use super::*;
 
 	#[test]
@@ -728,7 +718,7 @@ mod join {
 			);
 
 			// Force the pools bonded balance to 0, simulating a 100% slash
-			StakingMock::set_bonded_balance(Pools::generate_bonded_account(1), 0);
+			set_pool_balance(Pools::generate_bonded_account(1), 0);
 			assert_noop!(
 				Pools::join(RuntimeOrigin::signed(11), 420, 1),
 				Error::<Runtime>::OverflowRisk
@@ -754,29 +744,13 @@ mod join {
 			let max_points_to_balance: u128 =
 				<<Runtime as Config>::MaxPointsToBalance as Get<u8>>::get().into();
 
-			StakingMock::set_bonded_balance(
-				Pools::generate_bonded_account(123),
-				max_points_to_balance,
-			);
+			set_pool_balance(Pools::generate_bonded_account(123), max_points_to_balance);
 			assert_noop!(
 				Pools::join(RuntimeOrigin::signed(11), 420, 123),
 				Error::<Runtime>::OverflowRisk
 			);
 
-			StakingMock::set_bonded_balance(
-				Pools::generate_bonded_account(123),
-				Balance::MAX / max_points_to_balance,
-			);
-			// Balance needs to be gt Balance::MAX / `MaxPointsToBalance`
-			assert_noop!(
-				Pools::join(RuntimeOrigin::signed(11), 5, 123),
-				TokenError::FundsUnavailable,
-			);
-
-			StakingMock::set_bonded_balance(
-				Pools::generate_bonded_account(1),
-				max_points_to_balance,
-			);
+			set_pool_balance(Pools::generate_bonded_account(1), max_points_to_balance);
 
 			// Cannot join a pool that isn't open
 			unsafe_set_state(123, PoolState::Blocked);
@@ -807,7 +781,7 @@ mod join {
 	#[cfg_attr(not(debug_assertions), should_panic)]
 	fn join_panics_when_reward_pool_not_found() {
 		ExtBuilder::default().build_and_execute(|| {
-			StakingMock::set_bonded_balance(Pools::generate_bonded_account(123), 100);
+			set_pool_balance(Pools::generate_bonded_account(123), 100);
 			BondedPool::<Runtime> {
 				id: 123,
 				inner: BondedPoolInner {
@@ -2321,8 +2295,8 @@ mod claim_payout {
 	fn rewards_are_rounded_down_depositor_collects_them() {
 		ExtBuilder::default().add_members(vec![(20, 20)]).build_and_execute(|| {
 			// initial balance of 10.
-
-			assert_eq!(Currency::free_balance(&10), 35);
+			let init_balance_10 = Currency::free_balance(&10);
+			assert_eq!(member_delegation(10), 10);
 			assert_eq!(
 				Currency::free_balance(&default_reward_account()),
 				Currency::minimum_balance()
@@ -2373,8 +2347,10 @@ mod claim_payout {
 			);
 
 			assert!(!Metadata::<T>::contains_key(1));
-			// original ed + ed put into reward account + reward + bond + dust.
-			assert_eq!(Currency::free_balance(&10), 35 + 5 + 13 + 10 + 1);
+			// original ed + ed put into reward account + reward + dust.
+			assert_eq!(Currency::free_balance(&10), init_balance_10 + 5 + 13 + 1);
+			// delegation reduced from 10 to 0.
+			assert_eq!(member_delegation(10), 0);
 		})
 	}
 
@@ -2444,9 +2420,10 @@ mod claim_payout {
 			let claimable_reward = 8 - ExistentialDeposit::get();
 			// NOTE: easier to read if we use 3, so let's use the number instead of variable.
 			assert_eq!(claimable_reward, 3, "test is correct if rewards are divisible by 3");
+			let init_balance = Currency::free_balance(&10);
 
 			// given
-			assert_eq!(Currency::free_balance(&10), 35);
+			assert_eq!(member_delegation(10), 10);
 
 			// when
 
@@ -2455,7 +2432,10 @@ mod claim_payout {
 			assert_ok!(Pools::claim_payout_other(RuntimeOrigin::signed(80), 10));
 
 			// then
-			assert_eq!(Currency::free_balance(&10), 36);
+			// delegated balance does not change.
+			assert_eq!(member_delegation(10), 10);
+			// reward of 1 is paid out to 10.
+			assert_eq!(Currency::free_balance(&10), init_balance + 1);
 			assert_eq!(Currency::free_balance(&default_reward_account()), 7);
 		})
 	}
@@ -2818,6 +2798,8 @@ mod unbond {
 		ExtBuilder::default()
 			.add_members(vec![(40, 40), (550, 550)])
 			.build_and_execute(|| {
+				let init_balance_40 = Currency::free_balance(&40);
+				let init_balance_550 = Currency::free_balance(&550);
 				let ed = Currency::minimum_balance();
 				// Given a slash from 600 -> 500
 				StakingMock::slash_by(1, 500);
@@ -2864,7 +2846,9 @@ mod unbond {
 					PoolMembers::<Runtime>::get(40).unwrap().unbonding_eras,
 					member_unbonding_eras!(3 => 6)
 				);
-				assert_eq!(Currency::free_balance(&40), 40 + 40); // We claim rewards when unbonding
+				assert_eq!(member_delegation(40), 40);
+				// We claim rewards when unbonding
+				assert_eq!(Currency::free_balance(&40), init_balance_40 + 40);
 
 				// When
 				unsafe_set_state(1, PoolState::Destroying);
@@ -2893,7 +2877,8 @@ mod unbond {
 					PoolMembers::<Runtime>::get(550).unwrap().unbonding_eras,
 					member_unbonding_eras!(3 => 92)
 				);
-				assert_eq!(Currency::free_balance(&550), 550 + 550);
+				assert_eq!(member_delegation(550), 550);
+				assert_eq!(Currency::free_balance(&550), init_balance_550 + 550);
 				assert_eq!(
 					pool_events_since_last_call(),
 					vec![
@@ -2934,7 +2919,8 @@ mod unbond {
 				);
 				assert_eq!(StakingMock::active_stake(&default_bonded_account()).unwrap(), 0);
 
-				assert_eq!(Currency::free_balance(&550), 550 + 550 + 92);
+				// 550 is removed from pool.
+				assert_eq!(member_delegation(550), 0);
 				assert_eq!(
 					pool_events_since_last_call(),
 					vec![
@@ -3532,7 +3518,7 @@ mod pool_withdraw_unbonded {
 
 			assert_eq!(StakingMock::active_stake(&default_bonded_account()), Ok(15));
 			assert_eq!(StakingMock::total_stake(&default_bonded_account()), Ok(20));
-			assert_eq!(Balances::free_balance(&default_bonded_account()), 20);
+			assert_eq!(pool_balance(1), 20);
 
 			// When
 			CurrentEra::set(StakingMock::current_era() + StakingMock::bonding_duration() + 1);
@@ -3541,7 +3527,7 @@ mod pool_withdraw_unbonded {
 			// Then their unbonding balance is no longer locked
 			assert_eq!(StakingMock::active_stake(&default_bonded_account()), Ok(15));
 			assert_eq!(StakingMock::total_stake(&default_bonded_account()), Ok(15));
-			assert_eq!(Balances::free_balance(&default_bonded_account()), 20);
+			assert_eq!(pool_balance(1), 20);
 		});
 	}
 	#[test]
@@ -3552,7 +3538,7 @@ mod pool_withdraw_unbonded {
 
 			assert_eq!(StakingMock::active_stake(&default_bonded_account()), Ok(15));
 			assert_eq!(StakingMock::total_stake(&default_bonded_account()), Ok(20));
-			assert_eq!(Balances::free_balance(&default_bonded_account()), 20);
+			assert_eq!(pool_balance(1), 20);
 			assert_eq!(TotalValueLocked::<T>::get(), 20);
 
 			// When
@@ -3568,14 +3554,14 @@ mod pool_withdraw_unbonded {
 			// Then their unbonding balance is no longer locked
 			assert_eq!(StakingMock::active_stake(&default_bonded_account()), Ok(15));
 			assert_eq!(StakingMock::total_stake(&default_bonded_account()), Ok(15));
-			assert_eq!(Currency::free_balance(&default_bonded_account()), 20);
+			assert_eq!(pool_balance(1), 20);
 
 			// The difference between TVL and member_balance is exactly the difference between
-			// `total_stake` and the `free_balance`.
-			// This relation is not guaranteed in the wild as arbitrary transfers towards
-			// `free_balance` can be made to the pool that are not accounted for.
-			let non_locked_balance = Balances::free_balance(&default_bonded_account()) -
-				StakingMock::total_stake(&default_bonded_account()).unwrap();
+			// `pool balance` (sum of all balance delegated to pool) and the `staked balance`.
+			// This is the withdrawn funds from the pool stake that has not yet been claimed by the
+			// respective members.
+			let non_locked_balance =
+				pool_balance(1) - StakingMock::total_stake(&default_bonded_account()).unwrap();
 			assert_eq!(member_balance, TotalValueLocked::<T>::get() + non_locked_balance);
 		});
 	}
@@ -3597,7 +3583,7 @@ mod withdraw_unbonded {
 				assert_eq!(StakingMock::bonding_duration(), 3);
 				assert_ok!(Pools::fully_unbond(RuntimeOrigin::signed(550), 550));
 				assert_ok!(Pools::fully_unbond(RuntimeOrigin::signed(40), 40));
-				assert_eq!(Currency::free_balance(&default_bonded_account()), 600);
+				assert_eq!(pool_balance(1), 600);
 
 				let mut current_era = 1;
 				CurrentEra::set(current_era);
@@ -3626,10 +3612,7 @@ mod withdraw_unbonded {
 						.1 /= 2;
 					UnbondingBalanceMap::set(&x);
 
-					Currency::set_balance(
-						&default_bonded_account(),
-						Currency::free_balance(&default_bonded_account()) / 2, // 300
-					);
+					set_pool_balance(1, pool_balance(1) / 2);
 					assert_eq!(StakingMock::active_stake(&default_bonded_account()).unwrap(), 10);
 					StakingMock::slash_by(1, 5);
 					assert_eq!(StakingMock::active_stake(&default_bonded_account()).unwrap(), 5);
@@ -3671,11 +3654,6 @@ mod withdraw_unbonded {
 						Event::PoolSlashed { pool_id: 1, balance: 5 }
 					]
 				);
-				assert_eq!(
-					balances_events_since_last_call(),
-					vec![BEvent::Burned { who: default_bonded_account(), amount: 300 }]
-				);
-
 				// When
 				assert_ok!(Pools::withdraw_unbonded(RuntimeOrigin::signed(550), 550, 0));
 
@@ -3691,10 +3669,9 @@ mod withdraw_unbonded {
 						Event::MemberRemoved { pool_id: 1, member: 550, released_balance: 0 }
 					]
 				);
-				assert_eq!(
-					balances_events_since_last_call(),
-					vec![BEvent::Transfer { from: default_bonded_account(), to: 550, amount: 275 }]
-				);
+
+				// member has 40 tokens in delegation, but only 20 can be withdrawan.
+				assert_eq!(member_delegation(40), 40);
 
 				// When
 				assert_ok!(Pools::withdraw_unbonded(RuntimeOrigin::signed(40), 40, 0));
@@ -3708,18 +3685,18 @@ mod withdraw_unbonded {
 				assert_eq!(
 					pool_events_since_last_call(),
 					vec![
+						// out of 40, 20 is withdrawn.
 						Event::Withdrawn { member: 40, pool_id: 1, balance: 20, points: 40 },
-						Event::MemberRemoved { pool_id: 1, member: 40, released_balance: 0 }
+						// member is removed and the dangling delegation of 20 tokens left in their
+						// account is released.
+						Event::MemberRemoved { pool_id: 1, member: 40, released_balance: 20 }
 					]
 				);
-				assert_eq!(
-					balances_events_since_last_call(),
-					vec![BEvent::Transfer { from: default_bonded_account(), to: 40, amount: 20 }]
-				);
 
 				// now, finally, the depositor can take out its share.
 				unsafe_set_state(1, PoolState::Destroying);
 				assert_ok!(fully_unbond_permissioned(10));
+				assert_eq!(member_delegation(10), 10);
 
 				current_era += 3;
 				CurrentEra::set(current_era);
@@ -3731,7 +3708,9 @@ mod withdraw_unbonded {
 					vec![
 						Event::Unbonded { member: 10, pool_id: 1, balance: 5, points: 5, era: 9 },
 						Event::Withdrawn { member: 10, pool_id: 1, balance: 5, points: 5 },
-						Event::MemberRemoved { pool_id: 1, member: 10, released_balance: 0 },
+						// when member is removed, any leftover delegation is released.
+						Event::MemberRemoved { pool_id: 1, member: 10, released_balance: 5 },
+						// when the last member leaves, the pool is destroyed.
 						Event::Destroyed { pool_id: 1 }
 					]
 				);
@@ -3739,7 +3718,6 @@ mod withdraw_unbonded {
 				assert_eq!(
 					balances_events_since_last_call(),
 					vec![
-						BEvent::Transfer { from: default_bonded_account(), to: 10, amount: 5 },
 						BEvent::Thawed { who: default_reward_account(), amount: 5 },
 						BEvent::Transfer { from: default_reward_account(), to: 10, amount: 5 }
 					]
@@ -3753,11 +3731,9 @@ mod withdraw_unbonded {
 			.add_members(vec![(40, 40), (550, 550)])
 			.build_and_execute(|| {
 				let _ = balances_events_since_last_call();
-
 				// Given
 				// current bond is 600, we slash it all to 300.
 				StakingMock::slash_by(1, 300);
-				Currency::set_balance(&default_bonded_account(), 300);
 				assert_eq!(StakingMock::total_stake(&default_bonded_account()), Ok(300));
 
 				assert_ok!(fully_unbond_permissioned(40));
@@ -3787,10 +3763,6 @@ mod withdraw_unbonded {
 						}
 					]
 				);
-				assert_eq!(
-					balances_events_since_last_call(),
-					vec![BEvent::Burned { who: default_bonded_account(), amount: 300 },]
-				);
 
 				CurrentEra::set(StakingMock::bonding_duration());
 
@@ -3798,10 +3770,6 @@ mod withdraw_unbonded {
 				assert_ok!(Pools::withdraw_unbonded(RuntimeOrigin::signed(40), 40, 0));
 
 				// Then
-				assert_eq!(
-					balances_events_since_last_call(),
-					vec![BEvent::Transfer { from: default_bonded_account(), to: 40, amount: 20 },]
-				);
 				assert_eq!(
 					pool_events_since_last_call(),
 					vec![
@@ -3819,10 +3787,6 @@ mod withdraw_unbonded {
 				assert_ok!(Pools::withdraw_unbonded(RuntimeOrigin::signed(550), 550, 0));
 
 				// Then
-				assert_eq!(
-					balances_events_since_last_call(),
-					vec![BEvent::Transfer { from: default_bonded_account(), to: 550, amount: 275 },]
-				);
 				assert_eq!(
 					pool_events_since_last_call(),
 					vec![
@@ -3852,9 +3816,11 @@ mod withdraw_unbonded {
 				assert_ok!(Pools::withdraw_unbonded(RuntimeOrigin::signed(10), 10, 0));
 
 				// then
-				assert_eq!(Currency::free_balance(&10), 10 + 35);
-				assert_eq!(Currency::free_balance(&default_bonded_account()), 0);
-
+				assert_eq!(
+					DelegateMock::agent_balance(Agent::from(default_bonded_account())),
+					None
+				);
+				assert_eq!(StakingMock::stake(&default_bonded_account()).unwrap().total, 0);
 				// in this test 10 also gets a fair share of the slash, because the slash was
 				// applied to the bonded account.
 				assert_eq!(
@@ -3870,7 +3836,6 @@ mod withdraw_unbonded {
 				assert_eq!(
 					balances_events_since_last_call(),
 					vec![
-						BEvent::Transfer { from: default_bonded_account(), to: 10, amount: 5 },
 						BEvent::Thawed { who: default_reward_account(), amount: 5 },
 						BEvent::Transfer { from: default_reward_account(), to: 10, amount: 5 }
 					]
@@ -3878,35 +3843,6 @@ mod withdraw_unbonded {
 			});
 	}
 
-	#[test]
-	fn withdraw_unbonded_handles_faulty_sub_pool_accounting() {
-		ExtBuilder::default().build_and_execute(|| {
-			// Given
-			assert_eq!(Currency::minimum_balance(), 5);
-			assert_eq!(Currency::free_balance(&10), 35);
-			assert_eq!(Currency::free_balance(&default_bonded_account()), 10);
-			unsafe_set_state(1, PoolState::Destroying);
-			assert_ok!(Pools::fully_unbond(RuntimeOrigin::signed(10), 10));
-
-			// Simulate a slash that is not accounted for in the sub pools.
-			Currency::set_balance(&default_bonded_account(), 5);
-			assert_eq!(
-				SubPoolsStorage::<Runtime>::get(1).unwrap().with_era,
-				//------------------------------balance decrease is not account for
-				unbonding_pools_with_era! { 3 => UnbondPool { points: 10, balance: 10 } }
-			);
-
-			CurrentEra::set(3);
-
-			// When
-			assert_ok!(Pools::withdraw_unbonded(RuntimeOrigin::signed(10), 10, 0));
-
-			// Then
-			assert_eq!(Currency::free_balance(&10), 10 + 35);
-			assert_eq!(Currency::free_balance(&default_bonded_account()), 0);
-		});
-	}
-
 	#[test]
 	fn withdraw_unbonded_errors_correctly() {
 		ExtBuilder::default().with_check(0).build_and_execute(|| {
@@ -3925,6 +3861,10 @@ mod withdraw_unbonded {
 			let mut member = PoolMember { pool_id: 1, points: 10, ..Default::default() };
 			PoolMembers::<Runtime>::insert(11, member.clone());
 
+			// set agent and delegator balance
+			DelegateMock::set_agent_balance(Pools::generate_bonded_account(1), 10);
+			DelegateMock::set_delegator_balance(11, 10);
+
 			// Simulate calling `unbond`
 			member.unbonding_eras = member_unbonding_eras!(3 => 10);
 			PoolMembers::<Runtime>::insert(11, member.clone());
@@ -4045,7 +3985,7 @@ mod withdraw_unbonded {
 				}
 			);
 			CurrentEra::set(StakingMock::bonding_duration());
-			assert_eq!(Currency::free_balance(&100), 100);
+			assert_eq!(member_delegation(100), 100);
 
 			// Cannot permissionlessly withdraw
 			assert_noop!(
@@ -4061,6 +4001,7 @@ mod withdraw_unbonded {
 
 			assert_eq!(SubPoolsStorage::<Runtime>::get(1).unwrap(), Default::default(),);
 			assert_eq!(Currency::free_balance(&100), 100 + 100);
+			assert_eq!(member_delegation(100), 0);
 			assert!(!PoolMembers::<Runtime>::contains_key(100));
 			assert_eq!(
 				pool_events_since_last_call(),
@@ -4662,10 +4603,6 @@ mod withdraw_unbonded {
 
 			// move to era when unbonded funds can be withdrawn.
 			CurrentEra::set(4);
-
-			// increment consumer by 1 reproducing the erroneous consumer bug.
-			// refer https://github.com/paritytech/polkadot-sdk/issues/4440.
-			assert_ok!(frame_system::Pallet::<T>::inc_consumers(&pool_one));
 			assert_ok!(Pools::withdraw_unbonded(RuntimeOrigin::signed(10), 10, 0));
 
 			assert_eq!(
@@ -4712,7 +4649,7 @@ mod create {
 			));
 			assert_eq!(TotalValueLocked::<T>::get(), 10 + StakingMock::minimum_nominator_bond());
 
-			assert_eq!(Currency::free_balance(&11), 0);
+			assert_eq!(member_delegation(11), StakingMock::minimum_nominator_bond());
 			assert_eq!(
 				PoolMembers::<Runtime>::get(11).unwrap(),
 				PoolMember {
@@ -4851,7 +4788,7 @@ mod create {
 				789
 			));
 
-			assert_eq!(Currency::free_balance(&11), 0);
+			assert_eq!(member_delegation(11), StakingMock::minimum_nominator_bond());
 			// delete the initial pool created, then pool_Id `1` will be free
 
 			assert_noop!(
@@ -5014,16 +4951,9 @@ mod set_state {
 			// surpassed. Making this pool destroyable by anyone.
 			StakingMock::slash_by(1, 10);
 
-			// in mock we are using transfer stake which implies slash is greedy. Extrinsic to
-			// apply pending slash should fail.
-			assert_noop!(
-				Pools::apply_slash(RuntimeOrigin::signed(11), 10),
-				Error::<Runtime>::NotSupported
-			);
-
-			// pending slash api should return zero as well.
-			assert_eq!(Pools::api_pool_pending_slash(1), 0);
-			assert_eq!(Pools::api_member_pending_slash(10), 0);
+			// pending slash is correct.
+			assert_eq!(Pools::api_pool_pending_slash(1), 10);
+			assert_eq!(Pools::api_member_pending_slash(10), 10);
 
 			// When
 			assert_ok!(Pools::set_state(RuntimeOrigin::signed(11), 1, PoolState::Destroying));
@@ -5175,13 +5105,13 @@ mod bond_extra {
 			// given
 			assert_eq!(PoolMembers::<Runtime>::get(10).unwrap().points, 10);
 			assert_eq!(BondedPools::<Runtime>::get(1).unwrap().points, 10);
-			assert_eq!(Currency::free_balance(&10), 100);
+			assert_eq!(member_delegation(10), 10);
 
 			// when
 			assert_ok!(Pools::bond_extra(RuntimeOrigin::signed(10), BondExtra::FreeBalance(10)));
 
 			// then
-			assert_eq!(Currency::free_balance(&10), 90);
+			assert_eq!(member_delegation(10), 10 + 10);
 			assert_eq!(PoolMembers::<Runtime>::get(10).unwrap().points, 20);
 			assert_eq!(BondedPools::<Runtime>::get(1).unwrap().points, 20);
 
@@ -5198,7 +5128,7 @@ mod bond_extra {
 			assert_ok!(Pools::bond_extra(RuntimeOrigin::signed(10), BondExtra::FreeBalance(20)));
 
 			// then
-			assert_eq!(Currency::free_balance(&10), 70);
+			assert_eq!(member_delegation(10), 20 + 20);
 			assert_eq!(PoolMembers::<Runtime>::get(10).unwrap().points, 40);
 			assert_eq!(BondedPools::<Runtime>::get(1).unwrap().points, 40);
 
@@ -5221,13 +5151,15 @@ mod bond_extra {
 			// given
 			assert_eq!(PoolMembers::<Runtime>::get(10).unwrap().points, 10);
 			assert_eq!(BondedPools::<Runtime>::get(1).unwrap().points, 10);
-			assert_eq!(Currency::free_balance(&10), 35);
+			// 10 has delegated 10 tokens to the pool.
+			assert_eq!(member_delegation(10), 10);
 
 			// when
 			assert_ok!(Pools::bond_extra(RuntimeOrigin::signed(10), BondExtra::Rewards));
 
 			// then
-			assert_eq!(Currency::free_balance(&10), 35);
+			// delegator balance is increased by the claimable reward.
+			assert_eq!(member_delegation(10), 10 + claimable_reward);
 			assert_eq!(PoolMembers::<Runtime>::get(10).unwrap().points, 10 + claimable_reward);
 			assert_eq!(BondedPools::<Runtime>::get(1).unwrap().points, 10 + claimable_reward);
 
@@ -5264,8 +5196,8 @@ mod bond_extra {
 			assert_eq!(PoolMembers::<Runtime>::get(20).unwrap().points, 20);
 			assert_eq!(BondedPools::<Runtime>::get(1).unwrap().points, 30);
 
-			assert_eq!(Currency::free_balance(&10), 35);
-			assert_eq!(Currency::free_balance(&20), 20);
+			assert_eq!(member_delegation(10), 10);
+			assert_eq!(member_delegation(20), 20);
 			assert_eq!(TotalValueLocked::<T>::get(), 30);
 
 			// when
@@ -5273,7 +5205,7 @@ mod bond_extra {
 			assert_eq!(Currency::free_balance(&default_reward_account()), 7);
 
 			// then
-			assert_eq!(Currency::free_balance(&10), 35);
+			assert_eq!(member_delegation(10), 10 + 1);
 			assert_eq!(TotalValueLocked::<T>::get(), 31);
 
 			// 10's share of the reward is 1/3, since they gave 10/30 of the total shares.
@@ -5284,11 +5216,11 @@ mod bond_extra {
 			assert_ok!(Pools::bond_extra(RuntimeOrigin::signed(20), BondExtra::Rewards));
 
 			// then
-			assert_eq!(Currency::free_balance(&20), 20);
 			assert_eq!(TotalValueLocked::<T>::get(), 33);
 
 			// 20's share of the rewards is the other 2/3 of the rewards, since they have 20/30 of
 			// the shares
+			assert_eq!(member_delegation(20), 20 + 2);
 			assert_eq!(PoolMembers::<Runtime>::get(20).unwrap().points, 20 + 2);
 			assert_eq!(BondedPools::<Runtime>::get(1).unwrap().points, 30 + 3);
 
@@ -5320,8 +5252,8 @@ mod bond_extra {
 			assert_eq!(PoolMembers::<Runtime>::get(10).unwrap().points, 10);
 			assert_eq!(PoolMembers::<Runtime>::get(20).unwrap().points, 20);
 			assert_eq!(BondedPools::<Runtime>::get(1).unwrap().points, 30);
-			assert_eq!(Currency::free_balance(&10), 35);
-			assert_eq!(Currency::free_balance(&20), 20);
+			assert_eq!(member_delegation(10), 10);
+			assert_eq!(member_delegation(20), 20);
 
 			// Permissioned by default
 			assert_noop!(
@@ -5337,7 +5269,7 @@ mod bond_extra {
 			assert_eq!(Currency::free_balance(&default_reward_account()), 7);
 
 			// then
-			assert_eq!(Currency::free_balance(&10), 35);
+			assert_eq!(member_delegation(10), 10 + 1);
 			assert_eq!(PoolMembers::<Runtime>::get(10).unwrap().points, 10 + 1);
 			assert_eq!(BondedPools::<Runtime>::get(1).unwrap().points, 30 + 1);
 
@@ -5355,7 +5287,7 @@ mod bond_extra {
 			));
 
 			// then
-			assert_eq!(Currency::free_balance(&20), 12);
+			assert_eq!(member_delegation(20), 20 + 10);
 			assert_eq!(Currency::free_balance(&default_reward_account()), 5);
 			assert_eq!(PoolMembers::<Runtime>::get(20).unwrap().points, 30);
 			assert_eq!(BondedPools::<Runtime>::get(1).unwrap().points, 41);
@@ -7487,63 +7419,3 @@ mod chill {
 		})
 	}
 }
-
-// the test mock is using `TransferStake` and so `DelegateStake` is not tested here. Extrinsics
-// meant for `DelegateStake` should be gated.
-//
-// `DelegateStake` tests are in `pallet-nomination-pools-test-delegate-stake`. Since we support both
-// strategies currently, we keep these tests as it is but in future we may remove `TransferStake`
-// completely.
-mod delegate_stake {
-	use super::*;
-	#[test]
-	fn delegation_specific_calls_are_gated() {
-		ExtBuilder::default().with_check(0).build_and_execute(|| {
-			// Given
-			Currency::set_balance(&11, ExistentialDeposit::get() + 2);
-			assert!(!PoolMembers::<Runtime>::contains_key(11));
-
-			// When
-			assert_ok!(Pools::join(RuntimeOrigin::signed(11), 2, 1));
-
-			// Then
-			assert_eq!(
-				pool_events_since_last_call(),
-				vec![
-					Event::Created { depositor: 10, pool_id: 1 },
-					Event::Bonded { member: 10, pool_id: 1, bonded: 10, joined: true },
-					Event::Bonded { member: 11, pool_id: 1, bonded: 2, joined: true },
-				]
-			);
-
-			assert_eq!(
-				PoolMembers::<Runtime>::get(11).unwrap(),
-				PoolMember::<Runtime> { pool_id: 1, points: 2, ..Default::default() }
-			);
-
-			// ensure pool 1 cannot be migrated.
-			assert!(!Pools::api_pool_needs_delegate_migration(1));
-			assert_noop!(
-				Pools::migrate_pool_to_delegate_stake(RuntimeOrigin::signed(10), 1),
-				Error::<Runtime>::NotSupported
-			);
-
-			// members cannot be migrated either.
-			assert!(!Pools::api_member_needs_delegate_migration(10));
-			assert_noop!(
-				Pools::migrate_delegation(RuntimeOrigin::signed(10), 11),
-				Error::<Runtime>::NotSupported
-			);
-
-			// Given
-			// The bonded balance is slashed in half
-			StakingMock::slash_by(1, 6);
-
-			// since slash is greedy with `TransferStake`, `apply_slash` should not work either.
-			assert_noop!(
-				Pools::apply_slash(RuntimeOrigin::signed(10), 11),
-				Error::<Runtime>::NotSupported
-			);
-		});
-	}
-}
diff --git a/substrate/frame/nomination-pools/test-delegate-stake/src/lib.rs b/substrate/frame/nomination-pools/test-delegate-stake/src/lib.rs
index cc6335959ab738115c88c4878bc3dc2d8bb6db81..54783332aa3ef245bced9e737ed5c8a89265e7d8 100644
--- a/substrate/frame/nomination-pools/test-delegate-stake/src/lib.rs
+++ b/substrate/frame/nomination-pools/test-delegate-stake/src/lib.rs
@@ -21,7 +21,10 @@ mod mock;
 
 use frame_support::{
 	assert_noop, assert_ok, hypothetically,
-	traits::{fungible::InspectHold, Currency},
+	traits::{
+		fungible::{InspectHold, Mutate},
+		Currency,
+	},
 };
 use mock::*;
 use pallet_nomination_pools::{
@@ -942,9 +945,13 @@ fn pool_slash_non_proportional_bonded_pool_and_chunks() {
 fn pool_migration_e2e() {
 	new_test_ext().execute_with(|| {
 		LegacyAdapter::set(true);
-		assert_eq!(Balances::minimum_balance(), 5);
 		assert_eq!(CurrentEra::<T>::get(), None);
 
+		// hack: mint ED to pool so that the deprecated `TransferStake` works correctly with
+		// staking.
+		assert_eq!(Balances::minimum_balance(), 5);
+		assert_ok!(Balances::mint_into(&POOL1_BONDED, 5));
+
 		// create the pool with TransferStake strategy.
 		assert_ok!(Pools::create(RuntimeOrigin::signed(10), 50, 10, 10, 10));
 		assert_eq!(LastPoolId::<Runtime>::get(), 1);
@@ -1050,10 +1057,11 @@ fn pool_migration_e2e() {
 
 		assert_eq!(
 			delegated_staking_events_since_last_call(),
+			// delegated also contains the extra ED that we minted when pool was `TransferStake` .
 			vec![DelegatedStakingEvent::Delegated {
 				agent: POOL1_BONDED,
 				delegator: proxy_delegator_1,
-				amount: 50 + 10 * 3
+				amount: 50 + 10 * 3 + 5
 			}]
 		);
 
@@ -1223,6 +1231,11 @@ fn disable_pool_operations_on_non_migrated() {
 		assert_eq!(Balances::minimum_balance(), 5);
 		assert_eq!(CurrentEra::<T>::get(), None);
 
+		// hack: mint ED to pool so that the deprecated `TransferStake` works correctly with
+		// staking.
+		assert_eq!(Balances::minimum_balance(), 5);
+		assert_ok!(Balances::mint_into(&POOL1_BONDED, 5));
+
 		// create the pool with TransferStake strategy.
 		assert_ok!(Pools::create(RuntimeOrigin::signed(10), 50, 10, 10, 10));
 		assert_eq!(LastPoolId::<Runtime>::get(), 1);
@@ -1331,11 +1344,12 @@ fn disable_pool_operations_on_non_migrated() {
 		assert_ok!(Pools::migrate_pool_to_delegate_stake(RuntimeOrigin::signed(10), 1));
 		assert_eq!(
 			delegated_staking_events_since_last_call(),
+			// delegated also contains the extra ED that we minted when pool was `TransferStake` .
 			vec![DelegatedStakingEvent::Delegated {
 				agent: POOL1_BONDED,
 				delegator: DelegatedStaking::generate_proxy_delegator(Agent::from(POOL1_BONDED))
 					.get(),
-				amount: 50 + 10
+				amount: 50 + 10 + 5
 			},]
 		);
 
diff --git a/substrate/frame/nomination-pools/test-delegate-stake/src/mock.rs b/substrate/frame/nomination-pools/test-delegate-stake/src/mock.rs
index d1bc4ef8ff281986532d069292b2fed7785e6ca4..d943ba6f533330453b8961886005b4f20be0804b 100644
--- a/substrate/frame/nomination-pools/test-delegate-stake/src/mock.rs
+++ b/substrate/frame/nomination-pools/test-delegate-stake/src/mock.rs
@@ -15,6 +15,9 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
+// Disable warnings for `TransferStake` being deprecated.
+#![allow(deprecated)]
+
 use frame_election_provider_support::VoteWeight;
 use frame_support::{
 	assert_ok, derive_impl,
@@ -92,6 +95,7 @@ parameter_types! {
 
 #[derive_impl(pallet_staking::config_preludes::TestDefaultConfig)]
 impl pallet_staking::Config for Runtime {
+	type OldCurrency = Balances;
 	type Currency = Balances;
 	type UnixTime = pallet_timestamp::Pallet<Self>;
 	type AdminOrigin = frame_system::EnsureRoot<Self::AccountId>;
diff --git a/substrate/frame/nomination-pools/test-transfer-stake/Cargo.toml b/substrate/frame/nomination-pools/test-transfer-stake/Cargo.toml
deleted file mode 100644
index 0b21d5f4e8cf1e3a98e3220a17bfe7eeb0b44cc0..0000000000000000000000000000000000000000
--- a/substrate/frame/nomination-pools/test-transfer-stake/Cargo.toml
+++ /dev/null
@@ -1,39 +0,0 @@
-[package]
-name = "pallet-nomination-pools-test-transfer-stake"
-version = "1.0.0"
-authors.workspace = true
-edition.workspace = true
-license = "Apache-2.0"
-homepage.workspace = true
-repository.workspace = true
-description = "FRAME nomination pools pallet tests with the staking pallet"
-publish = false
-
-[lints]
-workspace = true
-
-[package.metadata.docs.rs]
-targets = ["x86_64-unknown-linux-gnu"]
-
-[dev-dependencies]
-codec = { features = ["derive"], workspace = true, default-features = true }
-scale-info = { features = ["derive"], workspace = true, default-features = true }
-
-sp-core = { workspace = true, default-features = true }
-sp-io = { workspace = true, default-features = true }
-sp-runtime = { workspace = true, default-features = true }
-sp-staking = { workspace = true, default-features = true }
-
-frame-election-provider-support = { workspace = true, default-features = true }
-frame-support = { workspace = true, default-features = true }
-frame-system = { workspace = true, default-features = true }
-
-pallet-bags-list = { workspace = true, default-features = true }
-pallet-balances = { workspace = true, default-features = true }
-pallet-nomination-pools = { workspace = true, default-features = true }
-pallet-staking = { workspace = true, default-features = true }
-pallet-staking-reward-curve = { workspace = true, default-features = true }
-pallet-timestamp = { workspace = true, default-features = true }
-
-log = { workspace = true, default-features = true }
-sp-tracing = { workspace = true, default-features = true }
diff --git a/substrate/frame/nomination-pools/test-transfer-stake/src/lib.rs b/substrate/frame/nomination-pools/test-transfer-stake/src/lib.rs
deleted file mode 100644
index cc39cfee91c80d95db2b069023991f7647eb3b79..0000000000000000000000000000000000000000
--- a/substrate/frame/nomination-pools/test-transfer-stake/src/lib.rs
+++ /dev/null
@@ -1,912 +0,0 @@
-// This file is part of Substrate.
-
-// Copyright (C) Parity Technologies (UK) Ltd.
-// SPDX-License-Identifier: Apache-2.0
-
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// 	http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#![cfg(test)]
-
-mod mock;
-
-use frame_support::{assert_noop, assert_ok, traits::Currency};
-use mock::*;
-use pallet_nomination_pools::{
-	BondExtra, BondedPools, Error as PoolsError, Event as PoolsEvent, LastPoolId, PoolMember,
-	PoolMembers, PoolState,
-};
-use pallet_staking::{
-	CurrentEra, Error as StakingError, Event as StakingEvent, Payee, RewardDestination,
-};
-use sp_runtime::{bounded_btree_map, traits::Zero};
-
-#[test]
-fn pool_lifecycle_e2e() {
-	new_test_ext().execute_with(|| {
-		assert_eq!(Balances::minimum_balance(), 5);
-		assert_eq!(CurrentEra::<T>::get(), None);
-
-		// create the pool, we know this has id 1.
-		assert_ok!(Pools::create(RuntimeOrigin::signed(10), 50, 10, 10, 10));
-		assert_eq!(LastPoolId::<Runtime>::get(), 1);
-
-		// have the pool nominate.
-		assert_ok!(Pools::nominate(RuntimeOrigin::signed(10), 1, vec![1, 2, 3]));
-
-		assert_eq!(
-			staking_events_since_last_call(),
-			vec![StakingEvent::Bonded { stash: POOL1_BONDED, amount: 50 }]
-		);
-		assert_eq!(
-			pool_events_since_last_call(),
-			vec![
-				PoolsEvent::Created { depositor: 10, pool_id: 1 },
-				PoolsEvent::Bonded { member: 10, pool_id: 1, bonded: 50, joined: true },
-			]
-		);
-
-		// have two members join
-		assert_ok!(Pools::join(RuntimeOrigin::signed(20), 10, 1));
-		assert_ok!(Pools::join(RuntimeOrigin::signed(21), 10, 1));
-
-		assert_eq!(
-			staking_events_since_last_call(),
-			vec![
-				StakingEvent::Bonded { stash: POOL1_BONDED, amount: 10 },
-				StakingEvent::Bonded { stash: POOL1_BONDED, amount: 10 },
-			]
-		);
-		assert_eq!(
-			pool_events_since_last_call(),
-			vec![
-				PoolsEvent::Bonded { member: 20, pool_id: 1, bonded: 10, joined: true },
-				PoolsEvent::Bonded { member: 21, pool_id: 1, bonded: 10, joined: true },
-			]
-		);
-
-		// pool goes into destroying
-		assert_ok!(Pools::set_state(RuntimeOrigin::signed(10), 1, PoolState::Destroying));
-
-		// depositor cannot unbond yet.
-		assert_noop!(
-			Pools::unbond(RuntimeOrigin::signed(10), 10, 50),
-			PoolsError::<Runtime>::MinimumBondNotMet,
-		);
-
-		// now the members want to unbond.
-		assert_ok!(Pools::unbond(RuntimeOrigin::signed(20), 20, 10));
-		assert_ok!(Pools::unbond(RuntimeOrigin::signed(21), 21, 10));
-
-		assert_eq!(PoolMembers::<Runtime>::get(20).unwrap().unbonding_eras.len(), 1);
-		assert_eq!(PoolMembers::<Runtime>::get(20).unwrap().points, 0);
-		assert_eq!(PoolMembers::<Runtime>::get(21).unwrap().unbonding_eras.len(), 1);
-		assert_eq!(PoolMembers::<Runtime>::get(21).unwrap().points, 0);
-
-		assert_eq!(
-			staking_events_since_last_call(),
-			vec![
-				StakingEvent::Unbonded { stash: POOL1_BONDED, amount: 10 },
-				StakingEvent::Unbonded { stash: POOL1_BONDED, amount: 10 },
-			]
-		);
-		assert_eq!(
-			pool_events_since_last_call(),
-			vec![
-				PoolsEvent::StateChanged { pool_id: 1, new_state: PoolState::Destroying },
-				PoolsEvent::Unbonded { member: 20, pool_id: 1, points: 10, balance: 10, era: 3 },
-				PoolsEvent::Unbonded { member: 21, pool_id: 1, points: 10, balance: 10, era: 3 },
-			]
-		);
-
-		// depositor cannot still unbond
-		assert_noop!(
-			Pools::unbond(RuntimeOrigin::signed(10), 10, 50),
-			PoolsError::<Runtime>::MinimumBondNotMet,
-		);
-
-		for e in 1..BondingDuration::get() {
-			CurrentEra::<Runtime>::set(Some(e));
-			assert_noop!(
-				Pools::withdraw_unbonded(RuntimeOrigin::signed(20), 20, 0),
-				PoolsError::<Runtime>::CannotWithdrawAny
-			);
-		}
-
-		// members are now unlocked.
-		CurrentEra::<Runtime>::set(Some(BondingDuration::get()));
-
-		// depositor cannot still unbond
-		assert_noop!(
-			Pools::unbond(RuntimeOrigin::signed(10), 10, 50),
-			PoolsError::<Runtime>::MinimumBondNotMet,
-		);
-
-		// but members can now withdraw.
-		assert_ok!(Pools::withdraw_unbonded(RuntimeOrigin::signed(20), 20, 0));
-		assert_ok!(Pools::withdraw_unbonded(RuntimeOrigin::signed(21), 21, 0));
-		assert!(PoolMembers::<Runtime>::get(20).is_none());
-		assert!(PoolMembers::<Runtime>::get(21).is_none());
-
-		assert_eq!(
-			staking_events_since_last_call(),
-			vec![StakingEvent::Withdrawn { stash: POOL1_BONDED, amount: 20 },]
-		);
-		assert_eq!(
-			pool_events_since_last_call(),
-			vec![
-				PoolsEvent::Withdrawn { member: 20, pool_id: 1, points: 10, balance: 10 },
-				PoolsEvent::MemberRemoved { pool_id: 1, member: 20, released_balance: 0 },
-				PoolsEvent::Withdrawn { member: 21, pool_id: 1, points: 10, balance: 10 },
-				PoolsEvent::MemberRemoved { pool_id: 1, member: 21, released_balance: 0 },
-			]
-		);
-
-		// as soon as all members have left, the depositor can try to unbond, but since the
-		// min-nominator intention is set, they must chill first.
-		assert_noop!(
-			Pools::unbond(RuntimeOrigin::signed(10), 10, 50),
-			pallet_staking::Error::<Runtime>::InsufficientBond
-		);
-
-		assert_ok!(Pools::chill(RuntimeOrigin::signed(10), 1));
-		assert_ok!(Pools::unbond(RuntimeOrigin::signed(10), 10, 50));
-
-		assert_eq!(
-			staking_events_since_last_call(),
-			vec![
-				StakingEvent::Chilled { stash: POOL1_BONDED },
-				StakingEvent::Unbonded { stash: POOL1_BONDED, amount: 50 },
-			]
-		);
-		assert_eq!(
-			pool_events_since_last_call(),
-			vec![PoolsEvent::Unbonded { member: 10, pool_id: 1, points: 50, balance: 50, era: 6 }]
-		);
-
-		// waiting another bonding duration:
-		CurrentEra::<Runtime>::set(Some(BondingDuration::get() * 2));
-		assert_ok!(Pools::withdraw_unbonded(RuntimeOrigin::signed(10), 10, 1));
-
-		// pools is fully destroyed now.
-		assert_eq!(
-			staking_events_since_last_call(),
-			vec![StakingEvent::Withdrawn { stash: POOL1_BONDED, amount: 50 },]
-		);
-		assert_eq!(
-			pool_events_since_last_call(),
-			vec![
-				PoolsEvent::Withdrawn { member: 10, pool_id: 1, points: 50, balance: 50 },
-				PoolsEvent::MemberRemoved { pool_id: 1, member: 10, released_balance: 0 },
-				PoolsEvent::Destroyed { pool_id: 1 }
-			]
-		);
-	})
-}
-
-#[test]
-fn destroy_pool_with_erroneous_consumer() {
-	new_test_ext().execute_with(|| {
-		// create the pool, we know this has id 1.
-		assert_ok!(Pools::create(RuntimeOrigin::signed(10), 50, 10, 10, 10));
-		assert_eq!(LastPoolId::<Runtime>::get(), 1);
-
-		// expect consumers on pool account to be 2 (staking lock and an explicit inc by staking).
-		assert_eq!(frame_system::Pallet::<T>::consumers(&POOL1_BONDED), 2);
-
-		// increment consumer by 1 reproducing the erroneous consumer bug.
-		// refer https://github.com/paritytech/polkadot-sdk/issues/4440.
-		assert_ok!(frame_system::Pallet::<T>::inc_consumers(&POOL1_BONDED));
-		assert_eq!(frame_system::Pallet::<T>::consumers(&POOL1_BONDED), 3);
-
-		// have the pool nominate.
-		assert_ok!(Pools::nominate(RuntimeOrigin::signed(10), 1, vec![1, 2, 3]));
-
-		assert_eq!(
-			staking_events_since_last_call(),
-			vec![StakingEvent::Bonded { stash: POOL1_BONDED, amount: 50 }]
-		);
-		assert_eq!(
-			pool_events_since_last_call(),
-			vec![
-				PoolsEvent::Created { depositor: 10, pool_id: 1 },
-				PoolsEvent::Bonded { member: 10, pool_id: 1, bonded: 50, joined: true },
-			]
-		);
-
-		// pool goes into destroying
-		assert_ok!(Pools::set_state(RuntimeOrigin::signed(10), 1, PoolState::Destroying));
-
-		assert_eq!(
-			pool_events_since_last_call(),
-			vec![PoolsEvent::StateChanged { pool_id: 1, new_state: PoolState::Destroying },]
-		);
-
-		// move to era 1
-		CurrentEra::<Runtime>::set(Some(1));
-
-		// depositor need to chill before unbonding
-		assert_noop!(
-			Pools::unbond(RuntimeOrigin::signed(10), 10, 50),
-			pallet_staking::Error::<Runtime>::InsufficientBond
-		);
-
-		assert_ok!(Pools::chill(RuntimeOrigin::signed(10), 1));
-		assert_ok!(Pools::unbond(RuntimeOrigin::signed(10), 10, 50));
-
-		assert_eq!(
-			staking_events_since_last_call(),
-			vec![
-				StakingEvent::Chilled { stash: POOL1_BONDED },
-				StakingEvent::Unbonded { stash: POOL1_BONDED, amount: 50 },
-			]
-		);
-		assert_eq!(
-			pool_events_since_last_call(),
-			vec![PoolsEvent::Unbonded {
-				member: 10,
-				pool_id: 1,
-				points: 50,
-				balance: 50,
-				era: 1 + 3
-			}]
-		);
-
-		// waiting bonding duration:
-		CurrentEra::<Runtime>::set(Some(1 + 3));
-		// this should work even with an extra consumer count on pool account.
-		assert_ok!(Pools::withdraw_unbonded(RuntimeOrigin::signed(10), 10, 1));
-
-		// pools is fully destroyed now.
-		assert_eq!(
-			staking_events_since_last_call(),
-			vec![StakingEvent::Withdrawn { stash: POOL1_BONDED, amount: 50 },]
-		);
-		assert_eq!(
-			pool_events_since_last_call(),
-			vec![
-				PoolsEvent::Withdrawn { member: 10, pool_id: 1, points: 50, balance: 50 },
-				PoolsEvent::MemberRemoved { pool_id: 1, member: 10, released_balance: 0 },
-				PoolsEvent::Destroyed { pool_id: 1 }
-			]
-		);
-	})
-}
-
-#[test]
-fn pool_chill_e2e() {
-	new_test_ext().execute_with(|| {
-		assert_eq!(Balances::minimum_balance(), 5);
-		assert_eq!(CurrentEra::<T>::get(), None);
-
-		// create the pool, we know this has id 1.
-		assert_ok!(Pools::create(RuntimeOrigin::signed(10), 50, 10, 10, 10));
-		assert_eq!(LastPoolId::<Runtime>::get(), 1);
-
-		// have the pool nominate.
-		assert_ok!(Pools::nominate(RuntimeOrigin::signed(10), 1, vec![1, 2, 3]));
-
-		assert_eq!(
-			staking_events_since_last_call(),
-			vec![StakingEvent::Bonded { stash: POOL1_BONDED, amount: 50 }]
-		);
-		assert_eq!(
-			pool_events_since_last_call(),
-			vec![
-				PoolsEvent::Created { depositor: 10, pool_id: 1 },
-				PoolsEvent::Bonded { member: 10, pool_id: 1, bonded: 50, joined: true },
-			]
-		);
-
-		// have two members join
-		assert_ok!(Pools::join(RuntimeOrigin::signed(20), 10, 1));
-		assert_ok!(Pools::join(RuntimeOrigin::signed(21), 10, 1));
-
-		assert_eq!(
-			staking_events_since_last_call(),
-			vec![
-				StakingEvent::Bonded { stash: POOL1_BONDED, amount: 10 },
-				StakingEvent::Bonded { stash: POOL1_BONDED, amount: 10 },
-			]
-		);
-		assert_eq!(
-			pool_events_since_last_call(),
-			vec![
-				PoolsEvent::Bonded { member: 20, pool_id: 1, bonded: 10, joined: true },
-				PoolsEvent::Bonded { member: 21, pool_id: 1, bonded: 10, joined: true },
-			]
-		);
-
-		// in case depositor does not have more than `MinNominatorBond` staked, we can end up in
-		// situation where a member unbonding would cause pool balance to drop below
-		// `MinNominatorBond` and hence not allowed. This can happen if the `MinNominatorBond` is
-		// increased after the pool is created.
-		assert_ok!(Staking::set_staking_configs(
-			RuntimeOrigin::root(),
-			pallet_staking::ConfigOp::Set(55), // minimum nominator bond
-			pallet_staking::ConfigOp::Noop,
-			pallet_staking::ConfigOp::Noop,
-			pallet_staking::ConfigOp::Noop,
-			pallet_staking::ConfigOp::Noop,
-			pallet_staking::ConfigOp::Noop,
-			pallet_staking::ConfigOp::Noop,
-		));
-
-		// members can unbond as long as total stake of the pool is above min nominator bond
-		assert_ok!(Pools::unbond(RuntimeOrigin::signed(20), 20, 10),);
-		assert_eq!(PoolMembers::<Runtime>::get(20).unwrap().unbonding_eras.len(), 1);
-		assert_eq!(PoolMembers::<Runtime>::get(20).unwrap().points, 0);
-
-		// this member cannot unbond since it will cause `pool stake < MinNominatorBond`
-		assert_noop!(
-			Pools::unbond(RuntimeOrigin::signed(21), 21, 10),
-			StakingError::<Runtime>::InsufficientBond,
-		);
-
-		// members can call `chill` permissionlessly now
-		assert_ok!(Pools::chill(RuntimeOrigin::signed(20), 1));
-
-		// now another member can unbond.
-		assert_ok!(Pools::unbond(RuntimeOrigin::signed(21), 21, 10));
-		assert_eq!(PoolMembers::<Runtime>::get(21).unwrap().unbonding_eras.len(), 1);
-		assert_eq!(PoolMembers::<Runtime>::get(21).unwrap().points, 0);
-
-		// nominator can not resume nomination until depositor have enough stake
-		assert_noop!(
-			Pools::nominate(RuntimeOrigin::signed(10), 1, vec![1, 2, 3]),
-			PoolsError::<Runtime>::MinimumBondNotMet,
-		);
-
-		// other members joining pool does not affect the depositor's ability to resume nomination
-		assert_ok!(Pools::join(RuntimeOrigin::signed(22), 10, 1));
-
-		assert_noop!(
-			Pools::nominate(RuntimeOrigin::signed(10), 1, vec![1, 2, 3]),
-			PoolsError::<Runtime>::MinimumBondNotMet,
-		);
-
-		// depositor can bond extra stake
-		assert_ok!(Pools::bond_extra(RuntimeOrigin::signed(10), BondExtra::FreeBalance(10)));
-
-		// `chill` can not be called permissionlessly anymore
-		assert_noop!(
-			Pools::chill(RuntimeOrigin::signed(20), 1),
-			PoolsError::<Runtime>::NotNominator,
-		);
-
-		// now nominator can resume nomination
-		assert_ok!(Pools::nominate(RuntimeOrigin::signed(10), 1, vec![1, 2, 3]));
-
-		// skip to make the unbonding period end.
-		CurrentEra::<Runtime>::set(Some(BondingDuration::get()));
-
-		// members can now withdraw.
-		assert_ok!(Pools::withdraw_unbonded(RuntimeOrigin::signed(20), 20, 0));
-		assert_ok!(Pools::withdraw_unbonded(RuntimeOrigin::signed(21), 21, 0));
-
-		assert_eq!(
-			staking_events_since_last_call(),
-			vec![
-				StakingEvent::Unbonded { stash: POOL1_BONDED, amount: 10 },
-				StakingEvent::Chilled { stash: POOL1_BONDED },
-				StakingEvent::Unbonded { stash: POOL1_BONDED, amount: 10 },
-				StakingEvent::Bonded { stash: POOL1_BONDED, amount: 10 }, // other member bonding
-				StakingEvent::Bonded { stash: POOL1_BONDED, amount: 10 }, // depositor bond extra
-				StakingEvent::Withdrawn { stash: POOL1_BONDED, amount: 20 },
-			]
-		);
-	})
-}
-
-#[test]
-fn pool_slash_e2e() {
-	new_test_ext().execute_with(|| {
-		ExistentialDeposit::set(1);
-		assert_eq!(Balances::minimum_balance(), 1);
-		assert_eq!(CurrentEra::<T>::get(), None);
-
-		// create the pool, we know this has id 1.
-		assert_ok!(Pools::create(RuntimeOrigin::signed(10), 40, 10, 10, 10));
-		assert_eq!(LastPoolId::<Runtime>::get(), 1);
-
-		assert_eq!(
-			staking_events_since_last_call(),
-			vec![StakingEvent::Bonded { stash: POOL1_BONDED, amount: 40 }]
-		);
-		assert_eq!(
-			pool_events_since_last_call(),
-			vec![
-				PoolsEvent::Created { depositor: 10, pool_id: 1 },
-				PoolsEvent::Bonded { member: 10, pool_id: 1, bonded: 40, joined: true },
-			]
-		);
-
-		assert_eq!(
-			Payee::<Runtime>::get(POOL1_BONDED),
-			Some(RewardDestination::Account(POOL1_REWARD))
-		);
-
-		// have two members join
-		assert_ok!(Pools::join(RuntimeOrigin::signed(20), 20, 1));
-		assert_ok!(Pools::join(RuntimeOrigin::signed(21), 20, 1));
-
-		assert_eq!(
-			staking_events_since_last_call(),
-			vec![
-				StakingEvent::Bonded { stash: POOL1_BONDED, amount: 20 },
-				StakingEvent::Bonded { stash: POOL1_BONDED, amount: 20 }
-			]
-		);
-		assert_eq!(
-			pool_events_since_last_call(),
-			vec![
-				PoolsEvent::Bonded { member: 20, pool_id: 1, bonded: 20, joined: true },
-				PoolsEvent::Bonded { member: 21, pool_id: 1, bonded: 20, joined: true },
-			]
-		);
-
-		// now let's progress a bit.
-		CurrentEra::<Runtime>::set(Some(1));
-
-		// 20 / 80 of the total funds are unlocked, and safe from any further slash.
-		assert_ok!(Pools::unbond(RuntimeOrigin::signed(10), 10, 10));
-		assert_ok!(Pools::unbond(RuntimeOrigin::signed(20), 20, 10));
-
-		assert_eq!(
-			staking_events_since_last_call(),
-			vec![
-				StakingEvent::Unbonded { stash: POOL1_BONDED, amount: 10 },
-				StakingEvent::Unbonded { stash: POOL1_BONDED, amount: 10 }
-			]
-		);
-		assert_eq!(
-			pool_events_since_last_call(),
-			vec![
-				PoolsEvent::Unbonded { member: 10, pool_id: 1, balance: 10, points: 10, era: 4 },
-				PoolsEvent::Unbonded { member: 20, pool_id: 1, balance: 10, points: 10, era: 4 }
-			]
-		);
-
-		CurrentEra::<Runtime>::set(Some(2));
-
-		// note: depositor cannot fully unbond at this point.
-		// these funds will still get slashed.
-		assert_ok!(Pools::unbond(RuntimeOrigin::signed(10), 10, 10));
-		assert_ok!(Pools::unbond(RuntimeOrigin::signed(20), 20, 10));
-		assert_ok!(Pools::unbond(RuntimeOrigin::signed(21), 21, 10));
-
-		assert_eq!(
-			staking_events_since_last_call(),
-			vec![
-				StakingEvent::Unbonded { stash: POOL1_BONDED, amount: 10 },
-				StakingEvent::Unbonded { stash: POOL1_BONDED, amount: 10 },
-				StakingEvent::Unbonded { stash: POOL1_BONDED, amount: 10 },
-			]
-		);
-
-		assert_eq!(
-			pool_events_since_last_call(),
-			vec![
-				PoolsEvent::Unbonded { member: 10, pool_id: 1, balance: 10, points: 10, era: 5 },
-				PoolsEvent::Unbonded { member: 20, pool_id: 1, balance: 10, points: 10, era: 5 },
-				PoolsEvent::Unbonded { member: 21, pool_id: 1, balance: 10, points: 10, era: 5 },
-			]
-		);
-
-		// At this point, 20 are safe from slash, 30 are unlocking but vulnerable to slash, and
-		// another 30 are active and vulnerable to slash. Let's slash half of them.
-		pallet_staking::slashing::do_slash::<Runtime>(
-			&POOL1_BONDED,
-			30,
-			&mut Default::default(),
-			&mut Default::default(),
-			2, // slash era 2, affects chunks at era 5 onwards.
-		);
-
-		assert_eq!(
-			staking_events_since_last_call(),
-			vec![StakingEvent::Slashed { staker: POOL1_BONDED, amount: 30 }]
-		);
-		assert_eq!(
-			pool_events_since_last_call(),
-			vec![
-				// 30 has been slashed to 15 (15 slash)
-				PoolsEvent::UnbondingPoolSlashed { pool_id: 1, era: 5, balance: 15 },
-				// 30 has been slashed to 15 (15 slash)
-				PoolsEvent::PoolSlashed { pool_id: 1, balance: 15 }
-			]
-		);
-
-		CurrentEra::<Runtime>::set(Some(3));
-		assert_ok!(Pools::unbond(RuntimeOrigin::signed(21), 21, 10));
-
-		assert_eq!(
-			PoolMembers::<Runtime>::get(21).unwrap(),
-			PoolMember {
-				pool_id: 1,
-				points: 0,
-				last_recorded_reward_counter: Zero::zero(),
-				// the 10 points unlocked just now correspond to 5 points in the unbond pool.
-				unbonding_eras: bounded_btree_map!(5 => 10, 6 => 5)
-			}
-		);
-		assert_eq!(
-			staking_events_since_last_call(),
-			vec![StakingEvent::Unbonded { stash: POOL1_BONDED, amount: 5 }]
-		);
-		assert_eq!(
-			pool_events_since_last_call(),
-			vec![PoolsEvent::Unbonded { member: 21, pool_id: 1, balance: 5, points: 5, era: 6 }]
-		);
-
-		// now we start withdrawing. we do it all at once, at era 6 where 20 and 21 are fully free.
-		CurrentEra::<Runtime>::set(Some(6));
-		assert_ok!(Pools::withdraw_unbonded(RuntimeOrigin::signed(20), 20, 0));
-		assert_ok!(Pools::withdraw_unbonded(RuntimeOrigin::signed(21), 21, 0));
-
-		assert_eq!(
-			pool_events_since_last_call(),
-			vec![
-				// 20 had unbonded 10 safely, and 10 got slashed by half.
-				PoolsEvent::Withdrawn { member: 20, pool_id: 1, balance: 10 + 5, points: 20 },
-				PoolsEvent::MemberRemoved { pool_id: 1, member: 20, released_balance: 0 },
-				// 21 unbonded all of it after the slash
-				PoolsEvent::Withdrawn { member: 21, pool_id: 1, balance: 5 + 5, points: 15 },
-				PoolsEvent::MemberRemoved { pool_id: 1, member: 21, released_balance: 0 }
-			]
-		);
-		assert_eq!(
-			staking_events_since_last_call(),
-			// a 10 (un-slashed) + 10/2 (slashed) balance from 10 has also been unlocked
-			vec![StakingEvent::Withdrawn { stash: POOL1_BONDED, amount: 15 + 10 + 15 }]
-		);
-
-		// now, finally, we can unbond the depositor further than their current limit.
-		assert_ok!(Pools::set_state(RuntimeOrigin::signed(10), 1, PoolState::Destroying));
-		assert_ok!(Pools::unbond(RuntimeOrigin::signed(10), 10, 20));
-
-		assert_eq!(
-			staking_events_since_last_call(),
-			vec![StakingEvent::Unbonded { stash: POOL1_BONDED, amount: 10 }]
-		);
-		assert_eq!(
-			pool_events_since_last_call(),
-			vec![
-				PoolsEvent::StateChanged { pool_id: 1, new_state: PoolState::Destroying },
-				PoolsEvent::Unbonded { member: 10, pool_id: 1, points: 10, balance: 10, era: 9 }
-			]
-		);
-
-		CurrentEra::<Runtime>::set(Some(9));
-		assert_eq!(
-			PoolMembers::<Runtime>::get(10).unwrap(),
-			PoolMember {
-				pool_id: 1,
-				points: 0,
-				last_recorded_reward_counter: Zero::zero(),
-				unbonding_eras: bounded_btree_map!(4 => 10, 5 => 10, 9 => 10)
-			}
-		);
-		// withdraw the depositor, they should lose 12 balance in total due to slash.
-		assert_ok!(Pools::withdraw_unbonded(RuntimeOrigin::signed(10), 10, 0));
-
-		assert_eq!(
-			staking_events_since_last_call(),
-			vec![StakingEvent::Withdrawn { stash: POOL1_BONDED, amount: 10 }]
-		);
-		assert_eq!(
-			pool_events_since_last_call(),
-			vec![
-				PoolsEvent::Withdrawn { member: 10, pool_id: 1, balance: 10 + 15, points: 30 },
-				PoolsEvent::MemberRemoved { pool_id: 1, member: 10, released_balance: 0 },
-				PoolsEvent::Destroyed { pool_id: 1 }
-			]
-		);
-	});
-}
-
-#[test]
-fn pool_slash_proportional() {
-	// a typical example where 3 pool members unbond in era 99, 100, and 101, and a slash that
-	// happened in era 100 should only affect the latter two.
-	new_test_ext().execute_with(|| {
-		ExistentialDeposit::set(1);
-		BondingDuration::set(28);
-		assert_eq!(Balances::minimum_balance(), 1);
-		assert_eq!(CurrentEra::<T>::get(), None);
-
-		// create the pool, we know this has id 1.
-		assert_ok!(Pools::create(RuntimeOrigin::signed(10), 40, 10, 10, 10));
-		assert_eq!(LastPoolId::<T>::get(), 1);
-
-		assert_eq!(
-			staking_events_since_last_call(),
-			vec![StakingEvent::Bonded { stash: POOL1_BONDED, amount: 40 }]
-		);
-		assert_eq!(
-			pool_events_since_last_call(),
-			vec![
-				PoolsEvent::Created { depositor: 10, pool_id: 1 },
-				PoolsEvent::Bonded { member: 10, pool_id: 1, bonded: 40, joined: true },
-			]
-		);
-
-		// have two members join
-		let bond = 20;
-		assert_ok!(Pools::join(RuntimeOrigin::signed(20), bond, 1));
-		assert_ok!(Pools::join(RuntimeOrigin::signed(21), bond, 1));
-		assert_ok!(Pools::join(RuntimeOrigin::signed(22), bond, 1));
-
-		assert_eq!(
-			staking_events_since_last_call(),
-			vec![
-				StakingEvent::Bonded { stash: POOL1_BONDED, amount: bond },
-				StakingEvent::Bonded { stash: POOL1_BONDED, amount: bond },
-				StakingEvent::Bonded { stash: POOL1_BONDED, amount: bond },
-			]
-		);
-		assert_eq!(
-			pool_events_since_last_call(),
-			vec![
-				PoolsEvent::Bonded { member: 20, pool_id: 1, bonded: bond, joined: true },
-				PoolsEvent::Bonded { member: 21, pool_id: 1, bonded: bond, joined: true },
-				PoolsEvent::Bonded { member: 22, pool_id: 1, bonded: bond, joined: true },
-			]
-		);
-
-		// now let's progress a lot.
-		CurrentEra::<T>::set(Some(99));
-
-		// and unbond
-		assert_ok!(Pools::unbond(RuntimeOrigin::signed(20), 20, bond));
-
-		assert_eq!(
-			staking_events_since_last_call(),
-			vec![StakingEvent::Unbonded { stash: POOL1_BONDED, amount: bond },]
-		);
-		assert_eq!(
-			pool_events_since_last_call(),
-			vec![PoolsEvent::Unbonded {
-				member: 20,
-				pool_id: 1,
-				balance: bond,
-				points: bond,
-				era: 127
-			}]
-		);
-
-		CurrentEra::<T>::set(Some(100));
-		assert_ok!(Pools::unbond(RuntimeOrigin::signed(21), 21, bond));
-		assert_eq!(
-			staking_events_since_last_call(),
-			vec![StakingEvent::Unbonded { stash: POOL1_BONDED, amount: bond },]
-		);
-		assert_eq!(
-			pool_events_since_last_call(),
-			vec![PoolsEvent::Unbonded {
-				member: 21,
-				pool_id: 1,
-				balance: bond,
-				points: bond,
-				era: 128
-			}]
-		);
-
-		CurrentEra::<T>::set(Some(101));
-		assert_ok!(Pools::unbond(RuntimeOrigin::signed(22), 22, bond));
-		assert_eq!(
-			staking_events_since_last_call(),
-			vec![StakingEvent::Unbonded { stash: POOL1_BONDED, amount: bond },]
-		);
-		assert_eq!(
-			pool_events_since_last_call(),
-			vec![PoolsEvent::Unbonded {
-				member: 22,
-				pool_id: 1,
-				balance: bond,
-				points: bond,
-				era: 129
-			}]
-		);
-
-		// Apply a slash that happened in era 100. This is typically applied with a delay.
-		// Of the total 100, 50 is slashed.
-		assert_eq!(BondedPools::<T>::get(1).unwrap().points, 40);
-		pallet_staking::slashing::do_slash::<Runtime>(
-			&POOL1_BONDED,
-			50,
-			&mut Default::default(),
-			&mut Default::default(),
-			100,
-		);
-
-		assert_eq!(
-			staking_events_since_last_call(),
-			vec![StakingEvent::Slashed { staker: POOL1_BONDED, amount: 50 }]
-		);
-		assert_eq!(
-			pool_events_since_last_call(),
-			vec![
-				// This era got slashed 12.5, which rounded up to 13.
-				PoolsEvent::UnbondingPoolSlashed { pool_id: 1, era: 128, balance: 7 },
-				// This era got slashed 12 instead of 12.5 because an earlier chunk got 0.5 more
-				// slashed, and 12 is all the remaining slash
-				PoolsEvent::UnbondingPoolSlashed { pool_id: 1, era: 129, balance: 8 },
-				// Bonded pool got slashed for 25, remaining 15 in it.
-				PoolsEvent::PoolSlashed { pool_id: 1, balance: 15 }
-			]
-		);
-	});
-}
-
-#[test]
-fn pool_slash_non_proportional_only_bonded_pool() {
-	// A typical example where a pool member unbonds in era 99, and they can get away with a slash
-	// that happened in era 100, as long as the pool has enough active bond to cover the slash. If
-	// everything else in the slashing/staking system works, this should always be the case.
-	// Nonetheless, `ledger.slash` has been written such that it will slash greedily from any chunk
-	// if it runs out of chunks that it thinks should be affected by the slash.
-	new_test_ext().execute_with(|| {
-		ExistentialDeposit::set(1);
-		BondingDuration::set(28);
-		assert_eq!(Balances::minimum_balance(), 1);
-		assert_eq!(CurrentEra::<T>::get(), None);
-
-		// create the pool, we know this has id 1.
-		assert_ok!(Pools::create(RuntimeOrigin::signed(10), 40, 10, 10, 10));
-		assert_eq!(
-			staking_events_since_last_call(),
-			vec![StakingEvent::Bonded { stash: POOL1_BONDED, amount: 40 }]
-		);
-		assert_eq!(
-			pool_events_since_last_call(),
-			vec![
-				PoolsEvent::Created { depositor: 10, pool_id: 1 },
-				PoolsEvent::Bonded { member: 10, pool_id: 1, bonded: 40, joined: true },
-			]
-		);
-
-		// have two members join
-		let bond = 20;
-		assert_ok!(Pools::join(RuntimeOrigin::signed(20), bond, 1));
-		assert_eq!(
-			staking_events_since_last_call(),
-			vec![StakingEvent::Bonded { stash: POOL1_BONDED, amount: bond }]
-		);
-		assert_eq!(
-			pool_events_since_last_call(),
-			vec![PoolsEvent::Bonded { member: 20, pool_id: 1, bonded: bond, joined: true }]
-		);
-
-		// progress and unbond.
-		CurrentEra::<T>::set(Some(99));
-		assert_ok!(Pools::unbond(RuntimeOrigin::signed(20), 20, bond));
-		assert_eq!(
-			staking_events_since_last_call(),
-			vec![StakingEvent::Unbonded { stash: POOL1_BONDED, amount: bond }]
-		);
-		assert_eq!(
-			pool_events_since_last_call(),
-			vec![PoolsEvent::Unbonded {
-				member: 20,
-				pool_id: 1,
-				balance: bond,
-				points: bond,
-				era: 127
-			}]
-		);
-
-		// slash for 30. This will be deducted only from the bonded pool.
-		CurrentEra::<T>::set(Some(100));
-		assert_eq!(BondedPools::<T>::get(1).unwrap().points, 40);
-		pallet_staking::slashing::do_slash::<Runtime>(
-			&POOL1_BONDED,
-			30,
-			&mut Default::default(),
-			&mut Default::default(),
-			100,
-		);
-
-		assert_eq!(
-			staking_events_since_last_call(),
-			vec![StakingEvent::Slashed { staker: POOL1_BONDED, amount: 30 }]
-		);
-		assert_eq!(
-			pool_events_since_last_call(),
-			vec![PoolsEvent::PoolSlashed { pool_id: 1, balance: 10 }]
-		);
-	});
-}
-
-#[test]
-fn pool_slash_non_proportional_bonded_pool_and_chunks() {
-	// An uncommon example where even though some funds are unlocked such that they should not be
-	// affected by a slash, we still slash out of them. This should not happen at all. If a
-	// nomination has unbonded, from the next era onwards, their exposure will drop, so if an era
-	// happens in that era, then their share of that slash should naturally be less, such that only
-	// their active ledger stake is enough to compensate it.
-	new_test_ext().execute_with(|| {
-		ExistentialDeposit::set(1);
-		BondingDuration::set(28);
-		assert_eq!(Balances::minimum_balance(), 1);
-		assert_eq!(CurrentEra::<T>::get(), None);
-
-		// create the pool, we know this has id 1.
-		assert_ok!(Pools::create(RuntimeOrigin::signed(10), 40, 10, 10, 10));
-		assert_eq!(
-			staking_events_since_last_call(),
-			vec![StakingEvent::Bonded { stash: POOL1_BONDED, amount: 40 }]
-		);
-		assert_eq!(
-			pool_events_since_last_call(),
-			vec![
-				PoolsEvent::Created { depositor: 10, pool_id: 1 },
-				PoolsEvent::Bonded { member: 10, pool_id: 1, bonded: 40, joined: true },
-			]
-		);
-
-		// have two members join
-		let bond = 20;
-		assert_ok!(Pools::join(RuntimeOrigin::signed(20), bond, 1));
-		assert_eq!(
-			staking_events_since_last_call(),
-			vec![StakingEvent::Bonded { stash: POOL1_BONDED, amount: bond }]
-		);
-		assert_eq!(
-			pool_events_since_last_call(),
-			vec![PoolsEvent::Bonded { member: 20, pool_id: 1, bonded: bond, joined: true }]
-		);
-
-		// progress and unbond.
-		CurrentEra::<T>::set(Some(99));
-		assert_ok!(Pools::unbond(RuntimeOrigin::signed(20), 20, bond));
-		assert_eq!(
-			staking_events_since_last_call(),
-			vec![StakingEvent::Unbonded { stash: POOL1_BONDED, amount: bond }]
-		);
-		assert_eq!(
-			pool_events_since_last_call(),
-			vec![PoolsEvent::Unbonded {
-				member: 20,
-				pool_id: 1,
-				balance: bond,
-				points: bond,
-				era: 127
-			}]
-		);
-
-		// slash 50. This will be deducted only from the bonded pool and one of the unbonding pools.
-		CurrentEra::<T>::set(Some(100));
-		assert_eq!(BondedPools::<T>::get(1).unwrap().points, 40);
-		pallet_staking::slashing::do_slash::<Runtime>(
-			&POOL1_BONDED,
-			50,
-			&mut Default::default(),
-			&mut Default::default(),
-			100,
-		);
-
-		assert_eq!(
-			staking_events_since_last_call(),
-			vec![StakingEvent::Slashed { staker: POOL1_BONDED, amount: 50 }]
-		);
-		assert_eq!(
-			pool_events_since_last_call(),
-			vec![
-				// out of 20, 10 was taken.
-				PoolsEvent::UnbondingPoolSlashed { pool_id: 1, era: 127, balance: 10 },
-				// out of 40, all was taken.
-				PoolsEvent::PoolSlashed { pool_id: 1, balance: 0 }
-			]
-		);
-	});
-}
diff --git a/substrate/frame/nomination-pools/test-transfer-stake/src/mock.rs b/substrate/frame/nomination-pools/test-transfer-stake/src/mock.rs
deleted file mode 100644
index d913c5fe6948cb5a0fae1d5ccf8e533e5ddb8cc7..0000000000000000000000000000000000000000
--- a/substrate/frame/nomination-pools/test-transfer-stake/src/mock.rs
+++ /dev/null
@@ -1,231 +0,0 @@
-// This file is part of Substrate.
-
-// Copyright (C) Parity Technologies (UK) Ltd.
-// SPDX-License-Identifier: Apache-2.0
-
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// 	http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-use frame_election_provider_support::VoteWeight;
-use frame_support::{
-	assert_ok, derive_impl,
-	pallet_prelude::*,
-	parameter_types,
-	traits::{ConstU64, ConstU8, VariantCountOf},
-	PalletId,
-};
-use sp_runtime::{
-	traits::{Convert, IdentityLookup},
-	BuildStorage, FixedU128, Perbill,
-};
-
-type AccountId = u128;
-type BlockNumber = u64;
-type Balance = u128;
-
-pub(crate) type T = Runtime;
-
-pub(crate) const POOL1_BONDED: AccountId = 20318131474730217858575332831085u128;
-pub(crate) const POOL1_REWARD: AccountId = 20397359637244482196168876781421u128;
-
-#[derive_impl(frame_system::config_preludes::TestDefaultConfig)]
-impl frame_system::Config for Runtime {
-	type AccountId = AccountId;
-	type Lookup = IdentityLookup<Self::AccountId>;
-	type Block = Block;
-	type AccountData = pallet_balances::AccountData<Balance>;
-}
-
-impl pallet_timestamp::Config for Runtime {
-	type Moment = u64;
-	type OnTimestampSet = ();
-	type MinimumPeriod = ConstU64<5>;
-	type WeightInfo = ();
-}
-
-parameter_types! {
-	pub static ExistentialDeposit: Balance = 5;
-}
-
-#[derive_impl(pallet_balances::config_preludes::TestDefaultConfig)]
-impl pallet_balances::Config for Runtime {
-	type Balance = Balance;
-	type ExistentialDeposit = ExistentialDeposit;
-	type AccountStore = System;
-	type FreezeIdentifier = RuntimeFreezeReason;
-	type MaxFreezes = VariantCountOf<RuntimeFreezeReason>;
-	type RuntimeFreezeReason = RuntimeFreezeReason;
-}
-
-pallet_staking_reward_curve::build! {
-	const I_NPOS: sp_runtime::curve::PiecewiseLinear<'static> = curve!(
-		min_inflation: 0_025_000,
-		max_inflation: 0_100_000,
-		ideal_stake: 0_500_000,
-		falloff: 0_050_000,
-		max_piece_count: 40,
-		test_precision: 0_005_000,
-	);
-}
-
-parameter_types! {
-	pub const RewardCurve: &'static sp_runtime::curve::PiecewiseLinear<'static> = &I_NPOS;
-	pub static BondingDuration: u32 = 3;
-}
-
-#[derive_impl(pallet_staking::config_preludes::TestDefaultConfig)]
-impl pallet_staking::Config for Runtime {
-	type Currency = Balances;
-	type UnixTime = pallet_timestamp::Pallet<Self>;
-	type AdminOrigin = frame_system::EnsureRoot<Self::AccountId>;
-	type BondingDuration = BondingDuration;
-	type EraPayout = pallet_staking::ConvertCurve<RewardCurve>;
-	type ElectionProvider =
-		frame_election_provider_support::NoElection<(AccountId, BlockNumber, Staking, ())>;
-	type GenesisElectionProvider = Self::ElectionProvider;
-	type VoterList = VoterList;
-	type TargetList = pallet_staking::UseValidatorsMap<Self>;
-	type EventListeners = Pools;
-	type BenchmarkingConfig = pallet_staking::TestBenchmarkingConfig;
-}
-
-parameter_types! {
-	pub static BagThresholds: &'static [VoteWeight] = &[10, 20, 30, 40, 50, 60, 1_000, 2_000, 10_000];
-}
-
-type VoterBagsListInstance = pallet_bags_list::Instance1;
-impl pallet_bags_list::Config<VoterBagsListInstance> for Runtime {
-	type RuntimeEvent = RuntimeEvent;
-	type WeightInfo = ();
-	type BagThresholds = BagThresholds;
-	type ScoreProvider = Staking;
-	type Score = VoteWeight;
-}
-
-pub struct BalanceToU256;
-impl Convert<Balance, sp_core::U256> for BalanceToU256 {
-	fn convert(n: Balance) -> sp_core::U256 {
-		n.into()
-	}
-}
-
-pub struct U256ToBalance;
-impl Convert<sp_core::U256, Balance> for U256ToBalance {
-	fn convert(n: sp_core::U256) -> Balance {
-		n.try_into().unwrap()
-	}
-}
-
-parameter_types! {
-	pub const PostUnbondingPoolsWindow: u32 = 10;
-	pub const PoolsPalletId: PalletId = PalletId(*b"py/nopls");
-}
-
-impl pallet_nomination_pools::Config for Runtime {
-	type RuntimeEvent = RuntimeEvent;
-	type WeightInfo = ();
-	type Currency = Balances;
-	type RuntimeFreezeReason = RuntimeFreezeReason;
-	type RewardCounter = FixedU128;
-	type BalanceToU256 = BalanceToU256;
-	type U256ToBalance = U256ToBalance;
-	type StakeAdapter = pallet_nomination_pools::adapter::TransferStake<Self, Staking>;
-	type PostUnbondingPoolsWindow = PostUnbondingPoolsWindow;
-	type MaxMetadataLen = ConstU32<256>;
-	type MaxUnbonding = ConstU32<8>;
-	type MaxPointsToBalance = ConstU8<10>;
-	type PalletId = PoolsPalletId;
-	type AdminOrigin = frame_system::EnsureRoot<Self::AccountId>;
-}
-
-type Block = frame_system::mocking::MockBlock<Runtime>;
-
-frame_support::construct_runtime!(
-	pub enum Runtime {
-		System: frame_system,
-		Timestamp: pallet_timestamp,
-		Balances: pallet_balances,
-		Staking: pallet_staking,
-		VoterList: pallet_bags_list::<Instance1>,
-		Pools: pallet_nomination_pools,
-	}
-);
-
-pub fn new_test_ext() -> sp_io::TestExternalities {
-	sp_tracing::try_init_simple();
-	let mut storage = frame_system::GenesisConfig::<Runtime>::default().build_storage().unwrap();
-	let _ = pallet_nomination_pools::GenesisConfig::<Runtime> {
-		min_join_bond: 2,
-		min_create_bond: 2,
-		max_pools: Some(3),
-		max_members_per_pool: Some(5),
-		max_members: Some(3 * 5),
-		global_max_commission: Some(Perbill::from_percent(90)),
-	}
-	.assimilate_storage(&mut storage)
-	.unwrap();
-
-	let _ = pallet_balances::GenesisConfig::<Runtime> {
-		balances: vec![(10, 100), (20, 100), (21, 100), (22, 100)],
-	}
-	.assimilate_storage(&mut storage)
-	.unwrap();
-
-	let mut ext = sp_io::TestExternalities::from(storage);
-
-	ext.execute_with(|| {
-		// for events to be deposited.
-		frame_system::Pallet::<Runtime>::set_block_number(1);
-
-		// set some limit for nominations.
-		assert_ok!(Staking::set_staking_configs(
-			RuntimeOrigin::root(),
-			pallet_staking::ConfigOp::Set(10), // minimum nominator bond
-			pallet_staking::ConfigOp::Noop,
-			pallet_staking::ConfigOp::Noop,
-			pallet_staking::ConfigOp::Noop,
-			pallet_staking::ConfigOp::Noop,
-			pallet_staking::ConfigOp::Noop,
-			pallet_staking::ConfigOp::Noop,
-		));
-	});
-
-	ext
-}
-
-parameter_types! {
-	static ObservedEventsPools: usize = 0;
-	static ObservedEventsStaking: usize = 0;
-	static ObservedEventsBalances: usize = 0;
-}
-
-pub(crate) fn pool_events_since_last_call() -> Vec<pallet_nomination_pools::Event<Runtime>> {
-	let events = System::events()
-		.into_iter()
-		.map(|r| r.event)
-		.filter_map(|e| if let RuntimeEvent::Pools(inner) = e { Some(inner) } else { None })
-		.collect::<Vec<_>>();
-	let already_seen = ObservedEventsPools::get();
-	ObservedEventsPools::set(events.len());
-	events.into_iter().skip(already_seen).collect()
-}
-
-pub(crate) fn staking_events_since_last_call() -> Vec<pallet_staking::Event<Runtime>> {
-	let events = System::events()
-		.into_iter()
-		.map(|r| r.event)
-		.filter_map(|e| if let RuntimeEvent::Staking(inner) = e { Some(inner) } else { None })
-		.collect::<Vec<_>>();
-	let already_seen = ObservedEventsStaking::get();
-	ObservedEventsStaking::set(events.len());
-	events.into_iter().skip(already_seen).collect()
-}
diff --git a/substrate/frame/offences/benchmarking/src/inner.rs b/substrate/frame/offences/benchmarking/src/inner.rs
index 75f3e9931e34c3d8827049d76d0e5510086ccdba..3d3cd470bc24cc3fd63eaf09a52b99ee5eb7b33e 100644
--- a/substrate/frame/offences/benchmarking/src/inner.rs
+++ b/substrate/frame/offences/benchmarking/src/inner.rs
@@ -180,16 +180,12 @@ where
 	<T as frame_system::Config>::RuntimeEvent: TryInto<frame_system::Event<T>>,
 {
 	// make sure that all slashes have been applied
-	// (n nominators + one validator) * (slashed + unlocked) + deposit to reporter +
-	// reporter account endowed + some funds rescinded from issuance.
-	assert_eq!(
-		System::<T>::read_events_for_pallet::<pallet_balances::Event<T>>().len(),
-		2 * (offender_count + 1) + 3
-	);
+	// deposit to reporter + reporter account endowed.
+	assert_eq!(System::<T>::read_events_for_pallet::<pallet_balances::Event<T>>().len(), 2);
 	// (n nominators + one validator) * slashed + Slash Reported
 	assert_eq!(
 		System::<T>::read_events_for_pallet::<pallet_staking::Event<T>>().len(),
-		1 * (offender_count + 1) + 1
+		1 * (offender_count + 1) as usize + 1
 	);
 	// offence
 	assert_eq!(System::<T>::read_events_for_pallet::<pallet_offences::Event>().len(), 1);
diff --git a/substrate/frame/offences/benchmarking/src/mock.rs b/substrate/frame/offences/benchmarking/src/mock.rs
index c5c178aa4443dbd44c38b56e212fcfa1599cbfb6..3c81f2a664e32138f5fee5f43aab30e1f7ce6819 100644
--- a/substrate/frame/offences/benchmarking/src/mock.rs
+++ b/substrate/frame/offences/benchmarking/src/mock.rs
@@ -125,6 +125,7 @@ impl onchain::Config for OnChainSeqPhragmen {
 
 #[derive_impl(pallet_staking::config_preludes::TestDefaultConfig)]
 impl pallet_staking::Config for Test {
+	type OldCurrency = Balances;
 	type Currency = Balances;
 	type CurrencyBalance = <Self as pallet_balances::Config>::Balance;
 	type UnixTime = pallet_timestamp::Pallet<Self>;
diff --git a/substrate/frame/proxy/Cargo.toml b/substrate/frame/proxy/Cargo.toml
index a36b2c1cb9c3af5dc545f35d4788d8a043f1a77e..3f2565abac88d2653781046d111c1f50fb757393 100644
--- a/substrate/frame/proxy/Cargo.toml
+++ b/substrate/frame/proxy/Cargo.toml
@@ -17,7 +17,7 @@ targets = ["x86_64-unknown-linux-gnu"]
 
 [dependencies]
 codec = { features = ["max-encoded-len"], workspace = true }
-frame = { workspace = true, features = ["experimental", "runtime"] }
+frame = { workspace = true, features = ["runtime"] }
 scale-info = { features = ["derive"], workspace = true }
 
 [dev-dependencies]
diff --git a/substrate/frame/revive/Cargo.toml b/substrate/frame/revive/Cargo.toml
index 1284f5ee8947b6ff8d0d6990ce45ba8710ab2711..0959cc50638ba51a671c3ad8311d7e5745ef086f 100644
--- a/substrate/frame/revive/Cargo.toml
+++ b/substrate/frame/revive/Cargo.toml
@@ -20,12 +20,13 @@ targets = ["x86_64-unknown-linux-gnu"]
 codec = { features = ["derive", "max-encoded-len"], workspace = true }
 derive_more = { workspace = true }
 environmental = { workspace = true }
+ethabi = { workspace = true }
 ethereum-types = { workspace = true, features = ["codec", "rlp", "serialize"] }
 hex = { workspace = true }
 impl-trait-for-tuples = { workspace = true }
 log = { workspace = true }
 paste = { workspace = true }
-polkavm = { version = "0.18.0", default-features = false }
+polkavm = { version = "0.19.0", default-features = false }
 rlp = { workspace = true }
 scale-info = { features = ["derive"], workspace = true }
 serde = { features = [
@@ -75,6 +76,7 @@ default = ["std"]
 std = [
 	"codec/std",
 	"environmental/std",
+	"ethabi/std",
 	"ethereum-types/std",
 	"frame-benchmarking?/std",
 	"frame-support/std",
diff --git a/substrate/frame/revive/README.md b/substrate/frame/revive/README.md
index 575920dfaac79f6188b0b81c8f747dd364f13a12..7538f77d10bc07cbed79dbc6eff82869f1a73673 100644
--- a/substrate/frame/revive/README.md
+++ b/substrate/frame/revive/README.md
@@ -49,29 +49,6 @@ This module executes PolkaVM smart contracts. These can potentially be written i
 RISC-V. For now, the only officially supported languages are Solidity (via [`revive`](https://github.com/xermicus/revive))
 and Rust (check the `fixtures` directory for Rust examples).
 
-## Debugging
-
-Contracts can emit messages to the client when called as RPC through the
-[`debug_message`](https://paritytech.github.io/substrate/master/pallet_revive/trait.SyscallDocs.html#tymethod.debug_message)
-API.
-
-Those messages are gathered into an internal buffer and sent to the RPC client. It is up to the individual client if
-and how those messages are presented to the user.
-
-This buffer is also printed as a debug message. In order to see these messages on the node console the log level for the
-`runtime::revive` target needs to be raised to at least the `debug` level. However, those messages are easy to
-overlook because of the noise generated by block production. A good starting point for observing them on the console is
-using this command line in the root directory of the Substrate repository:
-
-```bash
-cargo run --release -- --dev -lerror,runtime::revive=debug
-```
-
-This raises the log level of `runtime::revive` to `debug` and all other targets to `error` in order to prevent them
-from spamming the console.
-
-`--dev`: Use a dev chain spec `--tmp`: Use temporary storage for chain data (the chain state is deleted on exit)
-
 ## Host function tracing
 
 For contract authors, it can be a helpful debugging tool to see which host functions are called, with which arguments,
diff --git a/substrate/frame/revive/fixtures/Cargo.toml b/substrate/frame/revive/fixtures/Cargo.toml
index e17bc88a3847a660500671d85c234dfb6d5e0b80..a6f25cc26f3c040ed9db6b56b9b402d8319ee3b1 100644
--- a/substrate/frame/revive/fixtures/Cargo.toml
+++ b/substrate/frame/revive/fixtures/Cargo.toml
@@ -21,7 +21,7 @@ sp-io = { workspace = true, default-features = true, optional = true }
 
 [build-dependencies]
 anyhow = { workspace = true, default-features = true }
-polkavm-linker = { version = "0.18.0" }
+polkavm-linker = { version = "0.19.0" }
 toml = { workspace = true }
 
 [features]
diff --git a/substrate/frame/revive/fixtures/build/_Cargo.toml b/substrate/frame/revive/fixtures/build/_Cargo.toml
index bfb9aaedd6f5cfa2535aa023f4ca68ba5f835cee..1a0a635420ad5acf02c55302f26c904cceb0f1b4 100644
--- a/substrate/frame/revive/fixtures/build/_Cargo.toml
+++ b/substrate/frame/revive/fixtures/build/_Cargo.toml
@@ -14,7 +14,8 @@ edition = "2021"
 [dependencies]
 uapi = { package = 'pallet-revive-uapi', path = "", features = ["unstable-hostfn"], default-features = false }
 common = { package = 'pallet-revive-fixtures-common', path = "" }
-polkavm-derive = { version = "0.18.0" }
+hex-literal = { version = "0.4.1", default-features = false }
+polkavm-derive = { version = "0.19.0" }
 
 [profile.release]
 opt-level = 3
diff --git a/substrate/frame/revive/fixtures/contracts/debug_message_logging_disabled.rs b/substrate/frame/revive/fixtures/contracts/debug_message_logging_disabled.rs
deleted file mode 100644
index 0ce2b6b5628da348415e5a39161576161b39146b..0000000000000000000000000000000000000000
--- a/substrate/frame/revive/fixtures/contracts/debug_message_logging_disabled.rs
+++ /dev/null
@@ -1,33 +0,0 @@
-// This file is part of Substrate.
-
-// Copyright (C) Parity Technologies (UK) Ltd.
-// SPDX-License-Identifier: Apache-2.0
-
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// 	http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-//! Emit a "Hello World!" debug message but assume that logging is disabled.
-#![no_std]
-#![no_main]
-
-extern crate common;
-use uapi::{HostFn, HostFnImpl as api, ReturnErrorCode};
-
-#[no_mangle]
-#[polkavm_derive::polkavm_export]
-pub extern "C" fn deploy() {}
-
-#[no_mangle]
-#[polkavm_derive::polkavm_export]
-pub extern "C" fn call() {
-	assert_eq!(api::debug_message(b"Hello World!"), Err(ReturnErrorCode::LoggingDisabled));
-}
diff --git a/substrate/frame/revive/fixtures/contracts/debug_message_works.rs b/substrate/frame/revive/fixtures/contracts/to_account_id.rs
similarity index 78%
rename from substrate/frame/revive/fixtures/contracts/debug_message_works.rs
rename to substrate/frame/revive/fixtures/contracts/to_account_id.rs
index 3a2509509d8f156300a256d1a5b0494ea961f5ef..c2a8fce3ec995c9cd28aa31584f6dd66948d6a25 100644
--- a/substrate/frame/revive/fixtures/contracts/debug_message_works.rs
+++ b/substrate/frame/revive/fixtures/contracts/to_account_id.rs
@@ -15,11 +15,10 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-//! Emit a "Hello World!" debug message.
 #![no_std]
 #![no_main]
 
-extern crate common;
+use common::input;
 use uapi::{HostFn, HostFnImpl as api};
 
 #[no_mangle]
@@ -29,5 +28,13 @@ pub extern "C" fn deploy() {}
 #[no_mangle]
 #[polkavm_derive::polkavm_export]
 pub extern "C" fn call() {
-	api::debug_message(b"Hello World!").unwrap();
+    input!(
+        address: &[u8; 20],
+        expected_account_id: &[u8; 32],
+    );
+
+    let mut account_id = [0u8; 32];
+    api::to_account_id(address, &mut account_id);
+
+    assert!(&account_id == expected_account_id);
 }
diff --git a/substrate/frame/revive/fixtures/contracts/tracing.rs b/substrate/frame/revive/fixtures/contracts/tracing.rs
new file mode 100644
index 0000000000000000000000000000000000000000..9cbef3bbc84355dc89968347248d4daed68aedad
--- /dev/null
+++ b/substrate/frame/revive/fixtures/contracts/tracing.rs
@@ -0,0 +1,75 @@
+// This file is part of Substrate.
+
+// Copyright (C) Parity Technologies (UK) Ltd.
+// SPDX-License-Identifier: Apache-2.0
+
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// 	http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+//! This fixture calls itself as many times as passed as argument.
+
+#![no_std]
+#![no_main]
+
+use common::input;
+use uapi::{HostFn, HostFnImpl as api};
+
+#[no_mangle]
+#[polkavm_derive::polkavm_export]
+pub extern "C" fn deploy() {}
+
+#[no_mangle]
+#[polkavm_derive::polkavm_export]
+pub extern "C" fn call() {
+	input!(calls_left: u32, callee_addr: &[u8; 20],);
+	if calls_left == 0 {
+		return
+	}
+
+	let next_input = (calls_left - 1).to_le_bytes();
+	api::deposit_event(&[], b"before");
+
+	// Call the callee, ignore revert.
+	let _ = api::call(
+		uapi::CallFlags::empty(),
+		callee_addr,
+		u64::MAX,       // How much ref_time to devote for the execution. u64::MAX = use all.
+		u64::MAX,       // How much proof_size to devote for the execution. u64::MAX = use all.
+		&[u8::MAX; 32], // No deposit limit.
+		&[0u8; 32],     // Value transferred to the contract.
+		&next_input,
+		None,
+	);
+
+	api::deposit_event(&[], b"after");
+
+	// own address
+	let mut addr = [0u8; 20];
+	api::address(&mut addr);
+	let mut input = [0u8; 24];
+
+	input[..4].copy_from_slice(&next_input);
+	input[4..24].copy_from_slice(&callee_addr[..20]);
+
+	// recurse
+	api::call(
+		uapi::CallFlags::ALLOW_REENTRY,
+		&addr,
+		u64::MAX,       // How much ref_time to devote for the execution. u64::MAX = use all.
+		u64::MAX,       // How much proof_size to devote for the execution. u64::MAX = use all.
+		&[u8::MAX; 32], // No deposit limit.
+		&[0u8; 32],     // Value transferred to the contract.
+		&input,
+		None,
+	)
+	.unwrap();
+}
diff --git a/substrate/frame/revive/fixtures/contracts/debug_message_invalid_utf8.rs b/substrate/frame/revive/fixtures/contracts/tracing_callee.rs
similarity index 62%
rename from substrate/frame/revive/fixtures/contracts/debug_message_invalid_utf8.rs
rename to substrate/frame/revive/fixtures/contracts/tracing_callee.rs
index 6c850a9ec66312a65e183ce04e7acdc6166093fb..d44771e417f9df5fd8c13277a2b4f5c0d7afe8f3 100644
--- a/substrate/frame/revive/fixtures/contracts/debug_message_invalid_utf8.rs
+++ b/substrate/frame/revive/fixtures/contracts/tracing_callee.rs
@@ -14,12 +14,10 @@
 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 // See the License for the specific language governing permissions and
 // limitations under the License.
-
-//! Emit a debug message with an invalid utf-8 code.
 #![no_std]
 #![no_main]
 
-extern crate common;
+use common::input;
 use uapi::{HostFn, HostFnImpl as api};
 
 #[no_mangle]
@@ -29,5 +27,19 @@ pub extern "C" fn deploy() {}
 #[no_mangle]
 #[polkavm_derive::polkavm_export]
 pub extern "C" fn call() {
-	api::debug_message(b"\xFC").unwrap();
+	input!(id: u32, );
+
+	match id {
+		// Revert with message "This function always fails"
+		2 => {
+			let data = hex_literal::hex!(
+		       "08c379a00000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000001a546869732066756e6374696f6e20616c77617973206661696c73000000000000"
+			);
+			api::return_value(uapi::ReturnFlags::REVERT, &data)
+		},
+		1 => {
+			panic!("booum");
+		},
+		_ => api::return_value(uapi::ReturnFlags::empty(), &id.to_le_bytes()),
+	};
 }
diff --git a/substrate/frame/revive/proc-macro/src/lib.rs b/substrate/frame/revive/proc-macro/src/lib.rs
index b09bdef14632b937a351cea6b2968e889f2202a0..6e38063d20a674c6c567835ced35def5c0858ac2 100644
--- a/substrate/frame/revive/proc-macro/src/lib.rs
+++ b/substrate/frame/revive/proc-macro/src/lib.rs
@@ -510,12 +510,7 @@ fn expand_functions(def: &EnvDef) -> TokenStream2 {
 			quote! {
 				// wrap body in closure to make sure the tracing is always executed
 				let result = (|| #body)();
-				if ::log::log_enabled!(target: "runtime::revive::strace", ::log::Level::Trace) {
-						use core::fmt::Write;
-						let mut msg = alloc::string::String::default();
-						let _ = core::write!(&mut msg, #trace_fmt_str, #( #trace_fmt_args, )* result);
-						self.ext().append_debug_buffer(&msg);
-				}
+				::log::trace!(target: "runtime::revive::strace", #trace_fmt_str, #( #trace_fmt_args, )* result);
 				result
 			}
 		};
diff --git a/substrate/frame/revive/rpc/examples/js/pvm/Errors.polkavm b/substrate/frame/revive/rpc/examples/js/pvm/Errors.polkavm
index 77de4ff3b1b3fe1f378ae31bbba24ddb38cc6300..48de6e0aa0c6cc1604008ba4e65c237dd557675b 100644
Binary files a/substrate/frame/revive/rpc/examples/js/pvm/Errors.polkavm and b/substrate/frame/revive/rpc/examples/js/pvm/Errors.polkavm differ
diff --git a/substrate/frame/revive/rpc/examples/js/pvm/EventExample.polkavm b/substrate/frame/revive/rpc/examples/js/pvm/EventExample.polkavm
index 6dbc5ca8b108c1ad04cc248b735b2d7d4f43f2a4..cea22e46adcad0dc9bf6375e676dd4923c624c89 100644
Binary files a/substrate/frame/revive/rpc/examples/js/pvm/EventExample.polkavm and b/substrate/frame/revive/rpc/examples/js/pvm/EventExample.polkavm differ
diff --git a/substrate/frame/revive/rpc/examples/js/pvm/Flipper.polkavm b/substrate/frame/revive/rpc/examples/js/pvm/Flipper.polkavm
index 488ee684f0c4aee5d64f8b691d048ebefdd51044..67f11e68f117309169a317d415ba547020c1f568 100644
Binary files a/substrate/frame/revive/rpc/examples/js/pvm/Flipper.polkavm and b/substrate/frame/revive/rpc/examples/js/pvm/Flipper.polkavm differ
diff --git a/substrate/frame/revive/rpc/examples/js/pvm/FlipperCaller.polkavm b/substrate/frame/revive/rpc/examples/js/pvm/FlipperCaller.polkavm
index 38a1098fe3a767aa0af74764bf7247e59f6110b7..29efafd8722db556b949b04c47c88eeec07535a6 100644
Binary files a/substrate/frame/revive/rpc/examples/js/pvm/FlipperCaller.polkavm and b/substrate/frame/revive/rpc/examples/js/pvm/FlipperCaller.polkavm differ
diff --git a/substrate/frame/revive/rpc/examples/js/pvm/PiggyBank.polkavm b/substrate/frame/revive/rpc/examples/js/pvm/PiggyBank.polkavm
index d0082db90e5e398832e4a32a9ec86dce83d16dd5..78455fcdd7c64a3a1f5e93b6d62cd03b46eb5953 100644
Binary files a/substrate/frame/revive/rpc/examples/js/pvm/PiggyBank.polkavm and b/substrate/frame/revive/rpc/examples/js/pvm/PiggyBank.polkavm differ
diff --git a/substrate/frame/revive/rpc/revive_chain.metadata b/substrate/frame/revive/rpc/revive_chain.metadata
index 402e8c2d22b21471929e9c61acd2cc968af614cf..a03c95b4944f663225642b1678ef66aaccec3fb5 100644
Binary files a/substrate/frame/revive/rpc/revive_chain.metadata and b/substrate/frame/revive/rpc/revive_chain.metadata differ
diff --git a/substrate/frame/revive/rpc/src/client.rs b/substrate/frame/revive/rpc/src/client.rs
index cd0effe7faf2f16084e665edd77eaa8449c8973b..c61c5871f76aeda6ae808d5215b5243f112ebba5 100644
--- a/substrate/frame/revive/rpc/src/client.rs
+++ b/substrate/frame/revive/rpc/src/client.rs
@@ -27,8 +27,9 @@ use crate::{
 use jsonrpsee::types::{error::CALL_EXECUTION_FAILED_CODE, ErrorObjectOwned};
 use pallet_revive::{
 	evm::{
-		Block, BlockNumberOrTag, BlockNumberOrTagOrHash, GenericTransaction, ReceiptInfo,
-		SyncingProgress, SyncingStatus, TransactionSigned, H160, H256, U256,
+		extract_revert_message, Block, BlockNumberOrTag, BlockNumberOrTagOrHash,
+		GenericTransaction, ReceiptInfo, SyncingProgress, SyncingStatus, TransactionSigned, H160,
+		H256, U256,
 	},
 	EthTransactError, EthTransactInfo,
 };
@@ -83,47 +84,6 @@ fn unwrap_call_err(err: &subxt::error::RpcError) -> Option<ErrorObjectOwned> {
 	}
 }
 
-/// Extract the revert message from a revert("msg") solidity statement.
-fn extract_revert_message(exec_data: &[u8]) -> Option<String> {
-	let error_selector = exec_data.get(0..4)?;
-
-	match error_selector {
-		// assert(false)
-		[0x4E, 0x48, 0x7B, 0x71] => {
-			let panic_code: u32 = U256::from_big_endian(exec_data.get(4..36)?).try_into().ok()?;
-
-			// See https://docs.soliditylang.org/en/latest/control-structures.html#panic-via-assert-and-error-via-require
-			let msg = match panic_code {
-				0x00 => "generic panic",
-				0x01 => "assert(false)",
-				0x11 => "arithmetic underflow or overflow",
-				0x12 => "division or modulo by zero",
-				0x21 => "enum overflow",
-				0x22 => "invalid encoded storage byte array accessed",
-				0x31 => "out-of-bounds array access; popping on an empty array",
-				0x32 => "out-of-bounds access of an array or bytesN",
-				0x41 => "out of memory",
-				0x51 => "uninitialized function",
-				code => return Some(format!("execution reverted: unknown panic code: {code:#x}")),
-			};
-
-			Some(format!("execution reverted: {msg}"))
-		},
-		// revert(string)
-		[0x08, 0xC3, 0x79, 0xA0] => {
-			let decoded = ethabi::decode(&[ethabi::ParamType::String], &exec_data[4..]).ok()?;
-			if let Some(ethabi::Token::String(msg)) = decoded.first() {
-				return Some(format!("execution reverted: {msg}"))
-			}
-			Some("execution reverted".to_string())
-		},
-		_ => {
-			log::debug!(target: LOG_TARGET, "Unknown revert function selector: {error_selector:?}");
-			Some("execution reverted".to_string())
-		},
-	}
-}
-
 /// The error type for the client.
 #[derive(Error, Debug)]
 pub enum ClientError {
diff --git a/substrate/frame/revive/src/benchmarking/call_builder.rs b/substrate/frame/revive/src/benchmarking/call_builder.rs
index 1177d47aadc3f2303533ef1e73258883c8e2fb1e..077e18ff5f0b805654d91c86f040e5269819eb67 100644
--- a/substrate/frame/revive/src/benchmarking/call_builder.rs
+++ b/substrate/frame/revive/src/benchmarking/call_builder.rs
@@ -22,7 +22,7 @@ use crate::{
 	storage::meter::Meter,
 	transient_storage::MeterEntry,
 	wasm::{PreparedCall, Runtime},
-	BalanceOf, Config, DebugBuffer, Error, GasMeter, MomentOf, Origin, WasmBlob, Weight,
+	BalanceOf, Config, Error, GasMeter, MomentOf, Origin, WasmBlob, Weight,
 };
 use alloc::{vec, vec::Vec};
 use frame_benchmarking::benchmarking;
@@ -38,7 +38,6 @@ pub struct CallSetup<T: Config> {
 	gas_meter: GasMeter<T>,
 	storage_meter: Meter<T>,
 	value: BalanceOf<T>,
-	debug_message: Option<DebugBuffer>,
 	data: Vec<u8>,
 	transient_storage_size: u32,
 }
@@ -91,7 +90,6 @@ where
 			gas_meter: GasMeter::new(Weight::MAX),
 			storage_meter,
 			value: 0u32.into(),
-			debug_message: None,
 			data: vec![],
 			transient_storage_size: 0,
 		}
@@ -122,16 +120,6 @@ where
 		self.transient_storage_size = size;
 	}
 
-	/// Set the debug message.
-	pub fn enable_debug_message(&mut self) {
-		self.debug_message = Some(Default::default());
-	}
-
-	/// Get the debug message.
-	pub fn debug_message(&self) -> Option<DebugBuffer> {
-		self.debug_message.clone()
-	}
-
 	/// Get the call's input data.
 	pub fn data(&self) -> Vec<u8> {
 		self.data.clone()
@@ -150,7 +138,6 @@ where
 			&mut self.gas_meter,
 			&mut self.storage_meter,
 			self.value,
-			self.debug_message.as_mut(),
 		);
 		if self.transient_storage_size > 0 {
 			Self::with_transient_storage(&mut ext.0, self.transient_storage_size).unwrap();
diff --git a/substrate/frame/revive/src/benchmarking/mod.rs b/substrate/frame/revive/src/benchmarking/mod.rs
index 1796348ff321209aa8b8c3c531b29d182d6f745c..16bdd6d1a18a034922b5eff47c190d76776f4d23 100644
--- a/substrate/frame/revive/src/benchmarking/mod.rs
+++ b/substrate/frame/revive/src/benchmarking/mod.rs
@@ -107,8 +107,6 @@ where
 			Code::Upload(module.code),
 			data,
 			salt,
-			DebugInfo::Skip,
-			CollectEvents::Skip,
 		);
 
 		let address = outcome.result?.addr;
@@ -558,6 +556,38 @@ mod benchmarks {
 		assert_eq!(result.unwrap(), 1);
 	}
 
+	#[benchmark(pov_mode = Measured)]
+	fn seal_to_account_id() {
+		// use a mapped address for the benchmark, to ensure that we bench the worst
+		// case (and not the fallback case).
+		let address = {
+			let caller = account("seal_to_account_id", 0, 0);
+			T::Currency::set_balance(&caller, caller_funding::<T>());
+			T::AddressMapper::map(&caller).unwrap();
+			T::AddressMapper::to_address(&caller)
+		};
+
+		let len = <T::AccountId as MaxEncodedLen>::max_encoded_len();
+		build_runtime!(runtime, memory: [vec![0u8; len], address.0, ]);
+
+		let result;
+		#[block]
+		{
+			result = runtime.bench_to_account_id(memory.as_mut_slice(), len as u32, 0);
+		}
+
+		assert_ok!(result);
+		assert_ne!(
+			memory.as_slice()[20..32],
+			[0xEE; 12],
+			"fallback suffix found where none should be"
+		);
+		assert_eq!(
+			T::AccountId::decode(&mut memory.as_slice()),
+			Ok(runtime.ext().to_account_id(&address))
+		);
+	}
+
 	#[benchmark(pov_mode = Measured)]
 	fn seal_code_hash() {
 		let contract = Contract::<T>::with_index(1, WasmModule::dummy(), vec![]).unwrap();
@@ -742,7 +772,7 @@ mod benchmarks {
 		let mut setup = CallSetup::<T>::default();
 		let input = setup.data();
 		let (mut ext, _) = setup.ext();
-		ext.override_export(crate::debug::ExportedFunction::Constructor);
+		ext.override_export(crate::exec::ExportedFunction::Constructor);
 
 		let mut runtime = crate::wasm::Runtime::<_, [u8]>::new(&mut ext, input);
 
@@ -1047,32 +1077,6 @@ mod benchmarks {
 		);
 	}
 
-	// Benchmark debug_message call
-	// Whereas this function is used in RPC mode only, it still should be secured
-	// against an excessive use.
-	//
-	// i: size of input in bytes up to maximum allowed contract memory or maximum allowed debug
-	// buffer size, whichever is less.
-	#[benchmark]
-	fn seal_debug_message(
-		i: Linear<0, { (limits::code::BLOB_BYTES).min(limits::DEBUG_BUFFER_BYTES) }>,
-	) {
-		let mut setup = CallSetup::<T>::default();
-		setup.enable_debug_message();
-		let (mut ext, _) = setup.ext();
-		let mut runtime = crate::wasm::Runtime::<_, [u8]>::new(&mut ext, vec![]);
-		// Fill memory with printable ASCII bytes.
-		let mut memory = (0..i).zip((32..127).cycle()).map(|i| i.1).collect::<Vec<_>>();
-
-		let result;
-		#[block]
-		{
-			result = runtime.bench_debug_message(memory.as_mut_slice(), 0, i);
-		}
-		assert_ok!(result);
-		assert_eq!(setup.debug_message().unwrap().len() as u32, i);
-	}
-
 	#[benchmark(skip_meta, pov_mode = Measured)]
 	fn get_storage_empty() -> Result<(), BenchmarkError> {
 		let max_key_len = limits::STORAGE_KEY_BYTES;
diff --git a/substrate/frame/revive/src/debug.rs b/substrate/frame/revive/src/debug.rs
deleted file mode 100644
index d1fc0823e03dff6719018ee60f0c0668aca37d7f..0000000000000000000000000000000000000000
--- a/substrate/frame/revive/src/debug.rs
+++ /dev/null
@@ -1,109 +0,0 @@
-// This file is part of Substrate.
-
-// Copyright (C) Parity Technologies (UK) Ltd.
-// SPDX-License-Identifier: Apache-2.0
-
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// 	http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-pub use crate::{
-	exec::{ExecResult, ExportedFunction},
-	primitives::ExecReturnValue,
-};
-use crate::{Config, LOG_TARGET};
-use sp_core::H160;
-
-/// Umbrella trait for all interfaces that serves for debugging.
-pub trait Debugger<T: Config>: Tracing<T> + CallInterceptor<T> {}
-
-impl<T: Config, V> Debugger<T> for V where V: Tracing<T> + CallInterceptor<T> {}
-
-/// Defines methods to capture contract calls, enabling external observers to
-/// measure, trace, and react to contract interactions.
-pub trait Tracing<T: Config> {
-	/// The type of [`CallSpan`] that is created by this trait.
-	type CallSpan: CallSpan;
-
-	/// Creates a new call span to encompass the upcoming contract execution.
-	///
-	/// This method should be invoked just before the execution of a contract and
-	/// marks the beginning of a traceable span of execution.
-	///
-	/// # Arguments
-	///
-	/// * `contract_address` - The address of the contract that is about to be executed.
-	/// * `entry_point` - Describes whether the call is the constructor or a regular call.
-	/// * `input_data` - The raw input data of the call.
-	fn new_call_span(
-		contract_address: &H160,
-		entry_point: ExportedFunction,
-		input_data: &[u8],
-	) -> Self::CallSpan;
-}
-
-/// Defines a span of execution for a contract call.
-pub trait CallSpan {
-	/// Called just after the execution of a contract.
-	///
-	/// # Arguments
-	///
-	/// * `output` - The raw output of the call.
-	fn after_call(self, output: &ExecReturnValue);
-}
-
-impl<T: Config> Tracing<T> for () {
-	type CallSpan = ();
-
-	fn new_call_span(contract_address: &H160, entry_point: ExportedFunction, input_data: &[u8]) {
-		log::trace!(target: LOG_TARGET, "call {entry_point:?} address: {contract_address:?}, input_data: {input_data:?}")
-	}
-}
-
-impl CallSpan for () {
-	fn after_call(self, output: &ExecReturnValue) {
-		log::trace!(target: LOG_TARGET, "call result {output:?}")
-	}
-}
-
-/// Provides an interface for intercepting contract calls.
-pub trait CallInterceptor<T: Config> {
-	/// Allows to intercept contract calls and decide whether they should be executed or not.
-	/// If the call is intercepted, the mocked result of the call is returned.
-	///
-	/// # Arguments
-	///
-	/// * `contract_address` - The address of the contract that is about to be executed.
-	/// * `entry_point` - Describes whether the call is the constructor or a regular call.
-	/// * `input_data` - The raw input data of the call.
-	///
-	/// # Expected behavior
-	///
-	/// This method should return:
-	/// * `Some(ExecResult)` - if the call should be intercepted and the mocked result of the call
-	/// is returned.
-	/// * `None` - otherwise, i.e. the call should be executed normally.
-	fn intercept_call(
-		contract_address: &H160,
-		entry_point: ExportedFunction,
-		input_data: &[u8],
-	) -> Option<ExecResult>;
-}
-
-impl<T: Config> CallInterceptor<T> for () {
-	fn intercept_call(
-		_contract_address: &H160,
-		_entry_point: ExportedFunction,
-		_input_data: &[u8],
-	) -> Option<ExecResult> {
-		None
-	}
-}
diff --git a/substrate/frame/revive/src/evm.rs b/substrate/frame/revive/src/evm.rs
index c8c967fbe091bb2af5bfaacaefbe854d4f9d0043..33660a36aa6ea57f713ccf57a62f8ba013ac4ef9 100644
--- a/substrate/frame/revive/src/evm.rs
+++ b/substrate/frame/revive/src/evm.rs
@@ -19,6 +19,51 @@
 
 mod api;
 pub use api::*;
+mod tracing;
+pub use tracing::*;
 mod gas_encoder;
 pub use gas_encoder::*;
 pub mod runtime;
+
+use crate::alloc::{format, string::*};
+
+/// Extract the revert message from a revert("msg") solidity statement.
+pub fn extract_revert_message(exec_data: &[u8]) -> Option<String> {
+	let error_selector = exec_data.get(0..4)?;
+
+	match error_selector {
+		// assert(false)
+		[0x4E, 0x48, 0x7B, 0x71] => {
+			let panic_code: u32 = U256::from_big_endian(exec_data.get(4..36)?).try_into().ok()?;
+
+			// See https://docs.soliditylang.org/en/latest/control-structures.html#panic-via-assert-and-error-via-require
+			let msg = match panic_code {
+				0x00 => "generic panic",
+				0x01 => "assert(false)",
+				0x11 => "arithmetic underflow or overflow",
+				0x12 => "division or modulo by zero",
+				0x21 => "enum overflow",
+				0x22 => "invalid encoded storage byte array accessed",
+				0x31 => "out-of-bounds array access; popping on an empty array",
+				0x32 => "out-of-bounds access of an array or bytesN",
+				0x41 => "out of memory",
+				0x51 => "uninitialized function",
+				code => return Some(format!("execution reverted: unknown panic code: {code:#x}")),
+			};
+
+			Some(format!("execution reverted: {msg}"))
+		},
+		// revert(string)
+		[0x08, 0xC3, 0x79, 0xA0] => {
+			let decoded = ethabi::decode(&[ethabi::ParamKind::String], &exec_data[4..]).ok()?;
+			if let Some(ethabi::Token::String(msg)) = decoded.first() {
+				return Some(format!("execution reverted: {}", String::from_utf8_lossy(msg)))
+			}
+			Some("execution reverted".to_string())
+		},
+		_ => {
+			log::debug!(target: crate::LOG_TARGET, "Unknown revert function selector: {error_selector:?}");
+			Some("execution reverted".to_string())
+		},
+	}
+}
diff --git a/substrate/frame/revive/src/evm/api.rs b/substrate/frame/revive/src/evm/api.rs
index fe18c8735bed4d8dc435f8517d86f67aa9c70393..7a34fdc83f9a5140cc459f4be0da188bd81b5b7b 100644
--- a/substrate/frame/revive/src/evm/api.rs
+++ b/substrate/frame/revive/src/evm/api.rs
@@ -16,6 +16,8 @@
 // limitations under the License.
 //! JSON-RPC methods and types, for Ethereum.
 
+mod hex_serde;
+
 mod byte;
 pub use byte::*;
 
@@ -25,6 +27,9 @@ pub use rlp;
 mod type_id;
 pub use type_id::*;
 
+mod debug_rpc_types;
+pub use debug_rpc_types::*;
+
 mod rpc_types;
 mod rpc_types_gen;
 pub use rpc_types_gen::*;
diff --git a/substrate/frame/revive/src/evm/api/byte.rs b/substrate/frame/revive/src/evm/api/byte.rs
index c2d64f8e5e424b2086e0247e5ccea0e08d4350b0..f11966d0072cf64d674bd2925862720192c43924 100644
--- a/substrate/frame/revive/src/evm/api/byte.rs
+++ b/substrate/frame/revive/src/evm/api/byte.rs
@@ -15,79 +15,16 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 //! Define Byte wrapper types for encoding and decoding hex strings
+use super::hex_serde::HexCodec;
 use alloc::{vec, vec::Vec};
 use codec::{Decode, Encode};
 use core::{
 	fmt::{Debug, Display, Formatter, Result as FmtResult},
 	str::FromStr,
 };
-use hex_serde::HexCodec;
 use scale_info::TypeInfo;
 use serde::{Deserialize, Serialize};
 
-mod hex_serde {
-	#[cfg(not(feature = "std"))]
-	use alloc::{format, string::String, vec::Vec};
-	use serde::{Deserialize, Deserializer, Serializer};
-
-	pub trait HexCodec: Sized {
-		type Error;
-		fn to_hex(&self) -> String;
-		fn from_hex(s: String) -> Result<Self, Self::Error>;
-	}
-
-	impl HexCodec for u8 {
-		type Error = core::num::ParseIntError;
-		fn to_hex(&self) -> String {
-			format!("0x{:x}", self)
-		}
-		fn from_hex(s: String) -> Result<Self, Self::Error> {
-			u8::from_str_radix(s.trim_start_matches("0x"), 16)
-		}
-	}
-
-	impl<const T: usize> HexCodec for [u8; T] {
-		type Error = hex::FromHexError;
-		fn to_hex(&self) -> String {
-			format!("0x{}", hex::encode(self))
-		}
-		fn from_hex(s: String) -> Result<Self, Self::Error> {
-			let data = hex::decode(s.trim_start_matches("0x"))?;
-			data.try_into().map_err(|_| hex::FromHexError::InvalidStringLength)
-		}
-	}
-
-	impl HexCodec for Vec<u8> {
-		type Error = hex::FromHexError;
-		fn to_hex(&self) -> String {
-			format!("0x{}", hex::encode(self))
-		}
-		fn from_hex(s: String) -> Result<Self, Self::Error> {
-			hex::decode(s.trim_start_matches("0x"))
-		}
-	}
-
-	pub fn serialize<S, T>(value: &T, serializer: S) -> Result<S::Ok, S::Error>
-	where
-		S: Serializer,
-		T: HexCodec,
-	{
-		let s = value.to_hex();
-		serializer.serialize_str(&s)
-	}
-
-	pub fn deserialize<'de, D, T>(deserializer: D) -> Result<T, D::Error>
-	where
-		D: Deserializer<'de>,
-		T: HexCodec,
-		<T as HexCodec>::Error: core::fmt::Debug,
-	{
-		let s = String::deserialize(deserializer)?;
-		let value = T::from_hex(s).map_err(|e| serde::de::Error::custom(format!("{:?}", e)))?;
-		Ok(value)
-	}
-}
-
 impl FromStr for Bytes {
 	type Err = hex::FromHexError;
 	fn from_str(s: &str) -> Result<Self, Self::Err> {
@@ -100,7 +37,7 @@ macro_rules! impl_hex {
     ($type:ident, $inner:ty, $default:expr) => {
         #[derive(Encode, Decode, Eq, PartialEq, TypeInfo, Clone, Serialize, Deserialize)]
         #[doc = concat!("`", stringify!($inner), "`", " wrapper type for encoding and decoding hex strings")]
-        pub struct $type(#[serde(with = "hex_serde")] pub $inner);
+        pub struct $type(#[serde(with = "crate::evm::api::hex_serde")] pub $inner);
 
         impl Default for $type {
             fn default() -> Self {
@@ -131,6 +68,13 @@ macro_rules! impl_hex {
     };
 }
 
+impl Bytes {
+	/// See `Vec::is_empty`
+	pub fn is_empty(&self) -> bool {
+		self.0.is_empty()
+	}
+}
+
 impl_hex!(Byte, u8, 0u8);
 impl_hex!(Bytes, Vec<u8>, vec![]);
 impl_hex!(Bytes8, [u8; 8], [0u8; 8]);
diff --git a/substrate/frame/revive/src/evm/api/debug_rpc_types.rs b/substrate/frame/revive/src/evm/api/debug_rpc_types.rs
new file mode 100644
index 0000000000000000000000000000000000000000..0857a59fbf3b650707069e1a0a8387acad49fe99
--- /dev/null
+++ b/substrate/frame/revive/src/evm/api/debug_rpc_types.rs
@@ -0,0 +1,219 @@
+// This file is part of Substrate.
+
+// Copyright (C) Parity Technologies (UK) Ltd.
+// SPDX-License-Identifier: Apache-2.0
+
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// 	http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+use crate::evm::{Bytes, CallTracer};
+use alloc::{fmt, string::String, vec::Vec};
+use codec::{Decode, Encode};
+use scale_info::TypeInfo;
+use serde::{
+	de::{self, MapAccess, Visitor},
+	Deserialize, Deserializer, Serialize,
+};
+use sp_core::{H160, H256, U256};
+
+/// Tracer configuration used to trace calls.
+#[derive(TypeInfo, Debug, Clone, Encode, Decode, Serialize, PartialEq)]
+#[serde(tag = "tracer", content = "tracerConfig")]
+pub enum TracerConfig {
+	/// A tracer that captures call traces.
+	#[serde(rename = "callTracer")]
+	CallTracer {
+		/// Whether or not to capture logs.
+		#[serde(rename = "withLog")]
+		with_logs: bool,
+	},
+}
+
+impl TracerConfig {
+	/// Build the tracer associated to this config.
+	pub fn build<G>(self, gas_mapper: G) -> CallTracer<U256, G> {
+		match self {
+			Self::CallTracer { with_logs } => CallTracer::new(with_logs, gas_mapper),
+		}
+	}
+}
+
+/// Custom deserializer to support the following JSON format:
+///
+/// ```json
+/// { "tracer": "callTracer", "tracerConfig": { "withLogs": false } }
+/// ```
+///
+/// ```json
+/// { "tracer": "callTracer" }
+/// ```
+impl<'de> Deserialize<'de> for TracerConfig {
+	fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
+	where
+		D: Deserializer<'de>,
+	{
+		struct TracerConfigVisitor;
+
+		impl<'de> Visitor<'de> for TracerConfigVisitor {
+			type Value = TracerConfig;
+
+			fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
+				formatter.write_str("a map with tracer and optional tracerConfig")
+			}
+
+			fn visit_map<M>(self, mut map: M) -> Result<Self::Value, M::Error>
+			where
+				M: MapAccess<'de>,
+			{
+				let mut tracer_type: Option<String> = None;
+				let mut with_logs = None;
+
+				while let Some(key) = map.next_key::<String>()? {
+					match key.as_str() {
+						"tracer" => {
+							tracer_type = map.next_value()?;
+						},
+						"tracerConfig" => {
+							#[derive(Deserialize)]
+							struct CallTracerConfig {
+								#[serde(rename = "withLogs")]
+								with_logs: Option<bool>,
+							}
+							let inner: CallTracerConfig = map.next_value()?;
+							with_logs = inner.with_logs;
+						},
+						_ => {},
+					}
+				}
+
+				match tracer_type.as_deref() {
+					Some("callTracer") =>
+						Ok(TracerConfig::CallTracer { with_logs: with_logs.unwrap_or(true) }),
+					_ => Err(de::Error::custom("Unsupported or missing tracer type")),
+				}
+			}
+		}
+
+		deserializer.deserialize_map(TracerConfigVisitor)
+	}
+}
+
+#[test]
+fn test_tracer_config_serialization() {
+	let tracers = vec![
+		(r#"{"tracer": "callTracer"}"#, TracerConfig::CallTracer { with_logs: true }),
+		(
+			r#"{"tracer": "callTracer", "tracerConfig": { "withLogs": true }}"#,
+			TracerConfig::CallTracer { with_logs: true },
+		),
+		(
+			r#"{"tracer": "callTracer", "tracerConfig": { "withLogs": false }}"#,
+			TracerConfig::CallTracer { with_logs: false },
+		),
+	];
+
+	for (json_data, expected) in tracers {
+		let result: TracerConfig =
+			serde_json::from_str(json_data).expect("Deserialization should succeed");
+		assert_eq!(result, expected);
+	}
+}
+
+impl Default for TracerConfig {
+	fn default() -> Self {
+		TracerConfig::CallTracer { with_logs: false }
+	}
+}
+
+/// The type of call that was executed.
+#[derive(
+	Default, TypeInfo, Encode, Decode, Serialize, Deserialize, Eq, PartialEq, Clone, Debug,
+)]
+#[serde(rename_all = "UPPERCASE")]
+pub enum CallType {
+	/// A regular call.
+	#[default]
+	Call,
+	/// A read-only call.
+	StaticCall,
+	/// A delegate call.
+	DelegateCall,
+}
+
+/// A smart contract execution call trace.
+#[derive(
+	TypeInfo, Default, Encode, Decode, Serialize, Deserialize, Clone, Debug, Eq, PartialEq,
+)]
+pub struct CallTrace<Gas = U256> {
+	/// Address of the sender.
+	pub from: H160,
+	/// Address of the receiver.
+	pub to: H160,
+	/// Call input data.
+	pub input: Vec<u8>,
+	/// Amount of value transferred.
+	#[serde(skip_serializing_if = "U256::is_zero")]
+	pub value: U256,
+	/// Type of call.
+	#[serde(rename = "type")]
+	pub call_type: CallType,
+	/// Amount of gas provided for the call.
+	pub gas: Gas,
+	/// Amount of gas used.
+	#[serde(rename = "gasUsed")]
+	pub gas_used: Gas,
+	/// Return data.
+	#[serde(flatten, skip_serializing_if = "Bytes::is_empty")]
+	pub output: Bytes,
+	/// The error message if the call failed.
+	#[serde(skip_serializing_if = "Option::is_none")]
+	pub error: Option<String>,
+	/// The revert reason, if the call reverted.
+	#[serde(rename = "revertReason")]
+	pub revert_reason: Option<String>,
+	/// List of sub-calls.
+	#[serde(skip_serializing_if = "Vec::is_empty")]
+	pub calls: Vec<CallTrace<Gas>>,
+	/// List of logs emitted during the call.
+	#[serde(skip_serializing_if = "Vec::is_empty")]
+	pub logs: Vec<CallLog>,
+}
+
+/// A log emitted during a call.
+#[derive(
+	Debug, Default, Clone, Encode, Decode, TypeInfo, Serialize, Deserialize, Eq, PartialEq,
+)]
+pub struct CallLog {
+	/// The address of the contract that emitted the log.
+	pub address: H160,
+	/// The log's data.
+	#[serde(skip_serializing_if = "Bytes::is_empty")]
+	pub data: Bytes,
+	/// The topics used to index the log.
+	#[serde(default, skip_serializing_if = "Vec::is_empty")]
+	pub topics: Vec<H256>,
+	/// Position of the log relative to subcalls within the same trace
+	/// See <https://github.com/ethereum/go-ethereum/pull/28389> for details
+	#[serde(with = "super::hex_serde")]
+	pub position: u32,
+}
+
+/// A transaction trace
+#[derive(Serialize, Deserialize, Clone, Debug)]
+pub struct TransactionTrace {
+	/// The transaction hash.
+	#[serde(rename = "txHash")]
+	pub tx_hash: H256,
+	/// The trace of the transaction.
+	#[serde(rename = "result")]
+	pub trace: CallTrace,
+}
diff --git a/substrate/frame/revive/src/evm/api/hex_serde.rs b/substrate/frame/revive/src/evm/api/hex_serde.rs
new file mode 100644
index 0000000000000000000000000000000000000000..ba07b36fa4be6e5cba82bba719c3893cd32aafc5
--- /dev/null
+++ b/substrate/frame/revive/src/evm/api/hex_serde.rs
@@ -0,0 +1,84 @@
+// This file is part of Substrate.
+
+// Copyright (C) Parity Technologies (UK) Ltd.
+// SPDX-License-Identifier: Apache-2.0
+
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// 	http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+use alloc::{format, string::String, vec::Vec};
+use serde::{Deserialize, Deserializer, Serializer};
+
+pub trait HexCodec: Sized {
+	type Error;
+	fn to_hex(&self) -> String;
+	fn from_hex(s: String) -> Result<Self, Self::Error>;
+}
+
+macro_rules! impl_hex_codec {
+    ($($t:ty),*) => {
+        $(
+            impl HexCodec for $t {
+                type Error = core::num::ParseIntError;
+                fn to_hex(&self) -> String {
+                    format!("0x{:x}", self)
+                }
+                fn from_hex(s: String) -> Result<Self, Self::Error> {
+                    <$t>::from_str_radix(s.trim_start_matches("0x"), 16)
+                }
+            }
+        )*
+    };
+}
+
+impl_hex_codec!(u8, u32);
+
+impl<const T: usize> HexCodec for [u8; T] {
+	type Error = hex::FromHexError;
+	fn to_hex(&self) -> String {
+		format!("0x{}", hex::encode(self))
+	}
+	fn from_hex(s: String) -> Result<Self, Self::Error> {
+		let data = hex::decode(s.trim_start_matches("0x"))?;
+		data.try_into().map_err(|_| hex::FromHexError::InvalidStringLength)
+	}
+}
+
+impl HexCodec for Vec<u8> {
+	type Error = hex::FromHexError;
+	fn to_hex(&self) -> String {
+		format!("0x{}", hex::encode(self))
+	}
+	fn from_hex(s: String) -> Result<Self, Self::Error> {
+		hex::decode(s.trim_start_matches("0x"))
+	}
+}
+
+pub fn serialize<S, T>(value: &T, serializer: S) -> Result<S::Ok, S::Error>
+where
+	S: Serializer,
+	T: HexCodec,
+{
+	let s = value.to_hex();
+	serializer.serialize_str(&s)
+}
+
+pub fn deserialize<'de, D, T>(deserializer: D) -> Result<T, D::Error>
+where
+	D: Deserializer<'de>,
+	T: HexCodec,
+	<T as HexCodec>::Error: core::fmt::Debug,
+{
+	let s = String::deserialize(deserializer)?;
+	let value = T::from_hex(s).map_err(|e| serde::de::Error::custom(format!("{:?}", e)))?;
+	Ok(value)
+}
diff --git a/substrate/frame/revive/src/evm/runtime.rs b/substrate/frame/revive/src/evm/runtime.rs
index d4b344e20eb850e6c42d84bdf870204c1382cb67..0e5fc3da545b5eddc4363676877f721473c8c42a 100644
--- a/substrate/frame/revive/src/evm/runtime.rs
+++ b/substrate/frame/revive/src/evm/runtime.rs
@@ -20,7 +20,7 @@ use crate::{
 		api::{GenericTransaction, TransactionSigned},
 		GasEncoder,
 	},
-	AccountIdOf, AddressMapper, BalanceOf, Config, MomentOf, LOG_TARGET,
+	AccountIdOf, AddressMapper, BalanceOf, Config, MomentOf, Weight, LOG_TARGET,
 };
 use alloc::vec::Vec;
 use codec::{Decode, Encode};
@@ -72,6 +72,18 @@ where
 	}
 }
 
+/// Convert a `Weight` into a gas value, using the fixed `GAS_PRICE`.
+/// and the `Config::WeightPrice` to compute the fee.
+/// The gas is calculated as `fee / GAS_PRICE`, rounded up to the nearest integer.
+pub fn gas_from_weight<T: Config>(weight: Weight) -> U256
+where
+	BalanceOf<T>: Into<U256>,
+{
+	use sp_runtime::traits::Convert;
+	let fee: BalanceOf<T> = T::WeightPrice::convert(weight);
+	gas_from_fee(fee)
+}
+
 /// Wraps [`generic::UncheckedExtrinsic`] to support checking unsigned
 /// [`crate::Call::eth_transact`] extrinsic.
 #[derive(Encode, Decode, Clone, PartialEq, Eq, RuntimeDebug)]
diff --git a/substrate/frame/revive/src/evm/tracing.rs b/substrate/frame/revive/src/evm/tracing.rs
new file mode 100644
index 0000000000000000000000000000000000000000..7466ec1de487738bcb662e92b9127852098e304d
--- /dev/null
+++ b/substrate/frame/revive/src/evm/tracing.rs
@@ -0,0 +1,134 @@
+// This file is part of Substrate.
+
+// Copyright (C) Parity Technologies (UK) Ltd.
+// SPDX-License-Identifier: Apache-2.0
+
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// 	http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+use crate::{
+	evm::{extract_revert_message, CallLog, CallTrace, CallType},
+	primitives::ExecReturnValue,
+	tracing::Tracer,
+	DispatchError, Weight,
+};
+use alloc::{format, string::ToString, vec::Vec};
+use sp_core::{H160, H256, U256};
+
+/// A Tracer that reports logs and nested call traces transactions.
+#[derive(Default, Debug, Clone, PartialEq, Eq)]
+pub struct CallTracer<Gas, GasMapper> {
+	/// Map Weight to Gas equivalent.
+	gas_mapper: GasMapper,
+	/// Store all in-progress CallTrace instances.
+	traces: Vec<CallTrace<Gas>>,
+	/// Stack of indices to the current active traces.
+	current_stack: Vec<usize>,
+	/// whether or not to capture logs.
+	with_log: bool,
+}
+
+impl<Gas, GasMapper> CallTracer<Gas, GasMapper> {
+	/// Create a new [`CallTracer`] instance.
+	pub fn new(with_log: bool, gas_mapper: GasMapper) -> Self {
+		Self { gas_mapper, traces: Vec::new(), current_stack: Vec::new(), with_log }
+	}
+
+	/// Collect the traces and return them.
+	pub fn collect_traces(&mut self) -> Vec<CallTrace<Gas>> {
+		core::mem::take(&mut self.traces)
+	}
+}
+
+impl<Gas: Default, GasMapper: Fn(Weight) -> Gas> Tracer for CallTracer<Gas, GasMapper> {
+	fn enter_child_span(
+		&mut self,
+		from: H160,
+		to: H160,
+		is_delegate_call: bool,
+		is_read_only: bool,
+		value: U256,
+		input: &[u8],
+		gas_left: Weight,
+	) {
+		let call_type = if is_read_only {
+			CallType::StaticCall
+		} else if is_delegate_call {
+			CallType::DelegateCall
+		} else {
+			CallType::Call
+		};
+
+		self.traces.push(CallTrace {
+			from,
+			to,
+			value,
+			call_type,
+			input: input.to_vec(),
+			gas: (self.gas_mapper)(gas_left),
+			..Default::default()
+		});
+
+		// Push the index onto the stack of the current active trace
+		self.current_stack.push(self.traces.len() - 1);
+	}
+
+	fn log_event(&mut self, address: H160, topics: &[H256], data: &[u8]) {
+		if !self.with_log {
+			return;
+		}
+
+		let current_index = self.current_stack.last().unwrap();
+		let position = self.traces[*current_index].calls.len() as u32;
+		let log =
+			CallLog { address, topics: topics.to_vec(), data: data.to_vec().into(), position };
+
+		let current_index = *self.current_stack.last().unwrap();
+		self.traces[current_index].logs.push(log);
+	}
+
+	fn exit_child_span(&mut self, output: &ExecReturnValue, gas_used: Weight) {
+		// Set the output of the current trace
+		let current_index = self.current_stack.pop().unwrap();
+		let trace = &mut self.traces[current_index];
+		trace.output = output.data.clone().into();
+		trace.gas_used = (self.gas_mapper)(gas_used);
+
+		if output.did_revert() {
+			trace.revert_reason = extract_revert_message(&output.data);
+			trace.error = Some("execution reverted".to_string());
+		}
+
+		//  Move the current trace into its parent
+		if let Some(parent_index) = self.current_stack.last() {
+			let child_trace = self.traces.remove(current_index);
+			self.traces[*parent_index].calls.push(child_trace);
+		}
+	}
+	fn exit_child_span_with_error(&mut self, error: DispatchError, gas_used: Weight) {
+		// Set the output of the current trace
+		let current_index = self.current_stack.pop().unwrap();
+		let trace = &mut self.traces[current_index];
+		trace.gas_used = (self.gas_mapper)(gas_used);
+
+		trace.error = match error {
+			DispatchError::Module(sp_runtime::ModuleError { message, .. }) =>
+				Some(message.unwrap_or_default().to_string()),
+			_ => Some(format!("{:?}", error)),
+		};
+
+		//  Move the current trace into its parent
+		if let Some(parent_index) = self.current_stack.last() {
+			let child_trace = self.traces.remove(current_index);
+			self.traces[*parent_index].calls.push(child_trace);
+		}
+	}
+}
diff --git a/substrate/frame/revive/src/exec.rs b/substrate/frame/revive/src/exec.rs
index c069216d6cc71bc96b6d18512198aece824535f7..d2ef6c9c7ba6ce2e79f2b1608702b1340ae8db1b 100644
--- a/substrate/frame/revive/src/exec.rs
+++ b/substrate/frame/revive/src/exec.rs
@@ -17,15 +17,15 @@
 
 use crate::{
 	address::{self, AddressMapper},
-	debug::{CallInterceptor, CallSpan, Tracing},
 	gas::GasMeter,
 	limits,
 	primitives::{ExecReturnValue, StorageDeposit},
 	runtime_decl_for_revive_api::{Decode, Encode, RuntimeDebugNoBound, TypeInfo},
 	storage::{self, meter::Diff, WriteOutcome},
+	tracing::if_tracing,
 	transient_storage::TransientStorage,
-	BalanceOf, CodeInfo, CodeInfoOf, Config, ContractInfo, ContractInfoOf, DebugBuffer, Error,
-	Event, ImmutableData, ImmutableDataOf, Pallet as Contracts, LOG_TARGET,
+	BalanceOf, CodeInfo, CodeInfoOf, Config, ContractInfo, ContractInfoOf, Error, Event,
+	ImmutableData, ImmutableDataOf, Pallet as Contracts,
 };
 use alloc::vec::Vec;
 use core::{fmt::Debug, marker::PhantomData, mem};
@@ -293,6 +293,9 @@ pub trait Ext: sealing::Sealed {
 	/// Check if a contract lives at the specified `address`.
 	fn is_contract(&self, address: &H160) -> bool;
 
+	/// Returns the account id for the given `address`.
+	fn to_account_id(&self, address: &H160) -> AccountIdOf<Self::T>;
+
 	/// Returns the code hash of the contract for the given `address`.
 	/// If not a contract but account exists then `keccak_256([])` is returned, otherwise `zero`.
 	fn code_hash(&self, address: &H160) -> H256;
@@ -378,19 +381,6 @@ pub trait Ext: sealing::Sealed {
 	/// Charges `diff` from the meter.
 	fn charge_storage(&mut self, diff: &Diff);
 
-	/// Append a string to the debug buffer.
-	///
-	/// It is added as-is without any additional new line.
-	///
-	/// This is a no-op if debug message recording is disabled which is always the case
-	/// when the code is executing on-chain.
-	///
-	/// Returns `true` if debug message recording is enabled. Otherwise `false` is returned.
-	fn append_debug_buffer(&mut self, msg: &str) -> bool;
-
-	/// Returns `true` if debug message recording is enabled. Otherwise `false` is returned.
-	fn debug_buffer_enabled(&self) -> bool;
-
 	/// Call some dispatchable and return the result.
 	fn call_runtime(&self, call: <Self::T as Config>::RuntimeCall) -> DispatchResultWithPostInfo;
 
@@ -555,11 +545,6 @@ pub struct Stack<'a, T: Config, E> {
 	frames: BoundedVec<Frame<T>, ConstU32<{ limits::CALL_STACK_DEPTH }>>,
 	/// Statically guarantee that each call stack has at least one frame.
 	first_frame: Frame<T>,
-	/// A text buffer used to output human readable information.
-	///
-	/// All the bytes added to this field should be valid UTF-8. The buffer has no defined
-	/// structure and is intended to be shown to users as-is for debugging purposes.
-	debug_message: Option<&'a mut DebugBuffer>,
 	/// Transient storage used to store data, which is kept for the duration of a transaction.
 	transient_storage: TransientStorage<T>,
 	/// Whether or not actual transfer of funds should be performed.
@@ -765,11 +750,6 @@ where
 {
 	/// Create and run a new call stack by calling into `dest`.
 	///
-	/// # Note
-	///
-	/// `debug_message` should only ever be set to `Some` when executing as an RPC because
-	/// it adds allocations and could be abused to drive the runtime into an OOM panic.
-	///
 	/// # Return Value
 	///
 	/// Result<(ExecReturnValue, CodeSize), (ExecError, CodeSize)>
@@ -781,7 +761,6 @@ where
 		value: U256,
 		input_data: Vec<u8>,
 		skip_transfer: bool,
-		debug_message: Option<&'a mut DebugBuffer>,
 	) -> ExecResult {
 		let dest = T::AddressMapper::to_account_id(&dest);
 		if let Some((mut stack, executable)) = Self::new(
@@ -791,21 +770,33 @@ where
 			storage_meter,
 			value,
 			skip_transfer,
-			debug_message,
 		)? {
 			stack.run(executable, input_data).map(|_| stack.first_frame.last_frame_output)
 		} else {
-			Self::transfer_from_origin(&origin, &origin, &dest, value)
+			if_tracing(|t| {
+				let address =
+					origin.account_id().map(T::AddressMapper::to_address).unwrap_or_default();
+				let dest = T::AddressMapper::to_address(&dest);
+				t.enter_child_span(address, dest, false, false, value, &input_data, Weight::zero());
+			});
+
+			let result = Self::transfer_from_origin(&origin, &origin, &dest, value);
+			match result {
+				Ok(ref output) => {
+					if_tracing(|t| {
+						t.exit_child_span(&output, Weight::zero());
+					});
+				},
+				Err(e) => {
+					if_tracing(|t| t.exit_child_span_with_error(e.error.into(), Weight::zero()));
+				},
+			}
+			result
 		}
 	}
 
 	/// Create and run a new call stack by instantiating a new contract.
 	///
-	/// # Note
-	///
-	/// `debug_message` should only ever be set to `Some` when executing as an RPC because
-	/// it adds allocations and could be abused to drive the runtime into an OOM panic.
-	///
 	/// # Return Value
 	///
 	/// Result<(NewContractAccountId, ExecReturnValue), ExecError)>
@@ -818,7 +809,6 @@ where
 		input_data: Vec<u8>,
 		salt: Option<&[u8; 32]>,
 		skip_transfer: bool,
-		debug_message: Option<&'a mut DebugBuffer>,
 	) -> Result<(H160, ExecReturnValue), ExecError> {
 		let (mut stack, executable) = Self::new(
 			FrameArgs::Instantiate {
@@ -832,7 +822,6 @@ where
 			storage_meter,
 			value,
 			skip_transfer,
-			debug_message,
 		)?
 		.expect(FRAME_ALWAYS_EXISTS_ON_INSTANTIATE);
 		let address = T::AddressMapper::to_address(&stack.top_frame().account_id);
@@ -848,7 +837,6 @@ where
 		gas_meter: &'a mut GasMeter<T>,
 		storage_meter: &'a mut storage::meter::Meter<T>,
 		value: BalanceOf<T>,
-		debug_message: Option<&'a mut DebugBuffer>,
 	) -> (Self, E) {
 		Self::new(
 			FrameArgs::Call {
@@ -861,7 +849,6 @@ where
 			storage_meter,
 			value.into(),
 			false,
-			debug_message,
 		)
 		.unwrap()
 		.unwrap()
@@ -878,7 +865,6 @@ where
 		storage_meter: &'a mut storage::meter::Meter<T>,
 		value: U256,
 		skip_transfer: bool,
-		debug_message: Option<&'a mut DebugBuffer>,
 	) -> Result<Option<(Self, E)>, ExecError> {
 		origin.ensure_mapped()?;
 		let Some((first_frame, executable)) = Self::new_frame(
@@ -903,7 +889,6 @@ where
 			block_number: <frame_system::Pallet<T>>::block_number(),
 			first_frame,
 			frames: Default::default(),
-			debug_message,
 			transient_storage: TransientStorage::new(limits::TRANSIENT_STORAGE_BYTES),
 			skip_transfer,
 			_phantom: Default::default(),
@@ -1051,6 +1036,7 @@ where
 	fn run(&mut self, executable: E, input_data: Vec<u8>) -> Result<(), ExecError> {
 		let frame = self.top_frame();
 		let entry_point = frame.entry_point;
+		let is_delegate_call = frame.delegate.is_some();
 		let delegated_code_hash =
 			if frame.delegate.is_some() { Some(*executable.code_hash()) } else { None };
 
@@ -1071,6 +1057,9 @@ where
 		let do_transaction = || -> ExecResult {
 			let caller = self.caller();
 			let frame = top_frame_mut!(self);
+			let read_only = frame.read_only;
+			let value_transferred = frame.value_transferred;
+			let account_id = &frame.account_id.clone();
 
 			// We need to charge the storage deposit before the initial transfer so that
 			// it can create the account in case the initial transfer is < ed.
@@ -1078,10 +1067,11 @@ where
 				// Root origin can't be used to instantiate a contract, so it is safe to assume that
 				// if we reached this point the origin has an associated account.
 				let origin = &self.origin.account_id()?;
+
 				frame.nested_storage.charge_instantiate(
 					origin,
-					&frame.account_id,
-					frame.contract_info.get(&frame.account_id),
+					&account_id,
+					frame.contract_info.get(&account_id),
 					executable.code_info(),
 					self.skip_transfer,
 				)?;
@@ -1102,15 +1092,34 @@ where
 				)?;
 			}
 
-			let contract_address = T::AddressMapper::to_address(&top_frame!(self).account_id);
-
-			let call_span = T::Debug::new_call_span(&contract_address, entry_point, &input_data);
+			let contract_address = T::AddressMapper::to_address(account_id);
+			let maybe_caller_address = caller.account_id().map(T::AddressMapper::to_address);
+
+			if_tracing(|tracer| {
+				tracer.enter_child_span(
+					maybe_caller_address.unwrap_or_default(),
+					contract_address,
+					is_delegate_call,
+					read_only,
+					value_transferred,
+					&input_data,
+					frame.nested_gas.gas_left(),
+				);
+			});
 
-			let output = T::Debug::intercept_call(&contract_address, entry_point, &input_data)
-				.unwrap_or_else(|| executable.execute(self, entry_point, input_data))
-				.map_err(|e| ExecError { error: e.error, origin: ErrorOrigin::Callee })?;
+			let output = executable.execute(self, entry_point, input_data).map_err(|e| {
+				if_tracing(|tracer| {
+					tracer.exit_child_span_with_error(
+						e.error,
+						top_frame_mut!(self).nested_gas.gas_consumed(),
+					);
+				});
+				ExecError { error: e.error, origin: ErrorOrigin::Callee }
+			})?;
 
-			call_span.after_call(&output);
+			if_tracing(|tracer| {
+				tracer.exit_child_span(&output, top_frame_mut!(self).nested_gas.gas_consumed());
+			});
 
 			// Avoid useless work that would be reverted anyways.
 			if output.did_revert() {
@@ -1128,34 +1137,11 @@ where
 				.enforce_limit(contract)
 				.map_err(|e| ExecError { error: e, origin: ErrorOrigin::Callee })?;
 
-			let account_id = T::AddressMapper::to_address(&frame.account_id);
-			match (entry_point, delegated_code_hash) {
-				(ExportedFunction::Constructor, _) => {
-					// It is not allowed to terminate a contract inside its constructor.
-					if matches!(frame.contract_info, CachedContract::Terminated) {
-						return Err(Error::<T>::TerminatedInConstructor.into());
-					}
-
-					let caller = T::AddressMapper::to_address(self.caller().account_id()?);
-					// Deposit an instantiation event.
-					Contracts::<T>::deposit_event(Event::Instantiated {
-						deployer: caller,
-						contract: account_id,
-					});
-				},
-				(ExportedFunction::Call, Some(code_hash)) => {
-					Contracts::<T>::deposit_event(Event::DelegateCalled {
-						contract: account_id,
-						code_hash,
-					});
-				},
-				(ExportedFunction::Call, None) => {
-					let caller = self.caller();
-					Contracts::<T>::deposit_event(Event::Called {
-						caller: caller.clone(),
-						contract: account_id,
-					});
-				},
+			// It is not allowed to terminate a contract inside its constructor.
+			if entry_point == ExportedFunction::Constructor &&
+				matches!(frame.contract_info, CachedContract::Terminated)
+			{
+				return Err(Error::<T>::TerminatedInConstructor.into());
 			}
 
 			Ok(output)
@@ -1250,13 +1236,6 @@ where
 				}
 			}
 		} else {
-			if let Some((msg, false)) = self.debug_message.as_ref().map(|m| (m, m.is_empty())) {
-				log::debug!(
-					target: LOG_TARGET,
-					"Execution finished with debug buffer: {}",
-					core::str::from_utf8(msg).unwrap_or("<Invalid UTF8>"),
-				);
-			}
 			self.gas_meter.absorb_nested(mem::take(&mut self.first_frame.nested_gas));
 			if !persist {
 				return;
@@ -1416,7 +1395,7 @@ where
 		&mut self,
 		gas_limit: Weight,
 		deposit_limit: U256,
-		dest: &H160,
+		dest_addr: &H160,
 		value: U256,
 		input_data: Vec<u8>,
 		allows_reentry: bool,
@@ -1432,7 +1411,7 @@ where
 		*self.last_frame_output_mut() = Default::default();
 
 		let try_call = || {
-			let dest = T::AddressMapper::to_account_id(dest);
+			let dest = T::AddressMapper::to_account_id(dest_addr);
 			if !self.allows_reentry(&dest) {
 				return Err(<Error<T>>::ReentranceDenied.into());
 			}
@@ -1569,10 +1548,6 @@ where
 				.charge_deposit(frame.account_id.clone(), StorageDeposit::Refund(*deposit));
 		}
 
-		Contracts::<T>::deposit_event(Event::Terminated {
-			contract: account_address,
-			beneficiary: *beneficiary,
-		});
 		Ok(())
 	}
 
@@ -1642,6 +1617,10 @@ where
 		ContractInfoOf::<T>::contains_key(&address)
 	}
 
+	fn to_account_id(&self, address: &H160) -> T::AccountId {
+		T::AddressMapper::to_account_id(address)
+	}
+
 	fn code_hash(&self, address: &H160) -> H256 {
 		<ContractInfoOf<T>>::get(&address)
 			.map(|contract| contract.code_hash)
@@ -1724,11 +1703,11 @@ where
 	}
 
 	fn deposit_event(&mut self, topics: Vec<H256>, data: Vec<u8>) {
-		Contracts::<Self::T>::deposit_event(Event::ContractEmitted {
-			contract: T::AddressMapper::to_address(self.account_id()),
-			data,
-			topics,
+		let contract = T::AddressMapper::to_address(self.account_id());
+		if_tracing(|tracer| {
+			tracer.log_event(contract, &topics, &data);
 		});
+		Contracts::<Self::T>::deposit_event(Event::ContractEmitted { contract, data, topics });
 	}
 
 	fn block_number(&self) -> U256 {
@@ -1759,28 +1738,6 @@ where
 		self.top_frame_mut().nested_storage.charge(diff)
 	}
 
-	fn debug_buffer_enabled(&self) -> bool {
-		self.debug_message.is_some()
-	}
-
-	fn append_debug_buffer(&mut self, msg: &str) -> bool {
-		if let Some(buffer) = &mut self.debug_message {
-			buffer
-				.try_extend(&mut msg.bytes())
-				.map_err(|_| {
-					log::debug!(
-						target: LOG_TARGET,
-						"Debug buffer (of {} bytes) exhausted!",
-						limits::DEBUG_BUFFER_BYTES,
-					)
-				})
-				.ok();
-			true
-		} else {
-			false
-		}
-	}
-
 	fn call_runtime(&self, call: <Self::T as Config>::RuntimeCall) -> DispatchResultWithPostInfo {
 		let mut origin: T::RuntimeOrigin = RawOrigin::Signed(self.account_id().clone()).into();
 		origin.add_filter(T::CallFilter::contains);
@@ -1847,11 +1804,6 @@ where
 
 		Self::increment_refcount(hash)?;
 		Self::decrement_refcount(prev_hash);
-		Contracts::<Self::T>::deposit_event(Event::ContractCodeUpdated {
-			contract: T::AddressMapper::to_address(&frame.account_id),
-			new_code_hash: hash,
-			old_code_hash: prev_hash,
-		});
 		Ok(())
 	}
 
@@ -2103,7 +2055,6 @@ mod tests {
 					value.into(),
 					vec![],
 					false,
-					None,
 				),
 				Ok(_)
 			);
@@ -2196,7 +2147,6 @@ mod tests {
 				value.into(),
 				vec![],
 				false,
-				None,
 			)
 			.unwrap();
 
@@ -2237,7 +2187,6 @@ mod tests {
 				value.into(),
 				vec![],
 				false,
-				None,
 			));
 
 			assert_eq!(get_balance(&ALICE), 100 - value);
@@ -2274,7 +2223,6 @@ mod tests {
 					U256::zero(),
 					vec![],
 					false,
-					None,
 				),
 				ExecError {
 					error: Error::<Test>::CodeNotFound.into(),
@@ -2292,7 +2240,6 @@ mod tests {
 				U256::zero(),
 				vec![],
 				false,
-				None,
 			));
 		});
 	}
@@ -2321,7 +2268,6 @@ mod tests {
 				55u64.into(),
 				vec![],
 				false,
-				None,
 			)
 			.unwrap();
 
@@ -2371,7 +2317,6 @@ mod tests {
 				U256::zero(),
 				vec![],
 				false,
-				None,
 			);
 
 			let output = result.unwrap();
@@ -2401,7 +2346,6 @@ mod tests {
 				U256::zero(),
 				vec![],
 				false,
-				None,
 			);
 
 			let output = result.unwrap();
@@ -2431,7 +2375,6 @@ mod tests {
 				U256::zero(),
 				vec![1, 2, 3, 4],
 				false,
-				None,
 			);
 			assert_matches!(result, Ok(_));
 		});
@@ -2468,7 +2411,6 @@ mod tests {
 					vec![1, 2, 3, 4],
 					Some(&[0; 32]),
 					false,
-					None,
 				);
 				assert_matches!(result, Ok(_));
 			});
@@ -2523,7 +2465,6 @@ mod tests {
 				value.into(),
 				vec![],
 				false,
-				None,
 			);
 
 			assert_matches!(result, Ok(_));
@@ -2588,7 +2529,6 @@ mod tests {
 				U256::zero(),
 				vec![],
 				false,
-				None,
 			);
 
 			assert_matches!(result, Ok(_));
@@ -2654,7 +2594,6 @@ mod tests {
 				U256::zero(),
 				vec![],
 				false,
-				None,
 			);
 
 			assert_matches!(result, Ok(_));
@@ -2687,7 +2626,40 @@ mod tests {
 				U256::zero(),
 				vec![],
 				false,
-				None,
+			);
+			assert_matches!(result, Ok(_));
+		});
+	}
+
+	#[test]
+	fn to_account_id_returns_proper_values() {
+		let bob_code_hash = MockLoader::insert(Call, |ctx, _| {
+			let alice_account_id = <Test as Config>::AddressMapper::to_account_id(&ALICE_ADDR);
+			assert_eq!(ctx.ext.to_account_id(&ALICE_ADDR), alice_account_id);
+
+			const UNMAPPED_ADDR: H160 = H160([99u8; 20]);
+			let mut unmapped_fallback_account_id = [0xEE; 32];
+			unmapped_fallback_account_id[..20].copy_from_slice(UNMAPPED_ADDR.as_bytes());
+			assert_eq!(
+				ctx.ext.to_account_id(&UNMAPPED_ADDR),
+				AccountId32::new(unmapped_fallback_account_id)
+			);
+
+			exec_success()
+		});
+
+		ExtBuilder::default().build().execute_with(|| {
+			place_contract(&BOB, bob_code_hash);
+			let origin = Origin::from_account_id(ALICE);
+			let mut storage_meter = storage::meter::Meter::new(&origin, 0, 0).unwrap();
+			let result = MockStack::run_call(
+				origin,
+				BOB_ADDR,
+				&mut GasMeter::<Test>::new(GAS_LIMIT),
+				&mut storage_meter,
+				U256::zero(),
+				vec![0],
+				false,
 			);
 			assert_matches!(result, Ok(_));
 		});
@@ -2725,7 +2697,6 @@ mod tests {
 				U256::zero(),
 				vec![0],
 				false,
-				None,
 			);
 			assert_matches!(result, Ok(_));
 		});
@@ -2752,7 +2723,6 @@ mod tests {
 				U256::zero(),
 				vec![0],
 				false,
-				None,
 			);
 			assert_matches!(result, Ok(_));
 		});
@@ -2797,7 +2767,6 @@ mod tests {
 				U256::zero(),
 				vec![0],
 				false,
-				None,
 			);
 			assert_matches!(result, Ok(_));
 		});
@@ -2824,7 +2793,6 @@ mod tests {
 				U256::zero(),
 				vec![0],
 				false,
-				None,
 			);
 			assert_matches!(result, Ok(_));
 		});
@@ -2851,7 +2819,6 @@ mod tests {
 				1u64.into(),
 				vec![0],
 				false,
-				None,
 			);
 			assert_matches!(result, Err(_));
 		});
@@ -2896,7 +2863,6 @@ mod tests {
 				U256::zero(),
 				vec![0],
 				false,
-				None,
 			);
 			assert_matches!(result, Ok(_));
 		});
@@ -2942,7 +2908,6 @@ mod tests {
 				U256::zero(),
 				vec![],
 				false,
-				None,
 			);
 
 			assert_matches!(result, Ok(_));
@@ -2969,7 +2934,6 @@ mod tests {
 					vec![],
 					Some(&[0; 32]),
 					false,
-					None,
 				),
 				Err(_)
 			);
@@ -3005,7 +2969,6 @@ mod tests {
 						vec![],
 						Some(&[0 ;32]),
 						false,
-						None,
 					),
 					Ok((address, ref output)) if output.data == vec![80, 65, 83, 83] => address
 				);
@@ -3021,13 +2984,6 @@ mod tests {
 					ContractInfo::<Test>::load_code_hash(&instantiated_contract_id).unwrap(),
 					dummy_ch
 				);
-				assert_eq!(
-					&events(),
-					&[Event::Instantiated {
-						deployer: ALICE_ADDR,
-						contract: instantiated_contract_address
-					}]
-				);
 			});
 	}
 
@@ -3060,7 +3016,6 @@ mod tests {
 						vec![],
 						Some(&[0; 32]),
 						false,
-						None,
 					),
 					Ok((address, ref output)) if output.data == vec![70, 65, 73, 76] => address
 				);
@@ -3125,7 +3080,6 @@ mod tests {
 						(min_balance * 10).into(),
 						vec![],
 						false,
-						None,
 					),
 					Ok(_)
 				);
@@ -3145,19 +3099,6 @@ mod tests {
 					ContractInfo::<Test>::load_code_hash(&instantiated_contract_id).unwrap(),
 					dummy_ch
 				);
-				assert_eq!(
-					&events(),
-					&[
-						Event::Instantiated {
-							deployer: BOB_ADDR,
-							contract: instantiated_contract_address
-						},
-						Event::Called {
-							caller: Origin::from_account_id(ALICE),
-							contract: BOB_ADDR
-						},
-					]
-				);
 			});
 	}
 
@@ -3206,17 +3147,9 @@ mod tests {
 						U256::zero(),
 						vec![],
 						false,
-						None,
 					),
 					Ok(_)
 				);
-
-				// The contract wasn't instantiated so we don't expect to see an instantiation
-				// event here.
-				assert_eq!(
-					&events(),
-					&[Event::Called { caller: Origin::from_account_id(ALICE), contract: BOB_ADDR },]
-				);
 			});
 	}
 
@@ -3250,7 +3183,6 @@ mod tests {
 						vec![],
 						Some(&[0; 32]),
 						false,
-						None,
 					),
 					Err(Error::<Test>::TerminatedInConstructor.into())
 				);
@@ -3315,7 +3247,6 @@ mod tests {
 				U256::zero(),
 				vec![0],
 				false,
-				None,
 			);
 			assert_matches!(result, Ok(_));
 		});
@@ -3378,7 +3309,6 @@ mod tests {
 					vec![],
 					Some(&[0; 32]),
 					false,
-					None,
 				);
 				assert_matches!(result, Ok(_));
 			});
@@ -3425,113 +3355,11 @@ mod tests {
 					U256::zero(),
 					vec![],
 					false,
-					None,
 				)
 				.unwrap();
 			});
 	}
 
-	#[test]
-	fn printing_works() {
-		let code_hash = MockLoader::insert(Call, |ctx, _| {
-			ctx.ext.append_debug_buffer("This is a test");
-			ctx.ext.append_debug_buffer("More text");
-			exec_success()
-		});
-
-		let mut debug_buffer = DebugBuffer::try_from(Vec::new()).unwrap();
-
-		ExtBuilder::default().build().execute_with(|| {
-			let min_balance = <Test as Config>::Currency::minimum_balance();
-
-			let mut gas_meter = GasMeter::<Test>::new(GAS_LIMIT);
-			set_balance(&ALICE, min_balance * 10);
-			place_contract(&BOB, code_hash);
-			let origin = Origin::from_account_id(ALICE);
-			let mut storage_meter = storage::meter::Meter::new(&origin, 0, 0).unwrap();
-			MockStack::run_call(
-				origin,
-				BOB_ADDR,
-				&mut gas_meter,
-				&mut storage_meter,
-				U256::zero(),
-				vec![],
-				false,
-				Some(&mut debug_buffer),
-			)
-			.unwrap();
-		});
-
-		assert_eq!(&String::from_utf8(debug_buffer.to_vec()).unwrap(), "This is a testMore text");
-	}
-
-	#[test]
-	fn printing_works_on_fail() {
-		let code_hash = MockLoader::insert(Call, |ctx, _| {
-			ctx.ext.append_debug_buffer("This is a test");
-			ctx.ext.append_debug_buffer("More text");
-			exec_trapped()
-		});
-
-		let mut debug_buffer = DebugBuffer::try_from(Vec::new()).unwrap();
-
-		ExtBuilder::default().build().execute_with(|| {
-			let min_balance = <Test as Config>::Currency::minimum_balance();
-
-			let mut gas_meter = GasMeter::<Test>::new(GAS_LIMIT);
-			set_balance(&ALICE, min_balance * 10);
-			place_contract(&BOB, code_hash);
-			let origin = Origin::from_account_id(ALICE);
-			let mut storage_meter = storage::meter::Meter::new(&origin, 0, 0).unwrap();
-			let result = MockStack::run_call(
-				origin,
-				BOB_ADDR,
-				&mut gas_meter,
-				&mut storage_meter,
-				U256::zero(),
-				vec![],
-				false,
-				Some(&mut debug_buffer),
-			);
-			assert!(result.is_err());
-		});
-
-		assert_eq!(&String::from_utf8(debug_buffer.to_vec()).unwrap(), "This is a testMore text");
-	}
-
-	#[test]
-	fn debug_buffer_is_limited() {
-		let code_hash = MockLoader::insert(Call, move |ctx, _| {
-			ctx.ext.append_debug_buffer("overflowing bytes");
-			exec_success()
-		});
-
-		// Pre-fill the buffer almost up to its limit, leaving not enough space to the message
-		let debug_buf_before = DebugBuffer::try_from(vec![0u8; DebugBuffer::bound() - 5]).unwrap();
-		let mut debug_buf_after = debug_buf_before.clone();
-
-		ExtBuilder::default().build().execute_with(|| {
-			let min_balance = <Test as Config>::Currency::minimum_balance();
-			let mut gas_meter = GasMeter::<Test>::new(GAS_LIMIT);
-			set_balance(&ALICE, min_balance * 10);
-			place_contract(&BOB, code_hash);
-			let origin = Origin::from_account_id(ALICE);
-			let mut storage_meter = storage::meter::Meter::new(&origin, 0, 0).unwrap();
-			MockStack::run_call(
-				origin,
-				BOB_ADDR,
-				&mut gas_meter,
-				&mut storage_meter,
-				U256::zero(),
-				vec![],
-				false,
-				Some(&mut debug_buf_after),
-			)
-			.unwrap();
-			assert_eq!(debug_buf_before, debug_buf_after);
-		});
-	}
-
 	#[test]
 	fn call_reentry_direct_recursion() {
 		// call the contract passed as input with disabled reentry
@@ -3559,7 +3387,6 @@ mod tests {
 				U256::zero(),
 				CHARLIE_ADDR.as_bytes().to_vec(),
 				false,
-				None,
 			));
 
 			// Calling into oneself fails
@@ -3572,7 +3399,6 @@ mod tests {
 					U256::zero(),
 					BOB_ADDR.as_bytes().to_vec(),
 					false,
-					None,
 				)
 				.map_err(|e| e.error),
 				<Error<Test>>::ReentranceDenied,
@@ -3623,7 +3449,6 @@ mod tests {
 					U256::zero(),
 					vec![0],
 					false,
-					None,
 				)
 				.map_err(|e| e.error),
 				<Error<Test>>::ReentranceDenied,
@@ -3658,31 +3483,20 @@ mod tests {
 				U256::zero(),
 				vec![],
 				false,
-				None,
 			)
 			.unwrap();
 
 			let remark_hash = <Test as frame_system::Config>::Hashing::hash(b"Hello World");
 			assert_eq!(
 				System::events(),
-				vec![
-					EventRecord {
-						phase: Phase::Initialization,
-						event: MetaEvent::System(frame_system::Event::Remarked {
-							sender: BOB_FALLBACK,
-							hash: remark_hash
-						}),
-						topics: vec![],
-					},
-					EventRecord {
-						phase: Phase::Initialization,
-						event: MetaEvent::Contracts(crate::Event::Called {
-							caller: Origin::from_account_id(ALICE),
-							contract: BOB_ADDR,
-						}),
-						topics: vec![],
-					},
-				]
+				vec![EventRecord {
+					phase: Phase::Initialization,
+					event: MetaEvent::System(frame_system::Event::Remarked {
+						sender: BOB_FALLBACK,
+						hash: remark_hash
+					}),
+					topics: vec![],
+				},]
 			);
 		});
 	}
@@ -3743,7 +3557,6 @@ mod tests {
 				U256::zero(),
 				vec![],
 				false,
-				None,
 			)
 			.unwrap();
 
@@ -3772,14 +3585,6 @@ mod tests {
 						},),
 						topics: vec![],
 					},
-					EventRecord {
-						phase: Phase::Initialization,
-						event: MetaEvent::Contracts(crate::Event::Called {
-							caller: Origin::from_account_id(ALICE),
-							contract: BOB_ADDR,
-						}),
-						topics: vec![],
-					},
 				]
 			);
 		});
@@ -3870,7 +3675,6 @@ mod tests {
 					vec![],
 					Some(&[0; 32]),
 					false,
-					None,
 				)
 				.ok();
 				assert_eq!(System::account_nonce(&ALICE), 0);
@@ -3884,7 +3688,6 @@ mod tests {
 					vec![],
 					Some(&[0; 32]),
 					false,
-					None,
 				));
 				assert_eq!(System::account_nonce(&ALICE), 1);
 
@@ -3897,7 +3700,6 @@ mod tests {
 					vec![],
 					Some(&[0; 32]),
 					false,
-					None,
 				));
 				assert_eq!(System::account_nonce(&ALICE), 2);
 
@@ -3910,7 +3712,6 @@ mod tests {
 					vec![],
 					Some(&[0; 32]),
 					false,
-					None,
 				));
 				assert_eq!(System::account_nonce(&ALICE), 3);
 			});
@@ -3979,7 +3780,6 @@ mod tests {
 				U256::zero(),
 				vec![],
 				false,
-				None,
 			));
 		});
 	}
@@ -4091,7 +3891,6 @@ mod tests {
 				U256::zero(),
 				vec![],
 				false,
-				None,
 			));
 		});
 	}
@@ -4131,7 +3930,6 @@ mod tests {
 				U256::zero(),
 				vec![],
 				false,
-				None,
 			));
 		});
 	}
@@ -4171,7 +3969,6 @@ mod tests {
 				U256::zero(),
 				vec![],
 				false,
-				None,
 			));
 		});
 	}
@@ -4225,7 +4022,6 @@ mod tests {
 				U256::zero(),
 				vec![],
 				false,
-				None,
 			));
 		});
 	}
@@ -4282,7 +4078,6 @@ mod tests {
 				U256::zero(),
 				vec![],
 				false,
-				None,
 			));
 		});
 	}
@@ -4358,7 +4153,6 @@ mod tests {
 				U256::zero(),
 				vec![],
 				false,
-				None,
 			));
 		});
 	}
@@ -4429,7 +4223,6 @@ mod tests {
 				U256::zero(),
 				vec![0],
 				false,
-				None,
 			);
 			assert_matches!(result, Ok(_));
 		});
@@ -4468,7 +4261,6 @@ mod tests {
 				U256::zero(),
 				vec![],
 				false,
-				None,
 			));
 		});
 	}
@@ -4531,7 +4323,6 @@ mod tests {
 				U256::zero(),
 				vec![0],
 				false,
-				None,
 			);
 			assert_matches!(result, Ok(_));
 		});
@@ -4565,7 +4356,6 @@ mod tests {
 				U256::zero(),
 				vec![],
 				false,
-				None,
 			);
 			assert_matches!(result, Ok(_));
 		});
@@ -4641,7 +4431,6 @@ mod tests {
 					U256::zero(),
 					vec![],
 					false,
-					None,
 				)
 				.unwrap()
 			});
@@ -4710,7 +4499,6 @@ mod tests {
 				U256::zero(),
 				vec![0],
 				false,
-				None,
 			);
 			assert_matches!(result, Ok(_));
 		});
@@ -4782,7 +4570,6 @@ mod tests {
 				U256::zero(),
 				vec![],
 				false,
-				None,
 			);
 			assert_matches!(result, Ok(_));
 		});
@@ -4834,7 +4621,6 @@ mod tests {
 					U256::zero(),
 					vec![],
 					false,
-					None,
 				)
 				.unwrap()
 			});
@@ -4904,7 +4690,6 @@ mod tests {
 					U256::zero(),
 					vec![],
 					false,
-					None,
 				)
 				.unwrap()
 			});
@@ -4951,7 +4736,6 @@ mod tests {
 					U256::zero(),
 					vec![],
 					false,
-					None,
 				)
 				.unwrap()
 			});
@@ -4996,7 +4780,6 @@ mod tests {
 					U256::zero(),
 					vec![],
 					false,
-					None,
 				)
 				.unwrap()
 			});
@@ -5052,7 +4835,6 @@ mod tests {
 					U256::zero(),
 					vec![0],
 					false,
-					None,
 				),
 				Ok(_)
 			);
diff --git a/substrate/frame/revive/src/lib.rs b/substrate/frame/revive/src/lib.rs
index bdb4b92edd9e62c5d13cfb154ce42887b5b103e5..c36cb3f47caed988ddefbd20a1f1caef680f2c75 100644
--- a/substrate/frame/revive/src/lib.rs
+++ b/substrate/frame/revive/src/lib.rs
@@ -35,9 +35,9 @@ mod wasm;
 mod tests;
 
 pub mod chain_extension;
-pub mod debug;
 pub mod evm;
 pub mod test_utils;
+pub mod tracing;
 pub mod weights;
 
 use crate::{
@@ -71,7 +71,7 @@ use frame_support::{
 use frame_system::{
 	ensure_signed,
 	pallet_prelude::{BlockNumberFor, OriginFor},
-	EventRecord, Pallet as System,
+	Pallet as System,
 };
 use pallet_transaction_payment::OnChargeTransaction;
 use scale_info::TypeInfo;
@@ -83,7 +83,6 @@ use sp_runtime::{
 
 pub use crate::{
 	address::{create1, create2, AccountId32Mapper, AddressMapper},
-	debug::Tracing,
 	exec::{MomentOf, Origin},
 	pallet::*,
 };
@@ -98,9 +97,6 @@ type BalanceOf<T> =
 	<<T as Config>::Currency as Inspect<<T as frame_system::Config>::AccountId>>::Balance;
 type OnChargeTransactionBalanceOf<T> = <<T as pallet_transaction_payment::Config>::OnChargeTransaction as OnChargeTransaction<T>>::Balance;
 type CodeVec = BoundedVec<u8, ConstU32<{ limits::code::BLOB_BYTES }>>;
-type EventRecordOf<T> =
-	EventRecord<<T as frame_system::Config>::RuntimeEvent, <T as frame_system::Config>::Hash>;
-type DebugBuffer = BoundedVec<u8, ConstU32<{ limits::DEBUG_BUFFER_BYTES }>>;
 type ImmutableData = BoundedVec<u8, ConstU32<{ limits::IMMUTABLE_BYTES }>>;
 
 /// Used as a sentinel value when reading and writing contract memory.
@@ -121,7 +117,6 @@ const LOG_TARGET: &str = "runtime::revive";
 #[frame_support::pallet]
 pub mod pallet {
 	use super::*;
-	use crate::debug::Debugger;
 	use frame_support::pallet_prelude::*;
 	use frame_system::pallet_prelude::*;
 	use sp_core::U256;
@@ -258,12 +253,6 @@ pub mod pallet {
 		#[pallet::no_default_bounds]
 		type InstantiateOrigin: EnsureOrigin<Self::RuntimeOrigin, Success = Self::AccountId>;
 
-		/// For most production chains, it's recommended to use the `()` implementation of this
-		/// trait. This implementation offers additional logging when the log target
-		/// "runtime::revive" is set to trace.
-		#[pallet::no_default_bounds]
-		type Debug: Debugger<Self>;
-
 		/// A type that exposes XCM APIs, allowing contracts to interact with other parachains, and
 		/// execute XCM programs.
 		#[pallet::no_default_bounds]
@@ -370,7 +359,6 @@ pub mod pallet {
 			type InstantiateOrigin = EnsureSigned<AccountId>;
 			type WeightInfo = ();
 			type WeightPrice = Self;
-			type Debug = ();
 			type Xcm = ();
 			type RuntimeMemory = ConstU32<{ 128 * 1024 * 1024 }>;
 			type PVFMemory = ConstU32<{ 512 * 1024 * 1024 }>;
@@ -382,25 +370,6 @@ pub mod pallet {
 
 	#[pallet::event]
 	pub enum Event<T: Config> {
-		/// Contract deployed by address at the specified address.
-		Instantiated { deployer: H160, contract: H160 },
-
-		/// Contract has been removed.
-		///
-		/// # Note
-		///
-		/// The only way for a contract to be removed and emitting this event is by calling
-		/// `seal_terminate`.
-		Terminated {
-			/// The contract that was terminated.
-			contract: H160,
-			/// The account that received the contracts remaining balance
-			beneficiary: H160,
-		},
-
-		/// Code with the specified hash has been stored.
-		CodeStored { code_hash: H256, deposit_held: BalanceOf<T>, uploader: H160 },
-
 		/// A custom event emitted by the contract.
 		ContractEmitted {
 			/// The contract that emitted the event.
@@ -412,54 +381,6 @@ pub mod pallet {
 			/// Number of topics is capped by [`limits::NUM_EVENT_TOPICS`].
 			topics: Vec<H256>,
 		},
-
-		/// A code with the specified hash was removed.
-		CodeRemoved { code_hash: H256, deposit_released: BalanceOf<T>, remover: H160 },
-
-		/// A contract's code was updated.
-		ContractCodeUpdated {
-			/// The contract that has been updated.
-			contract: H160,
-			/// New code hash that was set for the contract.
-			new_code_hash: H256,
-			/// Previous code hash of the contract.
-			old_code_hash: H256,
-		},
-
-		/// A contract was called either by a plain account or another contract.
-		///
-		/// # Note
-		///
-		/// Please keep in mind that like all events this is only emitted for successful
-		/// calls. This is because on failure all storage changes including events are
-		/// rolled back.
-		Called {
-			/// The caller of the `contract`.
-			caller: Origin<T>,
-			/// The contract that was called.
-			contract: H160,
-		},
-
-		/// A contract delegate called a code hash.
-		///
-		/// # Note
-		///
-		/// Please keep in mind that like all events this is only emitted for successful
-		/// calls. This is because on failure all storage changes including events are
-		/// rolled back.
-		DelegateCalled {
-			/// The contract that performed the delegate call and hence in whose context
-			/// the `code_hash` is executed.
-			contract: H160,
-			/// The code hash that was delegate called.
-			code_hash: H256,
-		},
-
-		/// Some funds have been transferred and held as storage deposit.
-		StorageDepositTransferredAndHeld { from: H160, to: H160, amount: BalanceOf<T> },
-
-		/// Some storage deposit funds have been transferred and released.
-		StorageDepositTransferredAndReleased { from: H160, to: H160, amount: BalanceOf<T> },
 	}
 
 	#[pallet::error]
@@ -810,9 +731,8 @@ pub mod pallet {
 				gas_limit,
 				DepositLimit::Balance(storage_deposit_limit),
 				data,
-				DebugInfo::Skip,
-				CollectEvents::Skip,
 			);
+
 			if let Ok(return_value) = &output.result {
 				if return_value.did_revert() {
 					output.result = Err(<Error<T>>::ContractReverted.into());
@@ -848,8 +768,6 @@ pub mod pallet {
 				Code::Existing(code_hash),
 				data,
 				salt,
-				DebugInfo::Skip,
-				CollectEvents::Skip,
 			);
 			if let Ok(retval) = &output.result {
 				if retval.result.did_revert() {
@@ -914,8 +832,6 @@ pub mod pallet {
 				Code::Upload(code),
 				data,
 				salt,
-				DebugInfo::Skip,
-				CollectEvents::Skip,
 			);
 			if let Ok(retval) = &output.result {
 				if retval.result.did_revert() {
@@ -993,11 +909,6 @@ pub mod pallet {
 				};
 				<ExecStack<T, WasmBlob<T>>>::increment_refcount(code_hash)?;
 				<ExecStack<T, WasmBlob<T>>>::decrement_refcount(contract.code_hash);
-				Self::deposit_event(Event::ContractCodeUpdated {
-					contract: dest,
-					new_code_hash: code_hash,
-					old_code_hash: contract.code_hash,
-				});
 				contract.code_hash = code_hash;
 				Ok(())
 			})
@@ -1085,16 +996,10 @@ where
 		gas_limit: Weight,
 		storage_deposit_limit: DepositLimit<BalanceOf<T>>,
 		data: Vec<u8>,
-		debug: DebugInfo,
-		collect_events: CollectEvents,
-	) -> ContractResult<ExecReturnValue, BalanceOf<T>, EventRecordOf<T>> {
+	) -> ContractResult<ExecReturnValue, BalanceOf<T>> {
 		let mut gas_meter = GasMeter::new(gas_limit);
 		let mut storage_deposit = Default::default();
-		let mut debug_message = if matches!(debug, DebugInfo::UnsafeDebug) {
-			Some(DebugBuffer::default())
-		} else {
-			None
-		};
+
 		let try_call = || {
 			let origin = Origin::from_runtime_origin(origin)?;
 			let mut storage_meter = match storage_deposit_limit {
@@ -1109,7 +1014,6 @@ where
 				Self::convert_native_to_evm(value),
 				data,
 				storage_deposit_limit.is_unchecked(),
-				debug_message.as_mut(),
 			)?;
 			storage_deposit = storage_meter
 				.try_into_deposit(&origin, storage_deposit_limit.is_unchecked())
@@ -1119,18 +1023,11 @@ where
 			Ok(result)
 		};
 		let result = Self::run_guarded(try_call);
-		let events = if matches!(collect_events, CollectEvents::UnsafeCollect) {
-			Some(System::<T>::read_events_no_consensus().map(|e| *e).collect())
-		} else {
-			None
-		};
 		ContractResult {
 			result: result.map_err(|r| r.error),
 			gas_consumed: gas_meter.gas_consumed(),
 			gas_required: gas_meter.gas_required(),
 			storage_deposit,
-			debug_message: debug_message.unwrap_or_default().to_vec(),
-			events,
 		}
 	}
 
@@ -1138,8 +1035,7 @@ where
 	///
 	/// Identical to [`Self::instantiate`] or [`Self::instantiate_with_code`] but tailored towards
 	/// being called by other code within the runtime as opposed to from an extrinsic. It returns
-	/// more information and allows the enablement of features that are not suitable for an
-	/// extrinsic (debugging, event collection).
+	/// more information to the caller useful to estimate the cost of the operation.
 	pub fn bare_instantiate(
 		origin: OriginFor<T>,
 		value: BalanceOf<T>,
@@ -1148,14 +1044,9 @@ where
 		code: Code,
 		data: Vec<u8>,
 		salt: Option<[u8; 32]>,
-		debug: DebugInfo,
-		collect_events: CollectEvents,
-	) -> ContractResult<InstantiateReturnValue, BalanceOf<T>, EventRecordOf<T>> {
+	) -> ContractResult<InstantiateReturnValue, BalanceOf<T>> {
 		let mut gas_meter = GasMeter::new(gas_limit);
 		let mut storage_deposit = Default::default();
-		let mut debug_message =
-			if debug == DebugInfo::UnsafeDebug { Some(DebugBuffer::default()) } else { None };
-
 		let unchecked_deposit_limit = storage_deposit_limit.is_unchecked();
 		let mut storage_deposit_limit = match storage_deposit_limit {
 			DepositLimit::Balance(limit) => limit,
@@ -1195,7 +1086,6 @@ where
 				data,
 				salt.as_ref(),
 				unchecked_deposit_limit,
-				debug_message.as_mut(),
 			);
 			storage_deposit = storage_meter
 				.try_into_deposit(&instantiate_origin, unchecked_deposit_limit)?
@@ -1203,11 +1093,6 @@ where
 			result
 		};
 		let output = Self::run_guarded(try_instantiate);
-		let events = if matches!(collect_events, CollectEvents::UnsafeCollect) {
-			Some(System::<T>::read_events_no_consensus().map(|e| *e).collect())
-		} else {
-			None
-		};
 		ContractResult {
 			result: output
 				.map(|(addr, result)| InstantiateReturnValue { result, addr })
@@ -1215,8 +1100,6 @@ where
 			gas_consumed: gas_meter.gas_consumed(),
 			gas_required: gas_meter.gas_required(),
 			storage_deposit,
-			debug_message: debug_message.unwrap_or_default().to_vec(),
-			events,
 		}
 	}
 
@@ -1254,7 +1137,6 @@ where
 			DepositLimit::Unchecked
 		};
 
-		// TODO remove once we have revisited how we encode the gas limit.
 		if tx.nonce.is_none() {
 			tx.nonce = Some(<System<T>>::account_nonce(&origin).into());
 		}
@@ -1273,8 +1155,6 @@ where
 		};
 
 		let input = tx.input.clone().unwrap_or_default().0;
-		let debug = DebugInfo::Skip;
-		let collect_events = CollectEvents::Skip;
 
 		let extract_error = |err| {
 			if err == Error::<T>::TransferFailed.into() ||
@@ -1305,8 +1185,6 @@ where
 					gas_limit,
 					storage_deposit_limit,
 					input.clone(),
-					debug,
-					collect_events,
 				);
 
 				let data = match result.result {
@@ -1363,8 +1241,6 @@ where
 					Code::Upload(code.to_vec()),
 					data.to_vec(),
 					None,
-					debug,
-					collect_events,
 				);
 
 				let returned_data = match result.result {
@@ -1535,12 +1411,11 @@ environmental!(executing_contract: bool);
 sp_api::decl_runtime_apis! {
 	/// The API used to dry-run contract interactions.
 	#[api_version(1)]
-	pub trait ReviveApi<AccountId, Balance, Nonce, BlockNumber, EventRecord> where
+	pub trait ReviveApi<AccountId, Balance, Nonce, BlockNumber> where
 		AccountId: Codec,
 		Balance: Codec,
 		Nonce: Codec,
 		BlockNumber: Codec,
-		EventRecord: Codec,
 	{
 		/// Returns the free balance of the given `[H160]` address, using EVM decimals.
 		fn balance(address: H160) -> U256;
@@ -1558,7 +1433,7 @@ sp_api::decl_runtime_apis! {
 			gas_limit: Option<Weight>,
 			storage_deposit_limit: Option<Balance>,
 			input_data: Vec<u8>,
-		) -> ContractResult<ExecReturnValue, Balance, EventRecord>;
+		) -> ContractResult<ExecReturnValue, Balance>;
 
 		/// Instantiate a new contract.
 		///
@@ -1571,7 +1446,7 @@ sp_api::decl_runtime_apis! {
 			code: Code,
 			data: Vec<u8>,
 			salt: Option<[u8; 32]>,
-		) -> ContractResult<InstantiateReturnValue, Balance, EventRecord>;
+		) -> ContractResult<InstantiateReturnValue, Balance>;
 
 
 		/// Perform an Ethereum call.
diff --git a/substrate/frame/revive/src/limits.rs b/substrate/frame/revive/src/limits.rs
index 3b55106c67d8ca45ebe6dcf6ce6b33c7e4d0fc68..f101abf0ea7e9478be157c693b4f251d80af4df7 100644
--- a/substrate/frame/revive/src/limits.rs
+++ b/substrate/frame/revive/src/limits.rs
@@ -57,11 +57,6 @@ pub const TRANSIENT_STORAGE_BYTES: u32 = 4 * 1024;
 /// The maximum allowable length in bytes for (transient) storage keys.
 pub const STORAGE_KEY_BYTES: u32 = 128;
 
-/// The maximum size of the debug buffer contracts can write messages to.
-///
-/// The buffer will always be disabled for on-chain execution.
-pub const DEBUG_BUFFER_BYTES: u32 = 2 * 1024 * 1024;
-
 /// The page size in which PolkaVM should allocate memory chunks.
 pub const PAGE_SIZE: u32 = 4 * 1024;
 
diff --git a/substrate/frame/revive/src/primitives.rs b/substrate/frame/revive/src/primitives.rs
index 452d2c8a3067cf1e7540abfd5bc44b615be69226..9c149c7cc38905bc75d57ce9d3af1cc6bf2e86a4 100644
--- a/substrate/frame/revive/src/primitives.rs
+++ b/substrate/frame/revive/src/primitives.rs
@@ -63,7 +63,7 @@ impl<T> From<T> for DepositLimit<T> {
 /// `ContractsApi` version. Therefore when SCALE decoding a `ContractResult` its trailing data
 /// should be ignored to avoid any potential compatibility issues.
 #[derive(Clone, Eq, PartialEq, Encode, Decode, RuntimeDebug, TypeInfo)]
-pub struct ContractResult<R, Balance, EventRecord> {
+pub struct ContractResult<R, Balance> {
 	/// How much weight was consumed during execution.
 	pub gas_consumed: Weight,
 	/// How much weight is required as gas limit in order to execute this call.
@@ -84,26 +84,8 @@ pub struct ContractResult<R, Balance, EventRecord> {
 	/// is `Err`. This is because on error all storage changes are rolled back including the
 	/// payment of the deposit.
 	pub storage_deposit: StorageDeposit<Balance>,
-	/// An optional debug message. This message is only filled when explicitly requested
-	/// by the code that calls into the contract. Otherwise it is empty.
-	///
-	/// The contained bytes are valid UTF-8. This is not declared as `String` because
-	/// this type is not allowed within the runtime.
-	///
-	/// Clients should not make any assumptions about the format of the buffer.
-	/// They should just display it as-is. It is **not** only a collection of log lines
-	/// provided by a contract but a formatted buffer with different sections.
-	///
-	/// # Note
-	///
-	/// The debug message is never generated during on-chain execution. It is reserved for
-	/// RPC calls.
-	pub debug_message: Vec<u8>,
 	/// The execution result of the wasm code.
 	pub result: Result<R, DispatchError>,
-	/// The events that were emitted during execution. It is an option as event collection is
-	/// optional.
-	pub events: Option<Vec<EventRecord>>,
 }
 
 /// The result of the execution of a `eth_transact` call.
@@ -284,36 +266,3 @@ where
 		}
 	}
 }
-
-/// Determines whether events should be collected during execution.
-#[derive(
-	Copy, Clone, PartialEq, Eq, RuntimeDebug, Decode, Encode, MaxEncodedLen, scale_info::TypeInfo,
-)]
-pub enum CollectEvents {
-	/// Collect events.
-	///
-	/// # Note
-	///
-	/// Events should only be collected when called off-chain, as this would otherwise
-	/// collect all the Events emitted in the block so far and put them into the PoV.
-	///
-	/// **Never** use this mode for on-chain execution.
-	UnsafeCollect,
-	/// Skip event collection.
-	Skip,
-}
-
-/// Determines whether debug messages will be collected.
-#[derive(
-	Copy, Clone, PartialEq, Eq, RuntimeDebug, Decode, Encode, MaxEncodedLen, scale_info::TypeInfo,
-)]
-pub enum DebugInfo {
-	/// Collect debug messages.
-	/// # Note
-	///
-	/// This should only ever be set to `UnsafeDebug` when executing as an RPC because
-	/// it adds allocations and could be abused to drive the runtime into an OOM panic.
-	UnsafeDebug,
-	/// Skip collection of debug messages.
-	Skip,
-}
diff --git a/substrate/frame/revive/src/storage/meter.rs b/substrate/frame/revive/src/storage/meter.rs
index 4febcb0c4066c418b1b7189c12a4cc8403601f72..cd390c86f63ae94a30d065def82e628a0091b653 100644
--- a/substrate/frame/revive/src/storage/meter.rs
+++ b/substrate/frame/revive/src/storage/meter.rs
@@ -18,8 +18,8 @@
 //! This module contains functions to meter the storage deposit.
 
 use crate::{
-	address::AddressMapper, storage::ContractInfo, AccountIdOf, BalanceOf, CodeInfo, Config, Error,
-	Event, HoldReason, Inspect, Origin, Pallet, StorageDeposit as Deposit, System, LOG_TARGET,
+	storage::ContractInfo, AccountIdOf, BalanceOf, CodeInfo, Config, Error, HoldReason, Inspect,
+	Origin, Pallet, StorageDeposit as Deposit, System, LOG_TARGET,
 };
 use alloc::vec::Vec;
 use core::{fmt::Debug, marker::PhantomData};
@@ -516,12 +516,6 @@ impl<T: Config> Ext<T> for ReservingExt {
 					Preservation::Preserve,
 					Fortitude::Polite,
 				)?;
-
-				Pallet::<T>::deposit_event(Event::StorageDepositTransferredAndHeld {
-					from: T::AddressMapper::to_address(origin),
-					to: T::AddressMapper::to_address(contract),
-					amount: *amount,
-				});
 			},
 			Deposit::Refund(amount) => {
 				let transferred = T::Currency::transfer_on_hold(
@@ -534,12 +528,6 @@ impl<T: Config> Ext<T> for ReservingExt {
 					Fortitude::Polite,
 				)?;
 
-				Pallet::<T>::deposit_event(Event::StorageDepositTransferredAndReleased {
-					from: T::AddressMapper::to_address(contract),
-					to: T::AddressMapper::to_address(origin),
-					amount: transferred,
-				});
-
 				if transferred < *amount {
 					// This should never happen, if it does it means that there is a bug in the
 					// runtime logic. In the rare case this happens we try to refund as much as we
diff --git a/substrate/frame/revive/src/test_utils/builder.rs b/substrate/frame/revive/src/test_utils/builder.rs
index 8ba5e7384070289617c667c8e95b0608a2846a6d..7fbb5b676439e6c378e3b51683bac449513faca5 100644
--- a/substrate/frame/revive/src/test_utils/builder.rs
+++ b/substrate/frame/revive/src/test_utils/builder.rs
@@ -17,9 +17,8 @@
 
 use super::{deposit_limit, GAS_LIMIT};
 use crate::{
-	address::AddressMapper, AccountIdOf, BalanceOf, Code, CollectEvents, Config, ContractResult,
-	DebugInfo, DepositLimit, EventRecordOf, ExecReturnValue, InstantiateReturnValue, OriginFor,
-	Pallet, Weight,
+	address::AddressMapper, AccountIdOf, BalanceOf, Code, Config, ContractResult, DepositLimit,
+	ExecReturnValue, InstantiateReturnValue, OriginFor, Pallet, Weight,
 };
 use frame_support::pallet_prelude::DispatchResultWithPostInfo;
 use paste::paste;
@@ -138,9 +137,7 @@ builder!(
 		code: Code,
 		data: Vec<u8>,
 		salt: Option<[u8; 32]>,
-		debug: DebugInfo,
-		collect_events: CollectEvents,
-	) -> ContractResult<InstantiateReturnValue, BalanceOf<T>, EventRecordOf<T>>;
+	) -> ContractResult<InstantiateReturnValue, BalanceOf<T>>;
 
 	/// Build the instantiate call and unwrap the result.
 	pub fn build_and_unwrap_result(self) -> InstantiateReturnValue {
@@ -164,8 +161,6 @@ builder!(
 			code,
 			data: vec![],
 			salt: Some([0; 32]),
-			debug: DebugInfo::UnsafeDebug,
-			collect_events: CollectEvents::Skip,
 		}
 	}
 );
@@ -201,9 +196,7 @@ builder!(
 		gas_limit: Weight,
 		storage_deposit_limit: DepositLimit<BalanceOf<T>>,
 		data: Vec<u8>,
-		debug: DebugInfo,
-		collect_events: CollectEvents,
-	) -> ContractResult<ExecReturnValue, BalanceOf<T>, EventRecordOf<T>>;
+	) -> ContractResult<ExecReturnValue, BalanceOf<T>>;
 
 	/// Build the call and unwrap the result.
 	pub fn build_and_unwrap_result(self) -> ExecReturnValue {
@@ -219,8 +212,6 @@ builder!(
 			gas_limit: GAS_LIMIT,
 			storage_deposit_limit: DepositLimit::Balance(deposit_limit::<T>()),
 			data: vec![],
-			debug: DebugInfo::UnsafeDebug,
-			collect_events: CollectEvents::Skip,
 		}
 	}
 );
diff --git a/substrate/frame/revive/src/tests.rs b/substrate/frame/revive/src/tests.rs
index cf02d17a4d03ddaf5ed2df81bf9e01e98ca1e1bb..90b9f053a03fbf05fd180be81c23da63955134ef 100644
--- a/substrate/frame/revive/src/tests.rs
+++ b/substrate/frame/revive/src/tests.rs
@@ -16,12 +16,8 @@
 // limitations under the License.
 
 mod pallet_dummy;
-mod test_debug;
 
-use self::{
-	test_debug::TestDebug,
-	test_utils::{ensure_stored, expected_deposit},
-};
+use self::test_utils::{ensure_stored, expected_deposit};
 use crate::{
 	self as pallet_revive,
 	address::{create1, create2, AddressMapper},
@@ -29,18 +25,19 @@ use crate::{
 		ChainExtension, Environment, Ext, RegisteredChainExtension, Result as ExtensionResult,
 		RetVal, ReturnFlags,
 	},
-	evm::{runtime::GAS_PRICE, GenericTransaction},
+	evm::{runtime::GAS_PRICE, CallTrace, CallTracer, CallType, GenericTransaction},
 	exec::Key,
 	limits,
 	primitives::CodeUploadReturnValue,
 	storage::DeletionQueueManager,
 	test_utils::*,
 	tests::test_utils::{get_contract, get_contract_checked},
+	tracing::trace,
 	wasm::Memory,
 	weights::WeightInfo,
-	AccountId32Mapper, BalanceOf, Code, CodeInfoOf, CollectEvents, Config, ContractInfo,
-	ContractInfoOf, DebugInfo, DeletionQueueCounter, DepositLimit, Error, EthTransactError,
-	HoldReason, Origin, Pallet, PristineCode, H160,
+	AccountId32Mapper, BalanceOf, Code, CodeInfoOf, Config, ContractInfo, ContractInfoOf,
+	DeletionQueueCounter, DepositLimit, Error, EthTransactError, HoldReason, Origin, Pallet,
+	PristineCode, H160,
 };
 
 use crate::test_utils::builder::Contract;
@@ -523,7 +520,6 @@ impl Config for Test {
 	type UploadOrigin = EnsureAccount<Self, UploadAccount>;
 	type InstantiateOrigin = EnsureAccount<Self, InstantiateAccount>;
 	type CodeHashLockupDepositPercent = CodeHashLockupDepositPercent;
-	type Debug = TestDebug;
 	type ChainId = ChainId;
 }
 
@@ -713,25 +709,6 @@ fn instantiate_and_call_and_deposit_event() {
 					}),
 					topics: vec![],
 				},
-				EventRecord {
-					phase: Phase::Initialization,
-					event: RuntimeEvent::Contracts(crate::Event::Instantiated {
-						deployer: ALICE_ADDR,
-						contract: addr
-					}),
-					topics: vec![],
-				},
-				EventRecord {
-					phase: Phase::Initialization,
-					event: RuntimeEvent::Contracts(
-						pallet_revive::Event::StorageDepositTransferredAndHeld {
-							from: ALICE_ADDR,
-							to: addr,
-							amount: test_utils::contract_info_storage_deposit(&addr),
-						}
-					),
-					topics: vec![],
-				},
 			]
 		);
 	});
@@ -1078,14 +1055,6 @@ fn deploy_and_call_other_contract() {
 					}),
 					topics: vec![],
 				},
-				EventRecord {
-					phase: Phase::Initialization,
-					event: RuntimeEvent::Contracts(crate::Event::Instantiated {
-						deployer: caller_addr,
-						contract: callee_addr,
-					}),
-					topics: vec![],
-				},
 				EventRecord {
 					phase: Phase::Initialization,
 					event: RuntimeEvent::Balances(pallet_balances::Event::Transfer {
@@ -1095,33 +1064,6 @@ fn deploy_and_call_other_contract() {
 					}),
 					topics: vec![],
 				},
-				EventRecord {
-					phase: Phase::Initialization,
-					event: RuntimeEvent::Contracts(crate::Event::Called {
-						caller: Origin::from_account_id(caller_account.clone()),
-						contract: callee_addr,
-					}),
-					topics: vec![],
-				},
-				EventRecord {
-					phase: Phase::Initialization,
-					event: RuntimeEvent::Contracts(crate::Event::Called {
-						caller: Origin::from_account_id(ALICE),
-						contract: caller_addr,
-					}),
-					topics: vec![],
-				},
-				EventRecord {
-					phase: Phase::Initialization,
-					event: RuntimeEvent::Contracts(
-						pallet_revive::Event::StorageDepositTransferredAndHeld {
-							from: ALICE_ADDR,
-							to: callee_addr,
-							amount: test_utils::contract_info_storage_deposit(&callee_addr),
-						}
-					),
-					topics: vec![],
-				},
 			]
 		);
 	});
@@ -1373,8 +1315,6 @@ fn self_destruct_works() {
 		// Check that the BOB contract has been instantiated.
 		let _ = get_contract(&contract.addr);
 
-		let info_deposit = test_utils::contract_info_storage_deposit(&contract.addr);
-
 		// Drop all previous events
 		initialize_block(2);
 
@@ -1404,33 +1344,6 @@ fn self_destruct_works() {
 		pretty_assertions::assert_eq!(
 			System::events(),
 			vec![
-				EventRecord {
-					phase: Phase::Initialization,
-					event: RuntimeEvent::Contracts(crate::Event::Terminated {
-						contract: contract.addr,
-						beneficiary: DJANGO_ADDR,
-					}),
-					topics: vec![],
-				},
-				EventRecord {
-					phase: Phase::Initialization,
-					event: RuntimeEvent::Contracts(crate::Event::Called {
-						caller: Origin::from_account_id(ALICE),
-						contract: contract.addr,
-					}),
-					topics: vec![],
-				},
-				EventRecord {
-					phase: Phase::Initialization,
-					event: RuntimeEvent::Contracts(
-						pallet_revive::Event::StorageDepositTransferredAndReleased {
-							from: contract.addr,
-							to: ALICE_ADDR,
-							amount: info_deposit,
-						}
-					),
-					topics: vec![],
-				},
 				EventRecord {
 					phase: Phase::Initialization,
 					event: RuntimeEvent::System(frame_system::Event::KilledAccount {
@@ -2184,58 +2097,6 @@ fn refcounter() {
 	});
 }
 
-#[test]
-fn debug_message_works() {
-	let (wasm, _code_hash) = compile_module("debug_message_works").unwrap();
-
-	ExtBuilder::default().existential_deposit(50).build().execute_with(|| {
-		let _ = <Test as Config>::Currency::set_balance(&ALICE, 1_000_000);
-		let Contract { addr, .. } = builder::bare_instantiate(Code::Upload(wasm))
-			.value(30_000)
-			.build_and_unwrap_contract();
-		let result = builder::bare_call(addr).debug(DebugInfo::UnsafeDebug).build();
-
-		assert_matches!(result.result, Ok(_));
-		assert_eq!(std::str::from_utf8(&result.debug_message).unwrap(), "Hello World!");
-	});
-}
-
-#[test]
-fn debug_message_logging_disabled() {
-	let (wasm, _code_hash) = compile_module("debug_message_logging_disabled").unwrap();
-
-	ExtBuilder::default().existential_deposit(50).build().execute_with(|| {
-		let _ = <Test as Config>::Currency::set_balance(&ALICE, 1_000_000);
-		let Contract { addr, .. } = builder::bare_instantiate(Code::Upload(wasm))
-			.value(30_000)
-			.build_and_unwrap_contract();
-		// the dispatchables always run without debugging
-		assert_ok!(Contracts::call(
-			RuntimeOrigin::signed(ALICE),
-			addr,
-			0,
-			GAS_LIMIT,
-			deposit_limit::<Test>(),
-			vec![]
-		));
-	});
-}
-
-#[test]
-fn debug_message_invalid_utf8() {
-	let (wasm, _code_hash) = compile_module("debug_message_invalid_utf8").unwrap();
-
-	ExtBuilder::default().existential_deposit(50).build().execute_with(|| {
-		let _ = <Test as Config>::Currency::set_balance(&ALICE, 1_000_000);
-		let Contract { addr, .. } = builder::bare_instantiate(Code::Upload(wasm))
-			.value(30_000)
-			.build_and_unwrap_contract();
-		let result = builder::bare_call(addr).debug(DebugInfo::UnsafeDebug).build();
-		assert_ok!(result.result);
-		assert!(result.debug_message.is_empty());
-	});
-}
-
 #[test]
 fn gas_estimation_for_subcalls() {
 	let (caller_code, _caller_hash) = compile_module("call_with_limit").unwrap();
@@ -2451,79 +2312,6 @@ fn ecdsa_recover() {
 	})
 }
 
-#[test]
-fn bare_instantiate_returns_events() {
-	let (wasm, _code_hash) = compile_module("transfer_return_code").unwrap();
-	ExtBuilder::default().existential_deposit(50).build().execute_with(|| {
-		let min_balance = Contracts::min_balance();
-		let _ = <Test as Config>::Currency::set_balance(&ALICE, 1000 * min_balance);
-
-		let result = builder::bare_instantiate(Code::Upload(wasm))
-			.value(min_balance * 100)
-			.collect_events(CollectEvents::UnsafeCollect)
-			.build();
-
-		let events = result.events.unwrap();
-		assert!(!events.is_empty());
-		assert_eq!(events, System::events());
-	});
-}
-
-#[test]
-fn bare_instantiate_does_not_return_events() {
-	let (wasm, _code_hash) = compile_module("transfer_return_code").unwrap();
-	ExtBuilder::default().existential_deposit(50).build().execute_with(|| {
-		let min_balance = Contracts::min_balance();
-		let _ = <Test as Config>::Currency::set_balance(&ALICE, 1000 * min_balance);
-
-		let result = builder::bare_instantiate(Code::Upload(wasm)).value(min_balance * 100).build();
-
-		let events = result.events;
-		assert!(!System::events().is_empty());
-		assert!(events.is_none());
-	});
-}
-
-#[test]
-fn bare_call_returns_events() {
-	let (wasm, _code_hash) = compile_module("transfer_return_code").unwrap();
-	ExtBuilder::default().existential_deposit(50).build().execute_with(|| {
-		let min_balance = Contracts::min_balance();
-		let _ = <Test as Config>::Currency::set_balance(&ALICE, 1000 * min_balance);
-
-		let Contract { addr, .. } = builder::bare_instantiate(Code::Upload(wasm))
-			.value(min_balance * 100)
-			.build_and_unwrap_contract();
-
-		let result = builder::bare_call(addr).collect_events(CollectEvents::UnsafeCollect).build();
-
-		let events = result.events.unwrap();
-		assert_return_code!(&result.result.unwrap(), RuntimeReturnCode::Success);
-		assert!(!events.is_empty());
-		assert_eq!(events, System::events());
-	});
-}
-
-#[test]
-fn bare_call_does_not_return_events() {
-	let (wasm, _code_hash) = compile_module("transfer_return_code").unwrap();
-	ExtBuilder::default().existential_deposit(50).build().execute_with(|| {
-		let min_balance = Contracts::min_balance();
-		let _ = <Test as Config>::Currency::set_balance(&ALICE, 1000 * min_balance);
-
-		let Contract { addr, .. } = builder::bare_instantiate(Code::Upload(wasm))
-			.value(min_balance * 100)
-			.build_and_unwrap_contract();
-
-		let result = builder::bare_call(addr).build();
-
-		let events = result.events;
-		assert_return_code!(&result.result.unwrap(), RuntimeReturnCode::Success);
-		assert!(!System::events().is_empty());
-		assert!(events.is_none());
-	});
-}
-
 #[test]
 fn sr25519_verify() {
 	let (wasm, _code_hash) = compile_module("sr25519_verify").unwrap();
@@ -2637,23 +2425,9 @@ fn upload_code_works() {
 		initialize_block(2);
 
 		assert!(!PristineCode::<Test>::contains_key(&code_hash));
-
 		assert_ok!(Contracts::upload_code(RuntimeOrigin::signed(ALICE), wasm, 1_000,));
 		// Ensure the contract was stored and get expected deposit amount to be reserved.
-		let deposit_expected = expected_deposit(ensure_stored(code_hash));
-
-		assert_eq!(
-			System::events(),
-			vec![EventRecord {
-				phase: Phase::Initialization,
-				event: RuntimeEvent::Contracts(crate::Event::CodeStored {
-					code_hash,
-					deposit_held: deposit_expected,
-					uploader: ALICE_ADDR
-				}),
-				topics: vec![],
-			},]
-		);
+		expected_deposit(ensure_stored(code_hash));
 	});
 }
 
@@ -2711,32 +2485,8 @@ fn remove_code_works() {
 
 		assert_ok!(Contracts::upload_code(RuntimeOrigin::signed(ALICE), wasm, 1_000,));
 		// Ensure the contract was stored and get expected deposit amount to be reserved.
-		let deposit_expected = expected_deposit(ensure_stored(code_hash));
-
+		expected_deposit(ensure_stored(code_hash));
 		assert_ok!(Contracts::remove_code(RuntimeOrigin::signed(ALICE), code_hash));
-		assert_eq!(
-			System::events(),
-			vec![
-				EventRecord {
-					phase: Phase::Initialization,
-					event: RuntimeEvent::Contracts(crate::Event::CodeStored {
-						code_hash,
-						deposit_held: deposit_expected,
-						uploader: ALICE_ADDR
-					}),
-					topics: vec![],
-				},
-				EventRecord {
-					phase: Phase::Initialization,
-					event: RuntimeEvent::Contracts(crate::Event::CodeRemoved {
-						code_hash,
-						deposit_released: deposit_expected,
-						remover: ALICE_ADDR
-					}),
-					topics: vec![],
-				},
-			]
-		);
 	});
 }
 
@@ -2752,25 +2502,12 @@ fn remove_code_wrong_origin() {
 
 		assert_ok!(Contracts::upload_code(RuntimeOrigin::signed(ALICE), wasm, 1_000,));
 		// Ensure the contract was stored and get expected deposit amount to be reserved.
-		let deposit_expected = expected_deposit(ensure_stored(code_hash));
+		expected_deposit(ensure_stored(code_hash));
 
 		assert_noop!(
 			Contracts::remove_code(RuntimeOrigin::signed(BOB), code_hash),
 			sp_runtime::traits::BadOrigin,
 		);
-
-		assert_eq!(
-			System::events(),
-			vec![EventRecord {
-				phase: Phase::Initialization,
-				event: RuntimeEvent::Contracts(crate::Event::CodeStored {
-					code_hash,
-					deposit_held: deposit_expected,
-					uploader: ALICE_ADDR
-				}),
-				topics: vec![],
-			},]
-		);
 	});
 }
 
@@ -2829,7 +2566,7 @@ fn instantiate_with_zero_balance_works() {
 			builder::bare_instantiate(Code::Upload(wasm)).build_and_unwrap_contract();
 
 		// Ensure the contract was stored and get expected deposit amount to be reserved.
-		let deposit_expected = expected_deposit(ensure_stored(code_hash));
+		expected_deposit(ensure_stored(code_hash));
 
 		// Make sure the account exists even though no free balance was send
 		assert_eq!(<Test as Config>::Currency::free_balance(&account_id), min_balance);
@@ -2841,15 +2578,6 @@ fn instantiate_with_zero_balance_works() {
 		assert_eq!(
 			System::events(),
 			vec![
-				EventRecord {
-					phase: Phase::Initialization,
-					event: RuntimeEvent::Contracts(crate::Event::CodeStored {
-						code_hash,
-						deposit_held: deposit_expected,
-						uploader: ALICE_ADDR
-					}),
-					topics: vec![],
-				},
 				EventRecord {
 					phase: Phase::Initialization,
 					event: RuntimeEvent::System(frame_system::Event::NewAccount {
@@ -2874,25 +2602,6 @@ fn instantiate_with_zero_balance_works() {
 					}),
 					topics: vec![],
 				},
-				EventRecord {
-					phase: Phase::Initialization,
-					event: RuntimeEvent::Contracts(crate::Event::Instantiated {
-						deployer: ALICE_ADDR,
-						contract: addr,
-					}),
-					topics: vec![],
-				},
-				EventRecord {
-					phase: Phase::Initialization,
-					event: RuntimeEvent::Contracts(
-						pallet_revive::Event::StorageDepositTransferredAndHeld {
-							from: ALICE_ADDR,
-							to: addr,
-							amount: test_utils::contract_info_storage_deposit(&addr),
-						}
-					),
-					topics: vec![],
-				},
 			]
 		);
 	});
@@ -2915,7 +2624,7 @@ fn instantiate_with_below_existential_deposit_works() {
 			.build_and_unwrap_contract();
 
 		// Ensure the contract was stored and get expected deposit amount to be reserved.
-		let deposit_expected = expected_deposit(ensure_stored(code_hash));
+		expected_deposit(ensure_stored(code_hash));
 		// Make sure the account exists even though not enough free balance was send
 		assert_eq!(<Test as Config>::Currency::free_balance(&account_id), min_balance + value);
 		assert_eq!(
@@ -2926,15 +2635,6 @@ fn instantiate_with_below_existential_deposit_works() {
 		assert_eq!(
 			System::events(),
 			vec![
-				EventRecord {
-					phase: Phase::Initialization,
-					event: RuntimeEvent::Contracts(crate::Event::CodeStored {
-						code_hash,
-						deposit_held: deposit_expected,
-						uploader: ALICE_ADDR
-					}),
-					topics: vec![],
-				},
 				EventRecord {
 					phase: Phase::Initialization,
 					event: RuntimeEvent::System(frame_system::Event::NewAccount {
@@ -2968,25 +2668,6 @@ fn instantiate_with_below_existential_deposit_works() {
 					}),
 					topics: vec![],
 				},
-				EventRecord {
-					phase: Phase::Initialization,
-					event: RuntimeEvent::Contracts(crate::Event::Instantiated {
-						deployer: ALICE_ADDR,
-						contract: addr,
-					}),
-					topics: vec![],
-				},
-				EventRecord {
-					phase: Phase::Initialization,
-					event: RuntimeEvent::Contracts(
-						pallet_revive::Event::StorageDepositTransferredAndHeld {
-							from: ALICE_ADDR,
-							to: addr,
-							amount: test_utils::contract_info_storage_deposit(&addr),
-						}
-					),
-					topics: vec![],
-				},
 			]
 		);
 	});
@@ -3028,74 +2709,15 @@ fn storage_deposit_works() {
 
 		assert_eq!(
 			System::events(),
-			vec![
-				EventRecord {
-					phase: Phase::Initialization,
-					event: RuntimeEvent::Balances(pallet_balances::Event::Transfer {
-						from: ALICE,
-						to: account_id.clone(),
-						amount: 42,
-					}),
-					topics: vec![],
-				},
-				EventRecord {
-					phase: Phase::Initialization,
-					event: RuntimeEvent::Contracts(crate::Event::Called {
-						caller: Origin::from_account_id(ALICE),
-						contract: addr,
-					}),
-					topics: vec![],
-				},
-				EventRecord {
-					phase: Phase::Initialization,
-					event: RuntimeEvent::Contracts(
-						pallet_revive::Event::StorageDepositTransferredAndHeld {
-							from: ALICE_ADDR,
-							to: addr,
-							amount: charged0,
-						}
-					),
-					topics: vec![],
-				},
-				EventRecord {
-					phase: Phase::Initialization,
-					event: RuntimeEvent::Contracts(crate::Event::Called {
-						caller: Origin::from_account_id(ALICE),
-						contract: addr,
-					}),
-					topics: vec![],
-				},
-				EventRecord {
-					phase: Phase::Initialization,
-					event: RuntimeEvent::Contracts(
-						pallet_revive::Event::StorageDepositTransferredAndHeld {
-							from: ALICE_ADDR,
-							to: addr,
-							amount: charged1,
-						}
-					),
-					topics: vec![],
-				},
-				EventRecord {
-					phase: Phase::Initialization,
-					event: RuntimeEvent::Contracts(crate::Event::Called {
-						caller: Origin::from_account_id(ALICE),
-						contract: addr,
-					}),
-					topics: vec![],
-				},
-				EventRecord {
-					phase: Phase::Initialization,
-					event: RuntimeEvent::Contracts(
-						pallet_revive::Event::StorageDepositTransferredAndReleased {
-							from: addr,
-							to: ALICE_ADDR,
-							amount: refunded0,
-						}
-					),
-					topics: vec![],
-				},
-			]
+			vec![EventRecord {
+				phase: Phase::Initialization,
+				event: RuntimeEvent::Balances(pallet_balances::Event::Transfer {
+					from: ALICE,
+					to: account_id.clone(),
+					amount: 42,
+				}),
+				topics: vec![],
+			},]
 		);
 	});
 }
@@ -3188,18 +2810,6 @@ fn set_code_extrinsic() {
 		assert_eq!(get_contract(&addr).code_hash, new_code_hash);
 		assert_refcount!(&code_hash, 0);
 		assert_refcount!(&new_code_hash, 1);
-		assert_eq!(
-			System::events(),
-			vec![EventRecord {
-				phase: Phase::Initialization,
-				event: RuntimeEvent::Contracts(pallet_revive::Event::ContractCodeUpdated {
-					contract: addr,
-					new_code_hash,
-					old_code_hash: code_hash,
-				}),
-				topics: vec![],
-			},]
-		);
 	});
 }
 
@@ -3305,7 +2915,7 @@ fn contract_reverted() {
 
 #[test]
 fn set_code_hash() {
-	let (wasm, code_hash) = compile_module("set_code_hash").unwrap();
+	let (wasm, _) = compile_module("set_code_hash").unwrap();
 	let (new_wasm, new_code_hash) = compile_module("new_set_code_hash_contract").unwrap();
 
 	ExtBuilder::default().existential_deposit(100).build().execute_with(|| {
@@ -3327,47 +2937,12 @@ fn set_code_hash() {
 		// First call sets new code_hash and returns 1
 		let result = builder::bare_call(contract_addr)
 			.data(new_code_hash.as_ref().to_vec())
-			.debug(DebugInfo::UnsafeDebug)
 			.build_and_unwrap_result();
 		assert_return_code!(result, 1);
 
 		// Second calls new contract code that returns 2
-		let result = builder::bare_call(contract_addr)
-			.debug(DebugInfo::UnsafeDebug)
-			.build_and_unwrap_result();
+		let result = builder::bare_call(contract_addr).build_and_unwrap_result();
 		assert_return_code!(result, 2);
-
-		// Checking for the last event only
-		assert_eq!(
-			&System::events(),
-			&[
-				EventRecord {
-					phase: Phase::Initialization,
-					event: RuntimeEvent::Contracts(crate::Event::ContractCodeUpdated {
-						contract: contract_addr,
-						new_code_hash,
-						old_code_hash: code_hash,
-					}),
-					topics: vec![],
-				},
-				EventRecord {
-					phase: Phase::Initialization,
-					event: RuntimeEvent::Contracts(crate::Event::Called {
-						caller: Origin::from_account_id(ALICE),
-						contract: contract_addr,
-					}),
-					topics: vec![],
-				},
-				EventRecord {
-					phase: Phase::Initialization,
-					event: RuntimeEvent::Contracts(crate::Event::Called {
-						caller: Origin::from_account_id(ALICE),
-						contract: contract_addr,
-					}),
-					topics: vec![],
-				},
-			],
-		);
 	});
 }
 
@@ -4660,6 +4235,43 @@ fn origin_api_works() {
 	});
 }
 
+#[test]
+fn to_account_id_works() {
+	let (code_hash_code, _) = compile_module("to_account_id").unwrap();
+
+	ExtBuilder::default().existential_deposit(1).build().execute_with(|| {
+		let _ = <Test as Config>::Currency::set_balance(&ALICE, 1_000_000);
+		let _ = <Test as Config>::Currency::set_balance(&EVE, 1_000_000);
+
+		let Contract { addr, .. } =
+			builder::bare_instantiate(Code::Upload(code_hash_code)).build_and_unwrap_contract();
+
+		// mapped account
+		<Pallet<Test>>::map_account(RuntimeOrigin::signed(EVE)).unwrap();
+		let expected_mapped_account_id = &<Test as Config>::AddressMapper::to_account_id(&EVE_ADDR);
+		assert_ne!(
+			expected_mapped_account_id.encode()[20..32],
+			[0xEE; 12],
+			"fallback suffix found where none should be"
+		);
+		assert_ok!(builder::call(addr)
+			.data((EVE_ADDR, expected_mapped_account_id).encode())
+			.build());
+
+		// fallback for unmapped accounts
+		let expected_fallback_account_id =
+			&<Test as Config>::AddressMapper::to_account_id(&BOB_ADDR);
+		assert_eq!(
+			expected_fallback_account_id.encode()[20..32],
+			[0xEE; 12],
+			"no fallback suffix found where one should be"
+		);
+		assert_ok!(builder::call(addr)
+			.data((BOB_ADDR, expected_fallback_account_id).encode())
+			.build());
+	});
+}
+
 #[test]
 fn code_hash_works() {
 	let (code_hash_code, self_code_hash) = compile_module("code_hash").unwrap();
@@ -4810,7 +4422,7 @@ fn skip_transfer_works() {
 					..Default::default()
 				},
 				Weight::MAX,
-				|_| 0u32
+				|_| 0u32,
 			),
 			EthTransactError::Message(format!(
 				"insufficient funds for gas * price + value: address {BOB_ADDR:?} have 0 (supplied gas 1)"
@@ -4825,7 +4437,7 @@ fn skip_transfer_works() {
 				..Default::default()
 			},
 			Weight::MAX,
-			|_| 0u32
+			|_| 0u32,
 		));
 
 		let Contract { addr, .. } =
@@ -4844,7 +4456,7 @@ fn skip_transfer_works() {
 					..Default::default()
 				},
 				Weight::MAX,
-				|_| 0u32
+				|_| 0u32,
 			),
 			EthTransactError::Message(format!(
 				"insufficient funds for gas * price + value: address {BOB_ADDR:?} have 0 (supplied gas 1)"
@@ -4869,7 +4481,7 @@ fn skip_transfer_works() {
 		assert_ok!(Pallet::<Test>::bare_eth_transact(
 			GenericTransaction { from: Some(BOB_ADDR), to: Some(addr), ..Default::default() },
 			Weight::MAX,
-			|_| 0u32
+			|_| 0u32,
 		));
 
 		// works when calling from a contract when no gas is specified.
@@ -4881,7 +4493,7 @@ fn skip_transfer_works() {
 				..Default::default()
 			},
 			Weight::MAX,
-			|_| 0u32
+			|_| 0u32,
 		));
 	});
 }
@@ -4938,3 +4550,151 @@ fn unstable_interface_rejected() {
 		assert_ok!(builder::bare_instantiate(Code::Upload(code)).build().result);
 	});
 }
+
+#[test]
+fn tracing_works_for_transfers() {
+	ExtBuilder::default().build().execute_with(|| {
+		let _ = <Test as Config>::Currency::set_balance(&ALICE, 100_000_000);
+		let mut tracer = CallTracer::new(false, |_| U256::zero());
+		trace(&mut tracer, || {
+			builder::bare_call(BOB_ADDR).value(10_000_000).build_and_unwrap_result();
+		});
+		assert_eq!(
+			tracer.collect_traces(),
+			vec![CallTrace {
+				from: ALICE_ADDR,
+				to: BOB_ADDR,
+				value: U256::from(10_000_000),
+				call_type: CallType::Call,
+				..Default::default()
+			},]
+		)
+	});
+}
+
+#[test]
+fn tracing_works() {
+	use crate::evm::*;
+	use CallType::*;
+	let (code, _code_hash) = compile_module("tracing").unwrap();
+	let (wasm_callee, _) = compile_module("tracing_callee").unwrap();
+	ExtBuilder::default().existential_deposit(200).build().execute_with(|| {
+		let _ = <Test as Config>::Currency::set_balance(&ALICE, 1_000_000);
+
+		let Contract { addr: addr_callee, .. } =
+			builder::bare_instantiate(Code::Upload(wasm_callee)).build_and_unwrap_contract();
+
+		let Contract { addr, .. } =
+			builder::bare_instantiate(Code::Upload(code)).build_and_unwrap_contract();
+
+		let tracer_options = vec![
+			( false , vec![]),
+			(
+				true ,
+				vec![
+					CallLog {
+						address: addr,
+						topics: Default::default(),
+						data: b"before".to_vec().into(),
+						position: 0,
+					},
+					CallLog {
+						address: addr,
+						topics: Default::default(),
+						data: b"after".to_vec().into(),
+						position: 1,
+					},
+				],
+			),
+		];
+
+		// Verify that the first trace report the same weight reported by bare_call
+		let mut tracer = CallTracer::new(false, |w| w);
+		let gas_used = trace(&mut tracer, || {
+			builder::bare_call(addr).data((3u32, addr_callee).encode()).build().gas_consumed
+		});
+		let traces = tracer.collect_traces();
+		assert_eq!(&traces[0].gas_used, &gas_used);
+
+		// Discarding gas usage, check that traces reported are correct
+		for (with_logs, logs) in tracer_options {
+			let mut tracer = CallTracer::new(with_logs, |_| U256::zero());
+			trace(&mut tracer, || {
+				builder::bare_call(addr).data((3u32, addr_callee).encode()).build()
+			});
+
+
+			assert_eq!(
+				tracer.collect_traces(),
+				vec![CallTrace {
+					from: ALICE_ADDR,
+					to: addr,
+					input: (3u32, addr_callee).encode(),
+					call_type: Call,
+					logs: logs.clone(),
+					calls: vec![
+						CallTrace {
+							from: addr,
+							to: addr_callee,
+							input: 2u32.encode(),
+							output: hex_literal::hex!(
+										"08c379a00000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000001a546869732066756e6374696f6e20616c77617973206661696c73000000000000"
+									).to_vec().into(),
+							revert_reason: Some(
+								"execution reverted: This function always fails".to_string()
+							),
+							error: Some("execution reverted".to_string()),
+							call_type: Call,
+							..Default::default()
+						},
+						CallTrace {
+							from: addr,
+							to: addr,
+							input: (2u32, addr_callee).encode(),
+							call_type: Call,
+							logs: logs.clone(),
+							calls: vec![
+								CallTrace {
+									from: addr,
+									to: addr_callee,
+									input: 1u32.encode(),
+									output: Default::default(),
+									error: Some("ContractTrapped".to_string()),
+									call_type: Call,
+									..Default::default()
+								},
+								CallTrace {
+									from: addr,
+									to: addr,
+									input: (1u32, addr_callee).encode(),
+									call_type: Call,
+									logs: logs.clone(),
+									calls: vec![
+										CallTrace {
+											from: addr,
+											to: addr_callee,
+											input: 0u32.encode(),
+											output: 0u32.to_le_bytes().to_vec().into(),
+											call_type: Call,
+											..Default::default()
+										},
+										CallTrace {
+											from: addr,
+											to: addr,
+											input: (0u32, addr_callee).encode(),
+											call_type: Call,
+											..Default::default()
+										},
+									],
+									..Default::default()
+								},
+							],
+							..Default::default()
+						},
+					],
+					..Default::default()
+				},]
+			);
+		}
+	});
+}
diff --git a/substrate/frame/revive/src/tests/test_debug.rs b/substrate/frame/revive/src/tests/test_debug.rs
deleted file mode 100644
index c9e19e52ace1372aecf6129c22f03b11f845f66c..0000000000000000000000000000000000000000
--- a/substrate/frame/revive/src/tests/test_debug.rs
+++ /dev/null
@@ -1,239 +0,0 @@
-// This file is part of Substrate.
-
-// Copyright (C) Parity Technologies (UK) Ltd.
-// SPDX-License-Identifier: Apache-2.0
-
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// 	http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-use super::*;
-
-use crate::{
-	debug::{CallInterceptor, CallSpan, ExecResult, ExportedFunction, Tracing},
-	primitives::ExecReturnValue,
-	test_utils::*,
-	DepositLimit,
-};
-use frame_support::traits::Currency;
-use pretty_assertions::assert_eq;
-use sp_core::H160;
-use std::cell::RefCell;
-
-#[derive(Clone, PartialEq, Eq, Debug)]
-struct DebugFrame {
-	contract_address: sp_core::H160,
-	call: ExportedFunction,
-	input: Vec<u8>,
-	result: Option<Vec<u8>>,
-}
-
-thread_local! {
-	static DEBUG_EXECUTION_TRACE: RefCell<Vec<DebugFrame>> = RefCell::new(Vec::new());
-	static INTERCEPTED_ADDRESS: RefCell<Option<sp_core::H160>> = RefCell::new(None);
-}
-
-pub struct TestDebug;
-pub struct TestCallSpan {
-	contract_address: sp_core::H160,
-	call: ExportedFunction,
-	input: Vec<u8>,
-}
-
-impl Tracing<Test> for TestDebug {
-	type CallSpan = TestCallSpan;
-
-	fn new_call_span(
-		contract_address: &crate::H160,
-		entry_point: ExportedFunction,
-		input_data: &[u8],
-	) -> TestCallSpan {
-		DEBUG_EXECUTION_TRACE.with(|d| {
-			d.borrow_mut().push(DebugFrame {
-				contract_address: *contract_address,
-				call: entry_point,
-				input: input_data.to_vec(),
-				result: None,
-			})
-		});
-		TestCallSpan {
-			contract_address: *contract_address,
-			call: entry_point,
-			input: input_data.to_vec(),
-		}
-	}
-}
-
-impl CallInterceptor<Test> for TestDebug {
-	fn intercept_call(
-		contract_address: &sp_core::H160,
-		_entry_point: ExportedFunction,
-		_input_data: &[u8],
-	) -> Option<ExecResult> {
-		INTERCEPTED_ADDRESS.with(|i| {
-			if i.borrow().as_ref() == Some(contract_address) {
-				Some(Ok(ExecReturnValue { flags: ReturnFlags::REVERT, data: vec![] }))
-			} else {
-				None
-			}
-		})
-	}
-}
-
-impl CallSpan for TestCallSpan {
-	fn after_call(self, output: &ExecReturnValue) {
-		DEBUG_EXECUTION_TRACE.with(|d| {
-			d.borrow_mut().push(DebugFrame {
-				contract_address: self.contract_address,
-				call: self.call,
-				input: self.input,
-				result: Some(output.data.clone()),
-			})
-		});
-	}
-}
-
-#[test]
-fn debugging_works() {
-	let (wasm_caller, _) = compile_module("call").unwrap();
-	let (wasm_callee, _) = compile_module("store_call").unwrap();
-
-	fn current_stack() -> Vec<DebugFrame> {
-		DEBUG_EXECUTION_TRACE.with(|stack| stack.borrow().clone())
-	}
-
-	fn deploy(wasm: Vec<u8>) -> H160 {
-		Contracts::bare_instantiate(
-			RuntimeOrigin::signed(ALICE),
-			0,
-			GAS_LIMIT,
-			DepositLimit::Balance(deposit_limit::<Test>()),
-			Code::Upload(wasm),
-			vec![],
-			Some([0u8; 32]),
-			DebugInfo::Skip,
-			CollectEvents::Skip,
-		)
-		.result
-		.unwrap()
-		.addr
-	}
-
-	fn constructor_frame(contract_address: &H160, after: bool) -> DebugFrame {
-		DebugFrame {
-			contract_address: *contract_address,
-			call: ExportedFunction::Constructor,
-			input: vec![],
-			result: if after { Some(vec![]) } else { None },
-		}
-	}
-
-	fn call_frame(contract_address: &H160, args: Vec<u8>, after: bool) -> DebugFrame {
-		DebugFrame {
-			contract_address: *contract_address,
-			call: ExportedFunction::Call,
-			input: args,
-			result: if after { Some(vec![]) } else { None },
-		}
-	}
-
-	ExtBuilder::default().existential_deposit(200).build().execute_with(|| {
-		let _ = Balances::deposit_creating(&ALICE, 1_000_000);
-
-		assert_eq!(current_stack(), vec![]);
-
-		let addr_caller = deploy(wasm_caller);
-		let addr_callee = deploy(wasm_callee);
-
-		assert_eq!(
-			current_stack(),
-			vec![
-				constructor_frame(&addr_caller, false),
-				constructor_frame(&addr_caller, true),
-				constructor_frame(&addr_callee, false),
-				constructor_frame(&addr_callee, true),
-			]
-		);
-
-		let main_args = (100u32, &addr_callee.clone()).encode();
-		let inner_args = (100u32).encode();
-
-		assert_ok!(Contracts::call(
-			RuntimeOrigin::signed(ALICE),
-			addr_caller,
-			0,
-			GAS_LIMIT,
-			deposit_limit::<Test>(),
-			main_args.clone()
-		));
-
-		let stack_top = current_stack()[4..].to_vec();
-		assert_eq!(
-			stack_top,
-			vec![
-				call_frame(&addr_caller, main_args.clone(), false),
-				call_frame(&addr_callee, inner_args.clone(), false),
-				call_frame(&addr_callee, inner_args, true),
-				call_frame(&addr_caller, main_args, true),
-			]
-		);
-	});
-}
-
-#[test]
-fn call_interception_works() {
-	let (wasm, _) = compile_module("dummy").unwrap();
-
-	ExtBuilder::default().existential_deposit(200).build().execute_with(|| {
-		let _ = Balances::deposit_creating(&ALICE, 1_000_000);
-
-		let account_id = Contracts::bare_instantiate(
-			RuntimeOrigin::signed(ALICE),
-			0,
-			GAS_LIMIT,
-			deposit_limit::<Test>().into(),
-			Code::Upload(wasm),
-			vec![],
-			// some salt to ensure that the address of this contract is unique among all tests
-			Some([0x41; 32]),
-			DebugInfo::Skip,
-			CollectEvents::Skip,
-		)
-		.result
-		.unwrap()
-		.addr;
-
-		// no interception yet
-		assert_ok!(Contracts::call(
-			RuntimeOrigin::signed(ALICE),
-			account_id,
-			0,
-			GAS_LIMIT,
-			deposit_limit::<Test>(),
-			vec![],
-		));
-
-		// intercept calls to this contract
-		INTERCEPTED_ADDRESS.with(|i| *i.borrow_mut() = Some(account_id));
-
-		assert_err_ignore_postinfo!(
-			Contracts::call(
-				RuntimeOrigin::signed(ALICE),
-				account_id,
-				0,
-				GAS_LIMIT,
-				deposit_limit::<Test>(),
-				vec![],
-			),
-			<Error<Test>>::ContractReverted,
-		);
-	});
-}
diff --git a/substrate/frame/revive/src/tracing.rs b/substrate/frame/revive/src/tracing.rs
new file mode 100644
index 0000000000000000000000000000000000000000..e9c05f8cb5058ee74b4fbb29cb9fe5aea30faac2
--- /dev/null
+++ b/substrate/frame/revive/src/tracing.rs
@@ -0,0 +1,64 @@
+// This file is part of Substrate.
+
+// Copyright (C) Parity Technologies (UK) Ltd.
+// SPDX-License-Identifier: Apache-2.0
+
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// 	http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+use crate::{primitives::ExecReturnValue, DispatchError, Weight};
+use environmental::environmental;
+use sp_core::{H160, H256, U256};
+
+environmental!(tracer: dyn Tracer + 'static);
+
+/// Trace the execution of the given closure.
+///
+/// # Warning
+///
+/// Only meant to be called from off-chain code as its additional resource usage is
+/// not accounted for in the weights or memory envelope.
+pub fn trace<R, F: FnOnce() -> R>(tracer: &mut (dyn Tracer + 'static), f: F) -> R {
+	tracer::using_once(tracer, f)
+}
+
+/// Run the closure when tracing is enabled.
+///
+/// This is safe to be called from on-chain code as tracing will never be activated
+/// there. Hence the closure is not executed in this case.
+pub(crate) fn if_tracing<F: FnOnce(&mut (dyn Tracer + 'static))>(f: F) {
+	tracer::with(f);
+}
+
+/// Defines methods to trace contract interactions.
+pub trait Tracer {
+	/// Called before a contract call is executed
+	fn enter_child_span(
+		&mut self,
+		from: H160,
+		to: H160,
+		is_delegate_call: bool,
+		is_read_only: bool,
+		value: U256,
+		input: &[u8],
+		gas: Weight,
+	);
+
+	/// Record a log event
+	fn log_event(&mut self, event: H160, topics: &[H256], data: &[u8]);
+
+	/// Called after a contract call is executed
+	fn exit_child_span(&mut self, output: &ExecReturnValue, gas_left: Weight);
+
+	/// Called when a contract call terminates with an error
+	fn exit_child_span_with_error(&mut self, error: DispatchError, gas_left: Weight);
+}
diff --git a/substrate/frame/revive/src/wasm/mod.rs b/substrate/frame/revive/src/wasm/mod.rs
index 3bd4bde5679f24ba89db49032f44909b851071c2..527cf16309540c3edacf626bb7e222f82677f3d9 100644
--- a/substrate/frame/revive/src/wasm/mod.rs
+++ b/substrate/frame/revive/src/wasm/mod.rs
@@ -29,14 +29,13 @@ pub use crate::wasm::runtime::{ReturnData, TrapReason};
 pub use crate::wasm::runtime::{Memory, Runtime, RuntimeCosts};
 
 use crate::{
-	address::AddressMapper,
 	exec::{ExecResult, Executable, ExportedFunction, Ext},
 	gas::{GasMeter, Token},
 	limits,
 	storage::meter::Diff,
 	weights::WeightInfo,
-	AccountIdOf, BadOrigin, BalanceOf, CodeInfoOf, CodeVec, Config, Error, Event, ExecError,
-	HoldReason, Pallet, PristineCode, Weight, LOG_TARGET,
+	AccountIdOf, BadOrigin, BalanceOf, CodeInfoOf, CodeVec, Config, Error, ExecError, HoldReason,
+	PristineCode, Weight, LOG_TARGET,
 };
 use alloc::vec::Vec;
 use codec::{Decode, Encode, MaxEncodedLen};
@@ -157,16 +156,9 @@ where
 					code_info.deposit,
 					BestEffort,
 				);
-				let deposit_released = code_info.deposit;
-				let remover = T::AddressMapper::to_address(&code_info.owner);
 
 				*existing = None;
 				<PristineCode<T>>::remove(&code_hash);
-				<Pallet<T>>::deposit_event(Event::CodeRemoved {
-					code_hash,
-					deposit_released,
-					remover,
-				});
 				Ok(())
 			} else {
 				Err(<Error<T>>::CodeNotFound.into())
@@ -202,12 +194,6 @@ where
 					self.code_info.refcount = 0;
 					<PristineCode<T>>::insert(code_hash, &self.code);
 					*stored_code_info = Some(self.code_info.clone());
-					let uploader = T::AddressMapper::to_address(&self.code_info.owner);
-					<Pallet<T>>::deposit_event(Event::CodeStored {
-						code_hash,
-						deposit_held: deposit,
-						uploader,
-					});
 					Ok(deposit)
 				},
 			}
@@ -288,7 +274,7 @@ impl<T: Config> WasmBlob<T> {
 		}
 		let engine = polkavm::Engine::new(&config).expect(
 			"on-chain (no_std) use of interpreter is hard coded.
-				interpreter is available on all plattforms; qed",
+				interpreter is available on all platforms; qed",
 		);
 
 		let mut module_config = polkavm::ModuleConfig::new();
diff --git a/substrate/frame/revive/src/wasm/runtime.rs b/substrate/frame/revive/src/wasm/runtime.rs
index 8529c7d9e73bbd878d91ba4fc4e483adda135504..4fbcfe1b47f5b1866e745ab11e599de6e0ce5f99 100644
--- a/substrate/frame/revive/src/wasm/runtime.rs
+++ b/substrate/frame/revive/src/wasm/runtime.rs
@@ -293,6 +293,8 @@ pub enum RuntimeCosts {
 	CallDataSize,
 	/// Weight of calling `seal_return_data_size`.
 	ReturnDataSize,
+	/// Weight of calling `seal_to_account_id`.
+	ToAccountId,
 	/// Weight of calling `seal_origin`.
 	Origin,
 	/// Weight of calling `seal_is_contract`.
@@ -339,8 +341,6 @@ pub enum RuntimeCosts {
 	Terminate(u32),
 	/// Weight of calling `seal_deposit_event` with the given number of topics and event size.
 	DepositEvent { num_topic: u32, len: u32 },
-	/// Weight of calling `seal_debug_message` per byte of passed message.
-	DebugMessage(u32),
 	/// Weight of calling `seal_set_storage` for the given storage item sizes.
 	SetStorage { old_bytes: u32, new_bytes: u32 },
 	/// Weight of calling `seal_clear_storage` per cleared byte.
@@ -468,6 +468,7 @@ impl<T: Config> Token<T> for RuntimeCosts {
 			Caller => T::WeightInfo::seal_caller(),
 			Origin => T::WeightInfo::seal_origin(),
 			IsContract => T::WeightInfo::seal_is_contract(),
+			ToAccountId => T::WeightInfo::seal_to_account_id(),
 			CodeHash => T::WeightInfo::seal_code_hash(),
 			CodeSize => T::WeightInfo::seal_code_size(),
 			OwnCodeHash => T::WeightInfo::seal_own_code_hash(),
@@ -489,7 +490,6 @@ impl<T: Config> Token<T> for RuntimeCosts {
 			WeightToFee => T::WeightInfo::seal_weight_to_fee(),
 			Terminate(locked_dependencies) => T::WeightInfo::seal_terminate(locked_dependencies),
 			DepositEvent { num_topic, len } => T::WeightInfo::seal_deposit_event(num_topic, len),
-			DebugMessage(len) => T::WeightInfo::seal_debug_message(len),
 			SetStorage { new_bytes, old_bytes } => {
 				cost_storage!(write, seal_set_storage, new_bytes, old_bytes)
 			},
@@ -669,10 +669,7 @@ impl<'a, E: Ext, M: ?Sized + Memory<E::T>> Runtime<'a, E, M> {
 		match result {
 			Ok(_) => Ok(ReturnErrorCode::Success),
 			Err(e) => {
-				if self.ext.debug_buffer_enabled() {
-					self.ext.append_debug_buffer("call failed with: ");
-					self.ext.append_debug_buffer(e.into());
-				};
+				log::debug!(target: LOG_TARGET, "call failed with: {e:?}");
 				Ok(ErrorReturnCode::get())
 			},
 		}
@@ -1832,27 +1829,6 @@ pub mod env {
 		self.contains_storage(memory, flags, key_ptr, key_len)
 	}
 
-	/// Emit a custom debug message.
-	/// See [`pallet_revive_uapi::HostFn::debug_message`].
-	fn debug_message(
-		&mut self,
-		memory: &mut M,
-		str_ptr: u32,
-		str_len: u32,
-	) -> Result<ReturnErrorCode, TrapReason> {
-		let str_len = str_len.min(limits::DEBUG_BUFFER_BYTES);
-		self.charge_gas(RuntimeCosts::DebugMessage(str_len))?;
-		if self.ext.append_debug_buffer("") {
-			let data = memory.read(str_ptr, str_len)?;
-			if let Some(msg) = core::str::from_utf8(&data).ok() {
-				self.ext.append_debug_buffer(msg);
-			}
-			Ok(ReturnErrorCode::Success)
-		} else {
-			Ok(ReturnErrorCode::LoggingDisabled)
-		}
-	}
-
 	/// Recovers the ECDSA public key from the given message hash and signature.
 	/// See [`pallet_revive_uapi::HostFn::ecdsa_recover`].
 	fn ecdsa_recover(
@@ -2162,12 +2138,30 @@ pub mod env {
 				Ok(ReturnErrorCode::Success)
 			},
 			Err(e) => {
-				if self.ext.append_debug_buffer("") {
-					self.ext.append_debug_buffer("seal0::xcm_send failed with: ");
-					self.ext.append_debug_buffer(e.into());
-				};
+				log::debug!(target: LOG_TARGET, "seal0::xcm_send failed with: {e:?}");
 				Ok(ReturnErrorCode::XcmSendFailed)
 			},
 		}
 	}
+
+	/// Retrieves the account id for a specified contract address.
+	///
+	/// See [`pallet_revive_uapi::HostFn::to_account_id`].
+	fn to_account_id(
+		&mut self,
+		memory: &mut M,
+		addr_ptr: u32,
+		out_ptr: u32,
+	) -> Result<(), TrapReason> {
+		self.charge_gas(RuntimeCosts::ToAccountId)?;
+		let address = memory.read_h160(addr_ptr)?;
+		let account_id = self.ext.to_account_id(&address);
+		Ok(self.write_fixed_sandbox_output(
+			memory,
+			out_ptr,
+			&account_id.encode(),
+			false,
+			already_charged,
+		)?)
+	}
 }
diff --git a/substrate/frame/revive/src/weights.rs b/substrate/frame/revive/src/weights.rs
index e35ba5ca0766458427ed0f73927eea6bb4f1cc66..52153d74ca758c429a4982e393b7d211d79ab5b4 100644
--- a/substrate/frame/revive/src/weights.rs
+++ b/substrate/frame/revive/src/weights.rs
@@ -67,6 +67,7 @@ pub trait WeightInfo {
 	fn seal_caller() -> Weight;
 	fn seal_origin() -> Weight;
 	fn seal_is_contract() -> Weight;
+	fn seal_to_account_id() -> Weight;
 	fn seal_code_hash() -> Weight;
 	fn seal_own_code_hash() -> Weight;
 	fn seal_code_size() -> Weight;
@@ -96,7 +97,6 @@ pub trait WeightInfo {
 	fn seal_return(n: u32, ) -> Weight;
 	fn seal_terminate(n: u32, ) -> Weight;
 	fn seal_deposit_event(t: u32, n: u32, ) -> Weight;
-	fn seal_debug_message(i: u32, ) -> Weight;
 	fn get_storage_empty() -> Weight;
 	fn get_storage_full() -> Weight;
 	fn set_storage_empty() -> Weight;
@@ -378,6 +378,16 @@ impl<T: frame_system::Config> WeightInfo for SubstrateWeight<T> {
 		Weight::from_parts(10_336_000, 3771)
 			.saturating_add(T::DbWeight::get().reads(1_u64))
 	}
+	/// Storage: `Revive::AddressSuffix` (r:1 w:0)
+	/// Proof: `Revive::AddressSuffix` (`max_values`: None, `max_size`: Some(32), added: 2507, mode: `Measured`)
+	fn seal_to_account_id() -> Weight {
+		// Proof Size summary in bytes:
+		//  Measured:  `212`
+		//  Estimated: `3677`
+		// Minimum execution time: 4_000_000 picoseconds.
+		Weight::from_parts(4_000_000, 3677)
+			.saturating_add(T::DbWeight::get().reads(1_u64))
+	}
 	/// Storage: `Revive::ContractInfoOf` (r:1 w:0)
 	/// Proof: `Revive::ContractInfoOf` (`max_values`: None, `max_size`: Some(1779), added: 4254, mode: `Measured`)
 	fn seal_code_hash() -> Weight {
@@ -643,16 +653,6 @@ impl<T: frame_system::Config> WeightInfo for SubstrateWeight<T> {
 			// Standard Error: 34
 			.saturating_add(Weight::from_parts(774, 0).saturating_mul(n.into()))
 	}
-	/// The range of component `i` is `[0, 262144]`.
-	fn seal_debug_message(i: u32, ) -> Weight {
-		// Proof Size summary in bytes:
-		//  Measured:  `0`
-		//  Estimated: `0`
-		// Minimum execution time: 340_000 picoseconds.
-		Weight::from_parts(306_527, 0)
-			// Standard Error: 1
-			.saturating_add(Weight::from_parts(728, 0).saturating_mul(i.into()))
-	}
 	/// Storage: `Skipped::Metadata` (r:0 w:0)
 	/// Proof: `Skipped::Metadata` (`max_values`: None, `max_size`: None, mode: `Measured`)
 	fn get_storage_empty() -> Weight {
@@ -1274,6 +1274,16 @@ impl WeightInfo for () {
 		Weight::from_parts(10_336_000, 3771)
 			.saturating_add(RocksDbWeight::get().reads(1_u64))
 	}
+	/// Storage: `Revive::AddressSuffix` (r:1 w:0)
+	/// Proof: `Revive::AddressSuffix` (`max_values`: None, `max_size`: Some(32), added: 2507, mode: `Measured`)
+	fn seal_to_account_id() -> Weight {
+		// Proof Size summary in bytes:
+		//  Measured:  `212`
+		//  Estimated: `3677`
+		// Minimum execution time: 4_000_000 picoseconds.
+		Weight::from_parts(4_000_000, 3677)
+			.saturating_add(RocksDbWeight::get().reads(1_u64))
+	}
 	/// Storage: `Revive::ContractInfoOf` (r:1 w:0)
 	/// Proof: `Revive::ContractInfoOf` (`max_values`: None, `max_size`: Some(1779), added: 4254, mode: `Measured`)
 	fn seal_code_hash() -> Weight {
@@ -1539,16 +1549,6 @@ impl WeightInfo for () {
 			// Standard Error: 34
 			.saturating_add(Weight::from_parts(774, 0).saturating_mul(n.into()))
 	}
-	/// The range of component `i` is `[0, 262144]`.
-	fn seal_debug_message(i: u32, ) -> Weight {
-		// Proof Size summary in bytes:
-		//  Measured:  `0`
-		//  Estimated: `0`
-		// Minimum execution time: 340_000 picoseconds.
-		Weight::from_parts(306_527, 0)
-			// Standard Error: 1
-			.saturating_add(Weight::from_parts(728, 0).saturating_mul(i.into()))
-	}
 	/// Storage: `Skipped::Metadata` (r:0 w:0)
 	/// Proof: `Skipped::Metadata` (`max_values`: None, `max_size`: None, mode: `Measured`)
 	fn get_storage_empty() -> Weight {
diff --git a/substrate/frame/revive/uapi/Cargo.toml b/substrate/frame/revive/uapi/Cargo.toml
index 7241d667fcdc75c4dbd6e2c5f3a6a02efd1835aa..cf006941cfd0a6477d35b9b0d992060105f6f3c1 100644
--- a/substrate/frame/revive/uapi/Cargo.toml
+++ b/substrate/frame/revive/uapi/Cargo.toml
@@ -22,7 +22,7 @@ paste = { workspace = true }
 scale-info = { features = ["derive"], optional = true, workspace = true }
 
 [target.'cfg(target_arch = "riscv64")'.dependencies]
-polkavm-derive = { version = "0.18.0" }
+polkavm-derive = { version = "0.19.0" }
 
 [package.metadata.docs.rs]
 features = ["unstable-hostfn"]
diff --git a/substrate/frame/revive/uapi/src/host.rs b/substrate/frame/revive/uapi/src/host.rs
index ba0a63b15c379d45a4add50cfc6e0ec6beb14e2b..3e5cf0eb0c243d44c19368bd9cf7305fb418b829 100644
--- a/substrate/frame/revive/uapi/src/host.rs
+++ b/substrate/frame/revive/uapi/src/host.rs
@@ -144,6 +144,18 @@ pub trait HostFn: private::Sealed {
 	/// - `output`: A reference to the output data buffer to write the origin's address.
 	fn origin(output: &mut [u8; 20]);
 
+	/// Retrieve the account id for a specified address.
+	///
+	/// # Parameters
+	///
+	/// - `addr`: A `H160` address.
+	/// - `output`: A reference to the output data buffer to write the account id.
+	///
+	/// # Note
+	///
+	/// If no mapping exists for `addr`, the fallback account id will be returned.
+	fn to_account_id(addr: &[u8; 20], output: &mut [u8]);
+
 	/// Retrieve the code hash for a specified contract address.
 	///
 	/// # Parameters
@@ -515,26 +527,6 @@ pub trait HostFn: private::Sealed {
 	#[unstable_hostfn]
 	fn contains_storage(flags: StorageFlags, key: &[u8]) -> Option<u32>;
 
-	/// Emit a custom debug message.
-	///
-	/// No newlines are added to the supplied message.
-	/// Specifying invalid UTF-8 just drops the message with no trap.
-	///
-	/// This is a no-op if debug message recording is disabled which is always the case
-	/// when the code is executing on-chain. The message is interpreted as UTF-8 and
-	/// appended to the debug buffer which is then supplied to the calling RPC client.
-	///
-	/// # Note
-	///
-	/// Even though no action is taken when debug message recording is disabled there is still
-	/// a non trivial overhead (and weight cost) associated with calling this function. Contract
-	/// languages should remove calls to this function (either at runtime or compile time) when
-	/// not being executed as an RPC. For example, they could allow users to disable logging
-	/// through compile time flags (cargo features) for on-chain deployment. Additionally, the
-	/// return value of this function can be cached in order to prevent further calls at runtime.
-	#[unstable_hostfn]
-	fn debug_message(str: &[u8]) -> Result;
-
 	/// Recovers the ECDSA public key from the given message hash and signature.
 	///
 	/// Writes the public key into the given output buffer.
diff --git a/substrate/frame/revive/uapi/src/host/riscv64.rs b/substrate/frame/revive/uapi/src/host/riscv64.rs
index 8c40bc9f48ea802b6d46bc0d1aaa858a991ec136..3726564e26ebaea2982912e63ad08924774790ed 100644
--- a/substrate/frame/revive/uapi/src/host/riscv64.rs
+++ b/substrate/frame/revive/uapi/src/host/riscv64.rs
@@ -69,6 +69,7 @@ mod sys {
 		pub fn caller(out_ptr: *mut u8);
 		pub fn origin(out_ptr: *mut u8);
 		pub fn is_contract(account_ptr: *const u8) -> ReturnCode;
+		pub fn to_account_id(address_ptr: *const u8, out_ptr: *mut u8);
 		pub fn code_hash(address_ptr: *const u8, out_ptr: *mut u8);
 		pub fn code_size(address_ptr: *const u8) -> u64;
 		pub fn own_code_hash(out_ptr: *mut u8);
@@ -109,7 +110,6 @@ mod sys {
 			out_ptr: *mut u8,
 			out_len_ptr: *mut u32,
 		) -> ReturnCode;
-		pub fn debug_message(str_ptr: *const u8, str_len: u32) -> ReturnCode;
 		pub fn call_runtime(call_ptr: *const u8, call_len: u32) -> ReturnCode;
 		pub fn ecdsa_recover(
 			signature_ptr: *const u8,
@@ -457,6 +457,11 @@ impl HostFn for HostFnImpl {
 		unsafe { sys::ref_time_left() }
 	}
 
+	#[unstable_hostfn]
+	fn to_account_id(address: &[u8; 20], output: &mut [u8]) {
+		unsafe { sys::to_account_id(address.as_ptr(), output.as_mut_ptr()) }
+	}
+
 	#[unstable_hostfn]
 	fn block_hash(block_number_ptr: &[u8; 32], output: &mut [u8; 32]) {
 		unsafe { sys::block_hash(block_number_ptr.as_ptr(), output.as_mut_ptr()) };
@@ -519,12 +524,6 @@ impl HostFn for HostFnImpl {
 		ret_code.into()
 	}
 
-	#[unstable_hostfn]
-	fn debug_message(str: &[u8]) -> Result {
-		let ret_code = unsafe { sys::debug_message(str.as_ptr(), str.len() as u32) };
-		ret_code.into()
-	}
-
 	#[unstable_hostfn]
 	fn ecdsa_recover(
 		signature: &[u8; 65],
diff --git a/substrate/frame/revive/uapi/src/lib.rs b/substrate/frame/revive/uapi/src/lib.rs
index ef1798b4bf615a7e50054954546c963094746042..867f356339876a433f01248780c9cdab6c8ebb7e 100644
--- a/substrate/frame/revive/uapi/src/lib.rs
+++ b/substrate/frame/revive/uapi/src/lib.rs
@@ -86,9 +86,8 @@ define_error_codes! {
 	/// Transfer failed for other not further specified reason. Most probably
 	/// reserved or locked balance of the sender that was preventing the transfer.
 	TransferFailed = 4,
-	/// The call to `debug_message` had no effect because debug message
-	/// recording was disabled.
-	LoggingDisabled = 5,
+	/// The subcall ran out of weight or storage deposit.
+	OutOfResources = 5,
 	/// The call dispatched by `call_runtime` was executed but returned an error.
 	CallRuntimeFailed = 6,
 	/// ECDSA public key recovery failed. Most probably wrong recovery id or signature.
@@ -99,8 +98,6 @@ define_error_codes! {
 	XcmExecutionFailed = 9,
 	/// The `xcm_send` call failed.
 	XcmSendFailed = 10,
-	/// The subcall ran out of weight or storage deposit.
-	OutOfResources = 11,
 }
 
 /// The raw return code returned by the host side.
diff --git a/substrate/frame/root-offences/src/mock.rs b/substrate/frame/root-offences/src/mock.rs
index 7a96b8eade4e13f290020ffc0f0a12fa5f72600d..3f14dc00b56068f8be244d24e4cb011748c0aa7f 100644
--- a/substrate/frame/root-offences/src/mock.rs
+++ b/substrate/frame/root-offences/src/mock.rs
@@ -126,6 +126,7 @@ parameter_types! {
 
 #[derive_impl(pallet_staking::config_preludes::TestDefaultConfig)]
 impl pallet_staking::Config for Test {
+	type OldCurrency = Balances;
 	type Currency = Balances;
 	type CurrencyBalance = <Self as pallet_balances::Config>::Balance;
 	type UnixTime = Timestamp;
@@ -206,10 +207,10 @@ impl ExtBuilder {
 				(30, self.balance_factor * 50),
 				(40, self.balance_factor * 50),
 				// stashes
-				(11, self.balance_factor * 1000),
-				(21, self.balance_factor * 1000),
-				(31, self.balance_factor * 500),
-				(41, self.balance_factor * 1000),
+				(11, self.balance_factor * 1500),
+				(21, self.balance_factor * 1500),
+				(31, self.balance_factor * 1000),
+				(41, self.balance_factor * 2000),
 			],
 		}
 		.assimilate_storage(&mut storage)
diff --git a/substrate/frame/salary/Cargo.toml b/substrate/frame/salary/Cargo.toml
index 626993a0547b58bee5a125edcec1ffce9ed5b779..84c55b110c8c2b3ebc1b948791311e551094dadf 100644
--- a/substrate/frame/salary/Cargo.toml
+++ b/substrate/frame/salary/Cargo.toml
@@ -17,7 +17,7 @@ targets = ["x86_64-unknown-linux-gnu"]
 
 [dependencies]
 codec = { features = ["derive"], workspace = true }
-frame = { workspace = true, features = ["experimental", "runtime"] }
+frame = { workspace = true, features = ["runtime"] }
 log = { workspace = true }
 pallet-ranked-collective = { optional = true, workspace = true }
 scale-info = { features = ["derive"], workspace = true }
diff --git a/substrate/frame/session/benchmarking/src/mock.rs b/substrate/frame/session/benchmarking/src/mock.rs
index 346cd04c0fa9eb5f917557aa050e759762f9a341..74201da3d2f310f58593d8566eb650086245c76f 100644
--- a/substrate/frame/session/benchmarking/src/mock.rs
+++ b/substrate/frame/session/benchmarking/src/mock.rs
@@ -133,6 +133,7 @@ impl onchain::Config for OnChainSeqPhragmen {
 
 #[derive_impl(pallet_staking::config_preludes::TestDefaultConfig)]
 impl pallet_staking::Config for Test {
+	type OldCurrency = Balances;
 	type Currency = Balances;
 	type CurrencyBalance = <Self as pallet_balances::Config>::Balance;
 	type UnixTime = pallet_timestamp::Pallet<Self>;
diff --git a/substrate/frame/src/lib.rs b/substrate/frame/src/lib.rs
index f23d9cbffa9170b7a4ab3475f28c0a709740e8e2..00605cfe565c244d041d703bf9f5bb80272a5443 100644
--- a/substrate/frame/src/lib.rs
+++ b/substrate/frame/src/lib.rs
@@ -106,7 +106,7 @@
 //! [dependencies]
 //! codec = { features = ["max-encoded-len"], workspace = true }
 //! scale-info = { features = ["derive"], workspace = true }
-//! frame = { workspace = true, features = ["experimental", "runtime"] }
+//! frame = { workspace = true, features = ["runtime"] }
 //!
 //! [features]
 //! default = ["std"]
@@ -150,7 +150,6 @@
 //! * `runtime::apis` should expose all common runtime APIs that all FRAME-based runtimes need.
 
 #![cfg_attr(not(feature = "std"), no_std)]
-#![cfg(feature = "experimental")]
 
 #[doc(no_inline)]
 pub use frame_support::pallet;
diff --git a/substrate/frame/staking/Cargo.toml b/substrate/frame/staking/Cargo.toml
index 22176b6d720b201583905f59780c19aa7c33d2f4..74b1c78e9cbee9661978ee2111ec9d3410a7f56c 100644
--- a/substrate/frame/staking/Cargo.toml
+++ b/substrate/frame/staking/Cargo.toml
@@ -41,6 +41,7 @@ rand_chacha = { optional = true, workspace = true }
 [dev-dependencies]
 frame-benchmarking = { workspace = true, default-features = true }
 frame-election-provider-support = { workspace = true, default-features = true }
+frame-support = { features = ["experimental"], workspace = true, default-features = true }
 pallet-bags-list = { workspace = true, default-features = true }
 pallet-balances = { workspace = true, default-features = true }
 pallet-staking-reward-curve = { workspace = true, default-features = true }
diff --git a/substrate/frame/staking/src/asset.rs b/substrate/frame/staking/src/asset.rs
index 23368b1f8fca713f78405165c00ec09d54a8d257..a1140d317c204957e5f7cc9178f704cffe5965c1 100644
--- a/substrate/frame/staking/src/asset.rs
+++ b/substrate/frame/staking/src/asset.rs
@@ -18,9 +18,15 @@
 //! Contains all the interactions with [`Config::Currency`] to manipulate the underlying staking
 //! asset.
 
-use frame_support::traits::{Currency, InspectLockableCurrency, LockableCurrency};
-
-use crate::{BalanceOf, Config, NegativeImbalanceOf, PositiveImbalanceOf};
+use crate::{BalanceOf, Config, HoldReason, NegativeImbalanceOf, PositiveImbalanceOf};
+use frame_support::traits::{
+	fungible::{
+		hold::{Balanced as FunHoldBalanced, Inspect as FunHoldInspect, Mutate as FunHoldMutate},
+		Balanced, Inspect as FunInspect,
+	},
+	tokens::{Fortitude, Precision, Preservation},
+};
+use sp_runtime::{DispatchResult, Saturating};
 
 /// Existential deposit for the chain.
 pub fn existential_deposit<T: Config>() -> BalanceOf<T> {
@@ -32,7 +38,7 @@ pub fn total_issuance<T: Config>() -> BalanceOf<T> {
 	T::Currency::total_issuance()
 }
 
-/// Total balance of `who`. Includes both, free and reserved.
+/// Total balance of `who`. Includes both free and staked.
 pub fn total_balance<T: Config>(who: &T::AccountId) -> BalanceOf<T> {
 	T::Currency::total_balance(who)
 }
@@ -41,42 +47,65 @@ pub fn total_balance<T: Config>(who: &T::AccountId) -> BalanceOf<T> {
 ///
 /// This includes balance free to stake along with any balance that is already staked.
 pub fn stakeable_balance<T: Config>(who: &T::AccountId) -> BalanceOf<T> {
-	T::Currency::free_balance(who)
+	free_to_stake::<T>(who).saturating_add(staked::<T>(who))
 }
 
 /// Balance of `who` that is currently at stake.
 ///
-/// The staked amount is locked and cannot be transferred out of `who`s account.
+/// The staked amount is on hold and cannot be transferred out of `who`s account.
 pub fn staked<T: Config>(who: &T::AccountId) -> BalanceOf<T> {
-	T::Currency::balance_locked(crate::STAKING_ID, who)
+	T::Currency::balance_on_hold(&HoldReason::Staking.into(), who)
+}
+
+/// Balance of who that can be staked additionally.
+///
+/// Does not include the current stake.
+pub fn free_to_stake<T: Config>(who: &T::AccountId) -> BalanceOf<T> {
+	// since we want to be able to use frozen funds for staking, we force the reduction.
+	T::Currency::reducible_balance(who, Preservation::Preserve, Fortitude::Force)
 }
 
 /// Set balance that can be staked for `who`.
 ///
-/// This includes any balance that is already staked.
+/// If `Value` is lower than the current staked balance, the difference is unlocked.
+///
+/// Should only be used with test.
 #[cfg(any(test, feature = "runtime-benchmarks"))]
 pub fn set_stakeable_balance<T: Config>(who: &T::AccountId, value: BalanceOf<T>) {
-	T::Currency::make_free_balance_be(who, value);
+	use frame_support::traits::fungible::Mutate;
+
+	// minimum free balance (non-staked) required to keep the account alive.
+	let ed = existential_deposit::<T>();
+	// currently on stake
+	let staked_balance = staked::<T>(who);
+
+	// if new value is greater than staked balance, mint some free balance.
+	if value > staked_balance {
+		let _ = T::Currency::set_balance(who, value - staked_balance + ed);
+	} else {
+		// else reduce the staked balance.
+		update_stake::<T>(who, value).expect("can remove from what is staked");
+		// burn all free, only leaving ED.
+		let _ = T::Currency::set_balance(who, ed);
+	}
+
+	// ensure new stakeable balance same as desired `value`.
+	assert_eq!(stakeable_balance::<T>(who), value);
 }
 
 /// Update `amount` at stake for `who`.
 ///
 /// Overwrites the existing stake amount. If passed amount is lower than the existing stake, the
 /// difference is unlocked.
-pub fn update_stake<T: Config>(who: &T::AccountId, amount: BalanceOf<T>) {
-	T::Currency::set_lock(
-		crate::STAKING_ID,
-		who,
-		amount,
-		frame_support::traits::WithdrawReasons::all(),
-	);
+pub fn update_stake<T: Config>(who: &T::AccountId, amount: BalanceOf<T>) -> DispatchResult {
+	T::Currency::set_on_hold(&HoldReason::Staking.into(), who, amount)
 }
 
-/// Kill the stake of `who`.
+/// Release all staked amount to `who`.
 ///
-/// All locked amount is unlocked.
-pub fn kill_stake<T: Config>(who: &T::AccountId) {
-	T::Currency::remove_lock(crate::STAKING_ID, who);
+/// Fails if there are consumers left on `who` that restricts it from being reaped.
+pub fn kill_stake<T: Config>(who: &T::AccountId) -> DispatchResult {
+	T::Currency::release_all(&HoldReason::Staking.into(), who, Precision::BestEffort).map(|_| ())
 }
 
 /// Slash the value from `who`.
@@ -86,29 +115,32 @@ pub fn slash<T: Config>(
 	who: &T::AccountId,
 	value: BalanceOf<T>,
 ) -> (NegativeImbalanceOf<T>, BalanceOf<T>) {
-	T::Currency::slash(who, value)
+	T::Currency::slash(&HoldReason::Staking.into(), who, value)
 }
 
 /// Mint `value` into an existing account `who`.
 ///
 /// This does not increase the total issuance.
-pub fn mint_existing<T: Config>(
+pub fn mint_into_existing<T: Config>(
 	who: &T::AccountId,
 	value: BalanceOf<T>,
 ) -> Option<PositiveImbalanceOf<T>> {
-	T::Currency::deposit_into_existing(who, value).ok()
+	// since the account already exists, we mint exact value even if value is below ED.
+	T::Currency::deposit(who, value, Precision::Exact).ok()
 }
 
-/// Mint reward and create account for `who` if it does not exist.
+/// Mint `value` and create account for `who` if it does not exist.
 ///
-/// This does not increase the total issuance.
+/// If value is below existential deposit, the account is not created.
+///
+/// Note: This does not increase the total issuance.
 pub fn mint_creating<T: Config>(who: &T::AccountId, value: BalanceOf<T>) -> PositiveImbalanceOf<T> {
-	T::Currency::deposit_creating(who, value)
+	T::Currency::deposit(who, value, Precision::BestEffort).unwrap_or_default()
 }
 
 /// Deposit newly issued or slashed `value` into `who`.
 pub fn deposit_slashed<T: Config>(who: &T::AccountId, value: NegativeImbalanceOf<T>) {
-	T::Currency::resolve_creating(who, value)
+	let _ = T::Currency::resolve(who, value);
 }
 
 /// Issue `value` increasing total issuance.
@@ -121,5 +153,5 @@ pub fn issue<T: Config>(value: BalanceOf<T>) -> NegativeImbalanceOf<T> {
 /// Burn the amount from the total issuance.
 #[cfg(feature = "runtime-benchmarks")]
 pub fn burn<T: Config>(amount: BalanceOf<T>) -> PositiveImbalanceOf<T> {
-	T::Currency::burn(amount)
+	T::Currency::rescind(amount)
 }
diff --git a/substrate/frame/staking/src/benchmarking.rs b/substrate/frame/staking/src/benchmarking.rs
index 79d8dd3fbc30365e4a81a2f708383754f67a49cc..59d272168d68bc06f73e4e85ce7fc5a407ca3daf 100644
--- a/substrate/frame/staking/src/benchmarking.rs
+++ b/substrate/frame/staking/src/benchmarking.rs
@@ -257,7 +257,11 @@ mod benchmarks {
 			.map(|l| l.active)
 			.ok_or("ledger not created after")?;
 
-		let _ = asset::mint_existing::<T>(&stash, max_additional).unwrap();
+		let _ = asset::mint_into_existing::<T>(
+			&stash,
+			max_additional + asset::existential_deposit::<T>(),
+		)
+		.unwrap();
 
 		whitelist_account!(stash);
 
@@ -1133,6 +1137,23 @@ mod benchmarks {
 		Ok(())
 	}
 
+	#[benchmark]
+	fn migrate_currency() -> Result<(), BenchmarkError> {
+		let (stash, _ctrl) =
+			create_stash_controller::<T>(USER_SEED, 100, RewardDestination::Staked)?;
+		let stake = asset::staked::<T>(&stash);
+		migrate_to_old_currency::<T>(stash.clone());
+		// no holds
+		assert!(asset::staked::<T>(&stash).is_zero());
+		whitelist_account!(stash);
+
+		#[extrinsic_call]
+		_(RawOrigin::Signed(stash.clone()), stash.clone());
+
+		assert_eq!(asset::staked::<T>(&stash), stake);
+		Ok(())
+	}
+
 	impl_benchmark_test_suite!(
 		Staking,
 		crate::mock::ExtBuilder::default().has_stakers(true),
diff --git a/substrate/frame/staking/src/ledger.rs b/substrate/frame/staking/src/ledger.rs
index ac3be04cf607176d6aee437a7b49bc3f4afffd55..1d66ebd27e9f729e8a42a93960435ccf78b79769 100644
--- a/substrate/frame/staking/src/ledger.rs
+++ b/substrate/frame/staking/src/ledger.rs
@@ -32,6 +32,7 @@
 //! state consistency.
 
 use frame_support::{defensive, ensure, traits::Defensive};
+use sp_runtime::DispatchResult;
 use sp_staking::{StakingAccount, StakingInterface};
 
 use crate::{
@@ -187,7 +188,8 @@ impl<T: Config> StakingLedger<T> {
 		// We skip locking virtual stakers.
 		if !Pallet::<T>::is_virtual_staker(&self.stash) {
 			// for direct stakers, update lock on stash based on ledger.
-			asset::update_stake::<T>(&self.stash, self.total);
+			asset::update_stake::<T>(&self.stash, self.total)
+				.map_err(|_| Error::<T>::NotEnoughFunds)?;
 		}
 
 		Ledger::<T>::insert(
@@ -250,7 +252,7 @@ impl<T: Config> StakingLedger<T> {
 
 	/// Clears all data related to a staking ledger and its bond in both [`Ledger`] and [`Bonded`]
 	/// storage items and updates the stash staking lock.
-	pub(crate) fn kill(stash: &T::AccountId) -> Result<(), Error<T>> {
+	pub(crate) fn kill(stash: &T::AccountId) -> DispatchResult {
 		let controller = <Bonded<T>>::get(stash).ok_or(Error::<T>::NotStash)?;
 
 		<Ledger<T>>::get(&controller).ok_or(Error::<T>::NotController).map(|ledger| {
@@ -259,9 +261,9 @@ impl<T: Config> StakingLedger<T> {
 			<Payee<T>>::remove(&stash);
 
 			// kill virtual staker if it exists.
-			if <VirtualStakers<T>>::take(&stash).is_none() {
+			if <VirtualStakers<T>>::take(&ledger.stash).is_none() {
 				// if not virtual staker, clear locks.
-				asset::kill_stake::<T>(&ledger.stash);
+				asset::kill_stake::<T>(&ledger.stash)?;
 			}
 
 			Ok(())
diff --git a/substrate/frame/staking/src/lib.rs b/substrate/frame/staking/src/lib.rs
index 6361663b2b1c05939ba24f572c171c00af2f77e9..42230cb27b756306446cf23cea536a054b11e119 100644
--- a/substrate/frame/staking/src/lib.rs
+++ b/substrate/frame/staking/src/lib.rs
@@ -312,7 +312,8 @@ use codec::{Decode, Encode, HasCompact, MaxEncodedLen};
 use frame_support::{
 	defensive, defensive_assert,
 	traits::{
-		ConstU32, Currency, Defensive, DefensiveMax, DefensiveSaturating, Get, LockIdentifier,
+		tokens::fungible::{Credit, Debt},
+		ConstU32, Defensive, DefensiveMax, DefensiveSaturating, Get, LockIdentifier,
 	},
 	weights::Weight,
 	BoundedVec, CloneNoBound, EqNoBound, PartialEqNoBound, RuntimeDebugNoBound,
@@ -361,12 +362,9 @@ pub type RewardPoint = u32;
 /// The balance type of this pallet.
 pub type BalanceOf<T> = <T as Config>::CurrencyBalance;
 
-type PositiveImbalanceOf<T> = <<T as Config>::Currency as Currency<
-	<T as frame_system::Config>::AccountId,
->>::PositiveImbalance;
-pub type NegativeImbalanceOf<T> = <<T as Config>::Currency as Currency<
-	<T as frame_system::Config>::AccountId,
->>::NegativeImbalance;
+type PositiveImbalanceOf<T> = Debt<<T as frame_system::Config>::AccountId, <T as Config>::Currency>;
+pub type NegativeImbalanceOf<T> =
+	Credit<<T as frame_system::Config>::AccountId, <T as Config>::Currency>;
 
 type AccountIdLookupOf<T> = <<T as frame_system::Config>::Lookup as StaticLookup>::Source;
 
diff --git a/substrate/frame/staking/src/mock.rs b/substrate/frame/staking/src/mock.rs
index 769b84826b41613f27873fed650f47999806441f..6346949576fa796726b136822737d6bd5c9fb3e4 100644
--- a/substrate/frame/staking/src/mock.rs
+++ b/substrate/frame/staking/src/mock.rs
@@ -25,8 +25,7 @@ use frame_election_provider_support::{
 use frame_support::{
 	assert_ok, derive_impl, ord_parameter_types, parameter_types,
 	traits::{
-		ConstU64, Currency, EitherOfDiverse, FindAuthor, Get, Imbalance, LockableCurrency,
-		OnUnbalanced, OneSessionHandler, WithdrawReasons,
+		ConstU64, EitherOfDiverse, FindAuthor, Get, Imbalance, OnUnbalanced, OneSessionHandler,
 	},
 	weights::constants::RocksDbWeight,
 };
@@ -264,6 +263,7 @@ pub(crate) const DISABLING_LIMIT_FACTOR: usize = 3;
 
 #[derive_impl(crate::config_preludes::TestDefaultConfig)]
 impl crate::pallet::pallet::Config for Test {
+	type OldCurrency = Balances;
 	type Currency = Balances;
 	type UnixTime = Timestamp;
 	type RewardRemainder = RewardRemainderMock;
@@ -432,6 +432,7 @@ impl ExtBuilder {
 	fn build(self) -> sp_io::TestExternalities {
 		sp_tracing::try_init_simple();
 		let mut storage = frame_system::GenesisConfig::<Test>::default().build_storage().unwrap();
+		let ed = ExistentialDeposit::get();
 
 		let _ = pallet_balances::GenesisConfig::<Test> {
 			balances: vec![
@@ -446,19 +447,23 @@ impl ExtBuilder {
 				(40, self.balance_factor),
 				(50, self.balance_factor),
 				// stashes
-				(11, self.balance_factor * 1000),
-				(21, self.balance_factor * 2000),
-				(31, self.balance_factor * 2000),
-				(41, self.balance_factor * 2000),
-				(51, self.balance_factor * 2000),
-				(201, self.balance_factor * 2000),
-				(202, self.balance_factor * 2000),
+				// Note: Previously this pallet used locks and stakers could stake all their
+				// balance including ED. Now with holds, stakers are required to maintain
+				// (non-staked) ED in their accounts. Therefore, we drop an additional existential
+				// deposit to genesis stakers.
+				(11, self.balance_factor * 1000 + ed),
+				(21, self.balance_factor * 2000 + ed),
+				(31, self.balance_factor * 2000 + ed),
+				(41, self.balance_factor * 2000 + ed),
+				(51, self.balance_factor * 2000 + ed),
+				(201, self.balance_factor * 2000 + ed),
+				(202, self.balance_factor * 2000 + ed),
 				// optional nominator
-				(100, self.balance_factor * 2000),
-				(101, self.balance_factor * 2000),
+				(100, self.balance_factor * 2000 + ed),
+				(101, self.balance_factor * 2000 + ed),
 				// aux accounts
 				(60, self.balance_factor),
-				(61, self.balance_factor * 2000),
+				(61, self.balance_factor * 2000 + ed),
 				(70, self.balance_factor),
 				(71, self.balance_factor * 2000),
 				(80, self.balance_factor),
@@ -575,7 +580,7 @@ pub(crate) fn current_era() -> EraIndex {
 }
 
 pub(crate) fn bond(who: AccountId, val: Balance) {
-	let _ = Balances::make_free_balance_be(&who, val);
+	let _ = asset::set_stakeable_balance::<Test>(&who, val);
 	assert_ok!(Staking::bond(RuntimeOrigin::signed(who), val, RewardDestination::Stash));
 }
 
@@ -600,10 +605,6 @@ pub(crate) fn bond_virtual_nominator(
 	val: Balance,
 	target: Vec<AccountId>,
 ) {
-	// In a real scenario, `who` is a keyless account managed by another pallet which provides for
-	// it.
-	System::inc_providers(&who);
-
 	// Bond who virtually.
 	assert_ok!(<Staking as sp_staking::StakingUnchecked>::virtual_bond(&who, val, &payee));
 	assert_ok!(Staking::nominate(RuntimeOrigin::signed(who), target));
@@ -809,7 +810,7 @@ pub(crate) fn bond_extra_no_checks(stash: &AccountId, amount: Balance) {
 	let mut ledger = Ledger::<Test>::get(&controller).expect("ledger must exist to bond_extra");
 
 	let new_total = ledger.total + amount;
-	Balances::set_lock(crate::STAKING_ID, stash, new_total, WithdrawReasons::all());
+	let _ = asset::update_stake::<Test>(stash, new_total);
 	ledger.total = new_total;
 	ledger.active = new_total;
 	Ledger::<Test>::insert(controller, ledger);
@@ -818,10 +819,10 @@ pub(crate) fn bond_extra_no_checks(stash: &AccountId, amount: Balance) {
 pub(crate) fn setup_double_bonded_ledgers() {
 	let init_ledgers = Ledger::<Test>::iter().count();
 
-	let _ = Balances::make_free_balance_be(&333, 2000);
-	let _ = Balances::make_free_balance_be(&444, 2000);
-	let _ = Balances::make_free_balance_be(&555, 2000);
-	let _ = Balances::make_free_balance_be(&777, 2000);
+	let _ = asset::set_stakeable_balance::<Test>(&333, 2000);
+	let _ = asset::set_stakeable_balance::<Test>(&444, 2000);
+	let _ = asset::set_stakeable_balance::<Test>(&555, 2000);
+	let _ = asset::set_stakeable_balance::<Test>(&777, 2000);
 
 	assert_ok!(Staking::bond(RuntimeOrigin::signed(333), 10, RewardDestination::Staked));
 	assert_ok!(Staking::bond(RuntimeOrigin::signed(444), 20, RewardDestination::Staked));
@@ -923,5 +924,5 @@ pub(crate) fn staking_events_since_last_call() -> Vec<crate::Event<Test>> {
 }
 
 pub(crate) fn balances(who: &AccountId) -> (Balance, Balance) {
-	(Balances::free_balance(who), Balances::reserved_balance(who))
+	(asset::stakeable_balance::<Test>(who), Balances::reserved_balance(who))
 }
diff --git a/substrate/frame/staking/src/pallet/impls.rs b/substrate/frame/staking/src/pallet/impls.rs
index 2ae925d036435976c2f58d49e27f81dad1bfc1d1..8c3ff23315a42d94803f2324cebf4c4930951fe0 100644
--- a/substrate/frame/staking/src/pallet/impls.rs
+++ b/substrate/frame/staking/src/pallet/impls.rs
@@ -27,8 +27,8 @@ use frame_support::{
 	dispatch::WithPostDispatchInfo,
 	pallet_prelude::*,
 	traits::{
-		Defensive, DefensiveSaturating, EstimateNextNewSession, Get, Imbalance, Len, OnUnbalanced,
-		TryCollect, UnixTime,
+		Defensive, DefensiveSaturating, EstimateNextNewSession, Get, Imbalance,
+		InspectLockableCurrency, Len, LockableCurrency, OnUnbalanced, TryCollect, UnixTime,
 	},
 	weights::Weight,
 };
@@ -36,10 +36,9 @@ use frame_system::{pallet_prelude::BlockNumberFor, RawOrigin};
 use pallet_session::historical;
 use sp_runtime::{
 	traits::{
-		Bounded, CheckedAdd, CheckedSub, Convert, One, SaturatedConversion, Saturating,
-		StaticLookup, Zero,
+		Bounded, CheckedAdd, Convert, One, SaturatedConversion, Saturating, StaticLookup, Zero,
 	},
-	ArithmeticError, Perbill, Percent,
+	ArithmeticError, DispatchResult, Perbill, Percent,
 };
 use sp_staking::{
 	currency_to_vote::CurrencyToVote,
@@ -54,6 +53,7 @@ use crate::{
 	BalanceOf, EraInfo, EraPayout, Exposure, ExposureOf, Forcing, IndividualExposure,
 	LedgerIntegrityState, MaxNominationsOf, MaxWinnersOf, Nominations, NominationsQuota,
 	PositiveImbalanceOf, RewardDestination, SessionInterface, StakingLedger, ValidatorPrefs,
+	STAKING_ID,
 };
 use alloc::{boxed::Box, vec, vec::Vec};
 
@@ -96,10 +96,12 @@ impl<T: Config> Pallet<T> {
 	pub(crate) fn inspect_bond_state(
 		stash: &T::AccountId,
 	) -> Result<LedgerIntegrityState, Error<T>> {
-		let lock = asset::staked::<T>(&stash);
+		// look at any old unmigrated lock as well.
+		let hold_or_lock = asset::staked::<T>(&stash)
+			.max(T::OldCurrency::balance_locked(STAKING_ID, &stash).into());
 
 		let controller = <Bonded<T>>::get(stash).ok_or_else(|| {
-			if lock == Zero::zero() {
+			if hold_or_lock == Zero::zero() {
 				Error::<T>::NotStash
 			} else {
 				Error::<T>::BadState
@@ -111,7 +113,7 @@ impl<T: Config> Pallet<T> {
 				if ledger.stash != *stash {
 					Ok(LedgerIntegrityState::Corrupted)
 				} else {
-					if lock != ledger.total {
+					if hold_or_lock != ledger.total {
 						Ok(LedgerIntegrityState::LockCorrupted)
 					} else {
 						Ok(LedgerIntegrityState::Ok)
@@ -163,11 +165,7 @@ impl<T: Config> Pallet<T> {
 			additional
 		} else {
 			// additional amount or actual balance of stash whichever is lower.
-			additional.min(
-				asset::stakeable_balance::<T>(stash)
-					.checked_sub(&ledger.total)
-					.ok_or(ArithmeticError::Overflow)?,
-			)
+			additional.min(asset::free_to_stake::<T>(stash))
 		};
 
 		ledger.total = ledger.total.checked_add(&extra).ok_or(ArithmeticError::Overflow)?;
@@ -416,12 +414,12 @@ impl<T: Config> Pallet<T> {
 		let dest = Self::payee(StakingAccount::Stash(stash.clone()))?;
 
 		let maybe_imbalance = match dest {
-			RewardDestination::Stash => asset::mint_existing::<T>(stash, amount),
+			RewardDestination::Stash => asset::mint_into_existing::<T>(stash, amount),
 			RewardDestination::Staked => Self::ledger(Stash(stash.clone()))
 				.and_then(|mut ledger| {
 					ledger.active += amount;
 					ledger.total += amount;
-					let r = asset::mint_existing::<T>(stash, amount);
+					let r = asset::mint_into_existing::<T>(stash, amount);
 
 					let _ = ledger
 						.update()
@@ -799,8 +797,6 @@ impl<T: Config> Pallet<T> {
 		Self::do_remove_validator(&stash);
 		Self::do_remove_nominator(&stash);
 
-		frame_system::Pallet::<T>::dec_consumers(&stash);
-
 		Ok(())
 	}
 
@@ -1163,6 +1159,81 @@ impl<T: Config> Pallet<T> {
 	) -> Exposure<T::AccountId, BalanceOf<T>> {
 		EraInfo::<T>::get_full_exposure(era, account)
 	}
+
+	pub(super) fn do_migrate_currency(stash: &T::AccountId) -> DispatchResult {
+		if Self::is_virtual_staker(stash) {
+			return Self::do_migrate_virtual_staker(stash);
+		}
+
+		let ledger = Self::ledger(Stash(stash.clone()))?;
+		let staked: BalanceOf<T> = T::OldCurrency::balance_locked(STAKING_ID, stash).into();
+		ensure!(!staked.is_zero(), Error::<T>::AlreadyMigrated);
+		ensure!(ledger.total == staked, Error::<T>::BadState);
+
+		// remove old staking lock
+		T::OldCurrency::remove_lock(STAKING_ID, &stash);
+
+		// check if we can hold all stake.
+		let max_hold = asset::free_to_stake::<T>(&stash);
+		let force_withdraw = if max_hold >= staked {
+			// this means we can hold all stake. yay!
+			asset::update_stake::<T>(&stash, staked)?;
+			Zero::zero()
+		} else {
+			// if we are here, it means we cannot hold all user stake. We will do a force withdraw
+			// from ledger, but that's okay since anyways user do not have funds for it.
+			let force_withdraw = staked.saturating_sub(max_hold);
+
+			// we ignore if active is 0. It implies the locked amount is not actively staked. The
+			// account can still get away from potential slash but we can't do much better here.
+			StakingLedger {
+				total: max_hold,
+				active: ledger.active.saturating_sub(force_withdraw),
+				// we are not changing the stash, so we can keep the stash.
+				..ledger
+			}
+			.update()?;
+			force_withdraw
+		};
+
+		// Get rid of the extra consumer we used to have with OldCurrency.
+		frame_system::Pallet::<T>::dec_consumers(&stash);
+
+		Self::deposit_event(Event::<T>::CurrencyMigrated { stash: stash.clone(), force_withdraw });
+		Ok(())
+	}
+
+	fn do_migrate_virtual_staker(stash: &T::AccountId) -> DispatchResult {
+		// Funds for virtual stakers not managed/held by this pallet. We only need to clear
+		// the extra consumer we used to have with OldCurrency.
+		frame_system::Pallet::<T>::dec_consumers(&stash);
+
+		// The delegation system that manages the virtual staker needed to increment provider
+		// previously because of the consumer needed by this pallet. In reality, this stash
+		// is just a key for managing the ledger and the account does not need to hold any
+		// balance or exist. We decrement this provider.
+		let actual_providers = frame_system::Pallet::<T>::providers(stash);
+
+		let expected_providers =
+			// provider is expected to be 1 but someone can always transfer some free funds to
+			// these accounts, increasing the provider.
+			if asset::free_to_stake::<T>(&stash) >= asset::existential_deposit::<T>() {
+				2
+			} else {
+				1
+			};
+
+		// We should never have more than expected providers.
+		ensure!(actual_providers <= expected_providers, Error::<T>::BadState);
+
+		// if actual provider is less than expected, it is already migrated.
+		ensure!(actual_providers == expected_providers, Error::<T>::AlreadyMigrated);
+
+		// dec provider
+		let _ = frame_system::Pallet::<T>::dec_providers(&stash)?;
+
+		return Ok(())
+	}
 }
 
 impl<T: Config> Pallet<T> {
@@ -1925,9 +1996,10 @@ impl<T: Config> StakingInterface for Pallet<T> {
 }
 
 impl<T: Config> sp_staking::StakingUnchecked for Pallet<T> {
-	fn migrate_to_virtual_staker(who: &Self::AccountId) {
-		asset::kill_stake::<T>(who);
+	fn migrate_to_virtual_staker(who: &Self::AccountId) -> DispatchResult {
+		asset::kill_stake::<T>(who)?;
 		VirtualStakers::<T>::insert(who, ());
+		Ok(())
 	}
 
 	/// Virtually bonds `keyless_who` to `payee` with `value`.
@@ -1945,9 +2017,6 @@ impl<T: Config> sp_staking::StakingUnchecked for Pallet<T> {
 		// check if payee not same as who.
 		ensure!(keyless_who != payee, Error::<T>::RewardDestinationRestricted);
 
-		// mark this pallet as consumer of `who`.
-		frame_system::Pallet::<T>::inc_consumers(&keyless_who).map_err(|_| Error::<T>::BadState)?;
-
 		// mark who as a virtual staker.
 		VirtualStakers::<T>::insert(keyless_who, ());
 
@@ -1959,11 +2028,13 @@ impl<T: Config> sp_staking::StakingUnchecked for Pallet<T> {
 		Ok(())
 	}
 
+	/// Only meant to be used in tests.
 	#[cfg(feature = "runtime-benchmarks")]
 	fn migrate_to_direct_staker(who: &Self::AccountId) {
 		assert!(VirtualStakers::<T>::contains_key(who));
 		let ledger = StakingLedger::<T>::get(Stash(who.clone())).unwrap();
-		asset::update_stake::<T>(who, ledger.total);
+		let _ = asset::update_stake::<T>(who, ledger.total)
+			.expect("funds must be transferred to stash");
 		VirtualStakers::<T>::remove(who);
 	}
 }
@@ -2100,7 +2171,7 @@ impl<T: Config> Pallet<T> {
 				if VirtualStakers::<T>::contains_key(stash.clone()) {
 					ensure!(
 						asset::staked::<T>(&stash) == Zero::zero(),
-						"virtual stakers should not have any locked balance"
+						"virtual stakers should not have any staked balance"
 					);
 					ensure!(
 						<Bonded<T>>::get(stash.clone()).unwrap() == stash.clone(),
@@ -2128,7 +2199,7 @@ impl<T: Config> Pallet<T> {
 				} else {
 					ensure!(
 						Self::inspect_bond_state(&stash) == Ok(LedgerIntegrityState::Ok),
-						"bond, ledger and/or staking lock inconsistent for a bonded stash."
+						"bond, ledger and/or staking hold inconsistent for a bonded stash."
 					);
 				}
 
diff --git a/substrate/frame/staking/src/pallet/mod.rs b/substrate/frame/staking/src/pallet/mod.rs
index b3f8c18f704cd7e5158c89f0e432f50d7bcb8c04..7d5da9ea0c49753e0db14092b978d0c88b6dcd63 100644
--- a/substrate/frame/staking/src/pallet/mod.rs
+++ b/substrate/frame/staking/src/pallet/mod.rs
@@ -25,8 +25,12 @@ use frame_election_provider_support::{
 use frame_support::{
 	pallet_prelude::*,
 	traits::{
+		fungible::{
+			hold::{Balanced as FunHoldBalanced, Mutate as FunHoldMutate},
+			Mutate as FunMutate,
+		},
 		Defensive, DefensiveSaturating, EnsureOrigin, EstimateNextNewSession, Get,
-		InspectLockableCurrency, LockableCurrency, OnUnbalanced, UnixTime,
+		InspectLockableCurrency, OnUnbalanced, UnixTime,
 	},
 	weights::Weight,
 	BoundedVec,
@@ -89,13 +93,27 @@ pub mod pallet {
 
 	#[pallet::config(with_default)]
 	pub trait Config: frame_system::Config {
+		/// The old trait for staking balance. Deprecated and only used for migrating old ledgers.
+		#[pallet::no_default]
+		type OldCurrency: InspectLockableCurrency<
+			Self::AccountId,
+			Moment = BlockNumberFor<Self>,
+			Balance = Self::CurrencyBalance,
+		>;
+
 		/// The staking balance.
 		#[pallet::no_default]
-		type Currency: LockableCurrency<
+		type Currency: FunHoldMutate<
 				Self::AccountId,
-				Moment = BlockNumberFor<Self>,
+				Reason = Self::RuntimeHoldReason,
 				Balance = Self::CurrencyBalance,
-			> + InspectLockableCurrency<Self::AccountId>;
+			> + FunMutate<Self::AccountId, Balance = Self::CurrencyBalance>
+			+ FunHoldBalanced<Self::AccountId, Balance = Self::CurrencyBalance>;
+
+		/// Overarching hold reason.
+		#[pallet::no_default_bounds]
+		type RuntimeHoldReason: From<HoldReason>;
+
 		/// Just the `Currency::Balance` type; we have this item to allow us to constrain it to
 		/// `From<u64>`.
 		type CurrencyBalance: sp_runtime::traits::AtLeast32BitUnsigned
@@ -106,6 +124,8 @@ pub mod pallet {
 			+ Default
 			+ From<u64>
 			+ TypeInfo
+			+ Send
+			+ Sync
 			+ MaxEncodedLen;
 		/// Time used for computing era duration.
 		///
@@ -309,6 +329,14 @@ pub mod pallet {
 		type WeightInfo: WeightInfo;
 	}
 
+	/// A reason for placing a hold on funds.
+	#[pallet::composite_enum]
+	pub enum HoldReason {
+		/// Funds on stake by a nominator or a validator.
+		#[codec(index = 0)]
+		Staking,
+	}
+
 	/// Default implementations of [`DefaultConfig`], which can be used to implement [`Config`].
 	pub mod config_preludes {
 		use super::*;
@@ -327,6 +355,8 @@ pub mod pallet {
 		impl DefaultConfig for TestDefaultConfig {
 			#[inject_runtime_type]
 			type RuntimeEvent = ();
+			#[inject_runtime_type]
+			type RuntimeHoldReason = ();
 			type CurrencyBalance = u128;
 			type CurrencyToVote = ();
 			type NominationsQuota = crate::FixedNominationsQuota<16>;
@@ -765,7 +795,7 @@ pub mod pallet {
 					status
 				);
 				assert!(
-					asset::stakeable_balance::<T>(stash) >= balance,
+					asset::free_to_stake::<T>(stash) >= balance,
 					"Stash does not have enough balance to bond."
 				);
 				frame_support::assert_ok!(<Pallet<T>>::bond(
@@ -858,6 +888,9 @@ pub mod pallet {
 		ValidatorDisabled { stash: T::AccountId },
 		/// Validator has been re-enabled.
 		ValidatorReenabled { stash: T::AccountId },
+		/// Staking balance migrated from locks to holds, with any balance that could not be held
+		/// is force withdrawn.
+		CurrencyMigrated { stash: T::AccountId, force_withdraw: BalanceOf<T> },
 	}
 
 	#[pallet::error]
@@ -929,6 +962,10 @@ pub mod pallet {
 		NotEnoughFunds,
 		/// Operation not allowed for virtual stakers.
 		VirtualStakerNotAllowed,
+		/// Stash could not be reaped as other pallet might depend on it.
+		CannotReapStash,
+		/// The stake of this account is already migrated to `Fungible` holds.
+		AlreadyMigrated,
 	}
 
 	#[pallet::hooks]
@@ -1172,10 +1209,7 @@ pub mod pallet {
 				return Err(Error::<T>::InsufficientBond.into())
 			}
 
-			// Would fail if account has no provider.
-			frame_system::Pallet::<T>::inc_consumers(&stash)?;
-
-			let stash_balance = asset::stakeable_balance::<T>(&stash);
+			let stash_balance = asset::free_to_stake::<T>(&stash);
 			let value = value.min(stash_balance);
 			Self::deposit_event(Event::<T>::Bonded { stash: stash.clone(), amount: value });
 			let ledger = StakingLedger::<T>::new(stash.clone(), value);
@@ -2231,8 +2265,8 @@ pub mod pallet {
 
 					let new_total = if let Some(total) = maybe_total {
 						let new_total = total.min(stash_balance);
-						// enforce lock == ledger.amount.
-						asset::update_stake::<T>(&stash, new_total);
+						// enforce hold == ledger.amount.
+						asset::update_stake::<T>(&stash, new_total)?;
 						new_total
 					} else {
 						current_lock
@@ -2259,13 +2293,13 @@ pub mod pallet {
 					// to enforce a new ledger.total and staking lock for this stash.
 					let new_total =
 						maybe_total.ok_or(Error::<T>::CannotRestoreLedger)?.min(stash_balance);
-					asset::update_stake::<T>(&stash, new_total);
+					asset::update_stake::<T>(&stash, new_total)?;
 
 					Ok((stash.clone(), new_total))
 				},
 				Err(Error::<T>::BadState) => {
 					// the stash and ledger do not exist but lock is lingering.
-					asset::kill_stake::<T>(&stash);
+					asset::kill_stake::<T>(&stash)?;
 					ensure!(
 						Self::inspect_bond_state(&stash) == Err(Error::<T>::NotStash),
 						Error::<T>::BadState
@@ -2291,6 +2325,26 @@ pub mod pallet {
 			);
 			Ok(())
 		}
+
+		/// Migrates permissionlessly a stash from locks to holds.
+		///
+		/// This removes the old lock on the stake and creates a hold on it atomically. If all
+		/// stake cannot be held, the best effort is made to hold as much as possible. The remaining
+		/// stake is removed from the ledger.
+		///
+		/// The fee is waived if the migration is successful.
+		#[pallet::call_index(30)]
+		#[pallet::weight(T::WeightInfo::migrate_currency())]
+		pub fn migrate_currency(
+			origin: OriginFor<T>,
+			stash: T::AccountId,
+		) -> DispatchResultWithPostInfo {
+			let _ = ensure_signed(origin)?;
+			Self::do_migrate_currency(&stash)?;
+
+			// Refund the transaction fee if successful.
+			Ok(Pays::No.into())
+		}
 	}
 }
 
diff --git a/substrate/frame/staking/src/testing_utils.rs b/substrate/frame/staking/src/testing_utils.rs
index 81337710aa9043bf975a95c3d5377b0dea0738f8..dfd5422106c081784c95985fa64477b596e93064 100644
--- a/substrate/frame/staking/src/testing_utils.rs
+++ b/substrate/frame/staking/src/testing_utils.rs
@@ -238,3 +238,21 @@ pub fn create_validators_with_nominators_for_era<T: Config>(
 pub fn current_era<T: Config>() -> EraIndex {
 	CurrentEra::<T>::get().unwrap_or(0)
 }
+
+pub fn migrate_to_old_currency<T: Config>(who: T::AccountId) {
+	use frame_support::traits::LockableCurrency;
+	let staked = asset::staked::<T>(&who);
+
+	// apply locks (this also adds a consumer).
+	T::OldCurrency::set_lock(
+		STAKING_ID,
+		&who,
+		staked,
+		frame_support::traits::WithdrawReasons::all(),
+	);
+	// remove holds.
+	asset::kill_stake::<T>(&who).expect("remove hold failed");
+
+	// replicate old behaviour of explicit increment of consumer.
+	frame_system::Pallet::<T>::inc_consumers(&who).expect("increment consumer failed");
+}
diff --git a/substrate/frame/staking/src/tests.rs b/substrate/frame/staking/src/tests.rs
index 6c2335e1aac8a0f32beba70ac6974729e70af441..90841514399403301be4eb3a0d2ba87da543ddc2 100644
--- a/substrate/frame/staking/src/tests.rs
+++ b/substrate/frame/staking/src/tests.rs
@@ -26,8 +26,12 @@ use frame_election_provider_support::{
 use frame_support::{
 	assert_noop, assert_ok, assert_storage_noop,
 	dispatch::{extract_actual_weight, GetDispatchInfo, WithPostDispatchInfo},
+	hypothetically,
 	pallet_prelude::*,
-	traits::{Currency, Get, ReservableCurrency},
+	traits::{
+		fungible::Inspect, Currency, Get, InspectLockableCurrency, LockableCurrency,
+		ReservableCurrency, WithdrawReasons,
+	},
 };
 
 use mock::*;
@@ -108,7 +112,7 @@ fn force_unstake_works() {
 		// Cant transfer
 		assert_noop!(
 			Balances::transfer_allow_death(RuntimeOrigin::signed(11), 1, 10),
-			TokenError::Frozen,
+			TokenError::FundsUnavailable,
 		);
 		// Force unstake requires root.
 		assert_noop!(Staking::force_unstake(RuntimeOrigin::signed(11), 11, 2), BadOrigin);
@@ -229,8 +233,7 @@ fn basic_setup_works() {
 		assert_eq!(active_era(), 0);
 
 		// Account 10 has `balance_factor` free balance
-		assert_eq!(asset::stakeable_balance::<Test>(&10), 1);
-		assert_eq!(asset::stakeable_balance::<Test>(&10), 1);
+		assert_eq!(Balances::balance(&10), 1);
 
 		// New era is not being forced
 		assert_eq!(ForceEra::<Test>::get(), Forcing::NotForcing);
@@ -360,8 +363,16 @@ fn rewards_should_work() {
 				remainder: maximum_payout - total_payout_0
 			}
 		);
+
+		// make note of total issuance before rewards.
+		let total_issuance_0 = asset::total_issuance::<Test>();
+
 		mock::make_all_reward_payment(0);
 
+		// total issuance should have increased
+		let total_issuance_1 = asset::total_issuance::<Test>();
+		assert_eq!(total_issuance_1, total_issuance_0 + total_payout_0);
+
 		assert_eq_error_rate!(
 			asset::total_balance::<Test>(&11),
 			init_balance_11 + part_for_11 * total_payout_0 * 2 / 3,
@@ -401,6 +412,7 @@ fn rewards_should_work() {
 		);
 		mock::make_all_reward_payment(1);
 
+		assert_eq!(asset::total_issuance::<Test>(), total_issuance_1 + total_payout_1);
 		assert_eq_error_rate!(
 			asset::total_balance::<Test>(&11),
 			init_balance_11 + part_for_11 * (total_payout_0 * 2 / 3 + total_payout_1),
@@ -490,7 +502,7 @@ fn staking_should_work() {
 			}
 		);
 		// e.g. it cannot reserve more than 500 that it has free from the total 2000
-		assert_noop!(Balances::reserve(&3, 501), BalancesError::<Test, _>::LiquidityRestrictions);
+		assert_noop!(Balances::reserve(&3, 501), DispatchError::ConsumerRemaining);
 		assert_ok!(Balances::reserve(&3, 409));
 	});
 }
@@ -689,7 +701,7 @@ fn nominating_and_rewards_should_work() {
 			);
 			// Nominator 3: has [400/1800 ~ 2/9 from 10] + [600/2200 ~ 3/11 from 21]'s reward. ==>
 			// 2/9 + 3/11
-			assert_eq!(asset::total_balance::<Test>(&3), initial_balance);
+			assert_eq!(asset::stakeable_balance::<Test>(&3), initial_balance);
 			// 333 is the reward destination for 3.
 			assert_eq_error_rate!(
 				asset::total_balance::<Test>(&333),
@@ -992,9 +1004,9 @@ fn cannot_transfer_staked_balance() {
 	ExtBuilder::default().nominate(false).build_and_execute(|| {
 		// Confirm account 11 is stashed
 		assert_eq!(Staking::bonded(&11), Some(11));
-		// Confirm account 11 has some free balance
+		// Confirm account 11 has some stakeable balance
 		assert_eq!(asset::stakeable_balance::<Test>(&11), 1000);
-		// Confirm account 11 (via controller) is totally staked
+		// Confirm account 11 is totally staked
 		assert_eq!(Staking::eras_stakers(active_era(), &11).total, 1000);
 		// Confirm account 11 cannot transfer as a result
 		assert_noop!(
@@ -1021,11 +1033,12 @@ fn cannot_transfer_staked_balance_2() {
 		assert_eq!(asset::stakeable_balance::<Test>(&21), 2000);
 		// Confirm account 21 (via controller) is totally staked
 		assert_eq!(Staking::eras_stakers(active_era(), &21).total, 1000);
-		// Confirm account 21 can transfer at most 1000
+		// Confirm account 21 cannot transfer more than 1000
 		assert_noop!(
 			Balances::transfer_allow_death(RuntimeOrigin::signed(21), 21, 1001),
 			TokenError::Frozen,
 		);
+		// Confirm account 21 needs to leave at least ED in free balance to be able to transfer
 		assert_ok!(Balances::transfer_allow_death(RuntimeOrigin::signed(21), 21, 1000));
 	});
 }
@@ -1036,17 +1049,61 @@ fn cannot_reserve_staked_balance() {
 	ExtBuilder::default().build_and_execute(|| {
 		// Confirm account 11 is stashed
 		assert_eq!(Staking::bonded(&11), Some(11));
-		// Confirm account 11 has some free balance
-		assert_eq!(asset::stakeable_balance::<Test>(&11), 1000);
-		// Confirm account 11 (via controller 10) is totally staked
-		assert_eq!(Staking::eras_stakers(active_era(), &11).own, 1000);
+		// Confirm account 11 is totally staked
+		assert_eq!(asset::staked::<Test>(&11), 1000);
+
 		// Confirm account 11 cannot reserve as a result
-		assert_noop!(Balances::reserve(&11, 1), BalancesError::<Test, _>::LiquidityRestrictions);
+		assert_noop!(Balances::reserve(&11, 2), BalancesError::<Test, _>::InsufficientBalance);
+		assert_noop!(Balances::reserve(&11, 1), DispatchError::ConsumerRemaining);
 
 		// Give account 11 extra free balance
-		let _ = asset::set_stakeable_balance::<Test>(&11, 10000);
+		let _ = asset::set_stakeable_balance::<Test>(&11, 1000 + 1000);
+		assert_eq!(asset::free_to_stake::<Test>(&11), 1000);
+
 		// Confirm account 11 can now reserve balance
-		assert_ok!(Balances::reserve(&11, 1));
+		assert_ok!(Balances::reserve(&11, 500));
+
+		// free to stake balance has reduced
+		assert_eq!(asset::free_to_stake::<Test>(&11), 500);
+	});
+}
+
+#[test]
+fn locked_balance_can_be_staked() {
+	// Checks that a bonded account cannot reserve balance from free balance
+	ExtBuilder::default().build_and_execute(|| {
+		// Confirm account 11 is stashed
+		assert_eq!(Staking::bonded(&11), Some(11));
+		assert_eq!(asset::staked::<Test>(&11), 1000);
+		assert_eq!(asset::free_to_stake::<Test>(&11), 0);
+
+		// add some staking balance to 11
+		let _ = asset::set_stakeable_balance::<Test>(&11, 1000 + 1000);
+		// free to stake is 1000
+		assert_eq!(asset::free_to_stake::<Test>(&11), 1000);
+
+		// lock some balance
+		Balances::set_lock(*b"somelock", &11, 500, WithdrawReasons::all());
+
+		// locked balance still available for staking
+		assert_eq!(asset::free_to_stake::<Test>(&11), 1000);
+
+		// can stake free balance
+		assert_ok!(Staking::bond_extra(RuntimeOrigin::signed(11), 500));
+		assert_eq!(asset::staked::<Test>(&11), 1500);
+
+		// Can stake the locked balance
+		assert_ok!(Staking::bond_extra(RuntimeOrigin::signed(11), 500));
+		assert_eq!(asset::staked::<Test>(&11), 2000);
+		// no balance left to stake
+		assert_eq!(asset::free_to_stake::<Test>(&11), 0);
+
+		// this does not fail if someone tries to stake more than free balance but just stakes
+		// whatever is available. (not sure if that is the best way, but we keep it backward
+		// compatible)
+		assert_ok!(Staking::bond_extra(RuntimeOrigin::signed(11), 10));
+		// no extra balance staked.
+		assert_eq!(asset::staked::<Test>(&11), 2000);
 	});
 }
 
@@ -1057,9 +1114,9 @@ fn reward_destination_works() {
 		// Check that account 11 is a validator
 		assert!(Session::validators().contains(&11));
 		// Check the balance of the validator account
-		assert_eq!(asset::stakeable_balance::<Test>(&10), 1);
+		assert_eq!(asset::total_balance::<Test>(&10), 1);
 		// Check the balance of the stash account
-		assert_eq!(asset::stakeable_balance::<Test>(&11), 1000);
+		assert_eq!(asset::total_balance::<Test>(&11), 1001);
 		// Check how much is at stake
 		assert_eq!(
 			Staking::ledger(11.into()).unwrap(),
@@ -1294,12 +1351,12 @@ fn bond_extra_and_withdraw_unbonded_works() {
 		// Give account 11 some large free balance greater than total
 		let _ = asset::set_stakeable_balance::<Test>(&11, 1000000);
 
+		// ensure it has the correct balance.
+		assert_eq!(asset::stakeable_balance::<Test>(&11), 1000000);
+
 		// Initial config should be correct
 		assert_eq!(active_era(), 0);
 
-		// check the balance of a validator accounts.
-		assert_eq!(asset::total_balance::<Test>(&11), 1000000);
-
 		// confirm that 10 is a normal validator and gets paid at the end of the era.
 		mock::start_active_era(1);
 
@@ -2077,7 +2134,7 @@ fn bond_with_no_staked_value() {
 			);
 			// bonded with absolute minimum value possible.
 			assert_ok!(Staking::bond(RuntimeOrigin::signed(1), 5, RewardDestination::Account(1)));
-			assert_eq!(pallet_balances::Locks::<Test>::get(&1)[0].amount, 5);
+			assert_eq!(pallet_balances::Holds::<Test>::get(&1)[0].amount, 5);
 
 			// unbonding even 1 will cause all to be unbonded.
 			assert_ok!(Staking::unbond(RuntimeOrigin::signed(1), 1));
@@ -2098,14 +2155,14 @@ fn bond_with_no_staked_value() {
 			// not yet removed.
 			assert_ok!(Staking::withdraw_unbonded(RuntimeOrigin::signed(1), 0));
 			assert!(Staking::ledger(1.into()).is_ok());
-			assert_eq!(pallet_balances::Locks::<Test>::get(&1)[0].amount, 5);
+			assert_eq!(pallet_balances::Holds::<Test>::get(&1)[0].amount, 5);
 
 			mock::start_active_era(3);
 
 			// poof. Account 1 is removed from the staking system.
 			assert_ok!(Staking::withdraw_unbonded(RuntimeOrigin::signed(1), 0));
 			assert!(Staking::ledger(1.into()).is_err());
-			assert_eq!(pallet_balances::Locks::<Test>::get(&1).len(), 0);
+			assert_eq!(pallet_balances::Holds::<Test>::get(&1).len(), 0);
 		});
 }
 
@@ -2338,9 +2395,20 @@ fn reward_validator_slashing_validator_does_not_overflow() {
 		EraInfo::<Test>::set_exposure(0, &11, exposure);
 		ErasValidatorReward::<Test>::insert(0, stake);
 		assert_ok!(Staking::payout_stakers_by_page(RuntimeOrigin::signed(1337), 11, 0, 0));
-		assert_eq!(asset::total_balance::<Test>(&11), stake * 2);
+		assert_eq!(asset::stakeable_balance::<Test>(&11), stake * 2);
 
-		// Set staker
+		// ensure ledger has `stake` and no more.
+		Ledger::<Test>::insert(
+			11,
+			StakingLedgerInspect {
+				stash: 11,
+				total: stake,
+				active: stake,
+				unlocking: Default::default(),
+				legacy_claimed_rewards: bounded_vec![1],
+			},
+		);
+		// Set staker (unsafe, can reduce balance below actual stake)
 		let _ = asset::set_stakeable_balance::<Test>(&11, stake);
 		let _ = asset::set_stakeable_balance::<Test>(&2, stake);
 
@@ -2366,8 +2434,8 @@ fn reward_validator_slashing_validator_does_not_overflow() {
 			&[Perbill::from_percent(100)],
 		);
 
-		assert_eq!(asset::total_balance::<Test>(&11), stake - 1);
-		assert_eq!(asset::total_balance::<Test>(&2), 1);
+		assert_eq!(asset::stakeable_balance::<Test>(&11), stake - 1);
+		assert_eq!(asset::stakeable_balance::<Test>(&2), 1);
 	})
 }
 
@@ -2627,8 +2695,8 @@ fn reporters_receive_their_slice() {
 		// 50% * (10% * initial_balance / 2)
 		let reward = (initial_balance / 20) / 2;
 		let reward_each = reward / 2; // split into two pieces.
-		assert_eq!(asset::stakeable_balance::<Test>(&1), 10 + reward_each);
-		assert_eq!(asset::stakeable_balance::<Test>(&2), 20 + reward_each);
+		assert_eq!(asset::total_balance::<Test>(&1), 10 + reward_each);
+		assert_eq!(asset::total_balance::<Test>(&2), 20 + reward_each);
 	});
 }
 
@@ -2653,7 +2721,7 @@ fn subsequent_reports_in_same_span_pay_out_less() {
 		// F1 * (reward_proportion * slash - 0)
 		// 50% * (10% * initial_balance * 20%)
 		let reward = (initial_balance / 5) / 20;
-		assert_eq!(asset::stakeable_balance::<Test>(&1), 10 + reward);
+		assert_eq!(asset::total_balance::<Test>(&1), 10 + reward);
 
 		on_offence_now(
 			&[OffenceDetails {
@@ -2668,7 +2736,7 @@ fn subsequent_reports_in_same_span_pay_out_less() {
 		// F1 * (reward_proportion * slash - prior_payout)
 		// 50% * (10% * (initial_balance / 2) - prior_payout)
 		let reward = ((initial_balance / 20) - prior_payout) / 2;
-		assert_eq!(asset::stakeable_balance::<Test>(&1), 10 + prior_payout + reward);
+		assert_eq!(asset::total_balance::<Test>(&1), 10 + prior_payout + reward);
 	});
 }
 
@@ -2812,8 +2880,9 @@ fn garbage_collection_after_slashing() {
 			// validator and nominator slash in era are garbage-collected by era change,
 			// so we don't test those here.
 
-			assert_eq!(asset::stakeable_balance::<Test>(&11), 2);
-			assert_eq!(asset::total_balance::<Test>(&11), 2);
+			assert_eq!(asset::stakeable_balance::<Test>(&11), 0);
+			// Non staked balance is not touched.
+			assert_eq!(asset::total_balance::<Test>(&11), ExistentialDeposit::get());
 
 			let slashing_spans = SlashingSpans::<Test>::get(&11).unwrap();
 			assert_eq!(slashing_spans.iter().count(), 2);
@@ -6092,7 +6161,7 @@ fn nomination_quota_max_changes_decoding() {
 		.add_staker(70, 71, 10, StakerStatus::Nominator(vec![1, 2, 3]))
 		.add_staker(30, 330, 10, StakerStatus::Nominator(vec![1, 2, 3, 4]))
 		.add_staker(50, 550, 10, StakerStatus::Nominator(vec![1, 2, 3, 4]))
-		.balance_factor(10)
+		.balance_factor(11)
 		.build_and_execute(|| {
 			// pre-condition.
 			assert_eq!(MaxNominationsOf::<Test>::get(), 16);
@@ -6208,240 +6277,248 @@ fn force_apply_min_commission_works() {
 
 #[test]
 fn proportional_slash_stop_slashing_if_remaining_zero() {
-	let c = |era, value| UnlockChunk::<Balance> { era, value };
+	ExtBuilder::default().nominate(true).build_and_execute(|| {
+		let c = |era, value| UnlockChunk::<Balance> { era, value };
 
-	// we have some chunks, but they are not affected.
-	let unlocking = bounded_vec![c(1, 10), c(2, 10)];
+		// we have some chunks, but they are not affected.
+		let unlocking = bounded_vec![c(1, 10), c(2, 10)];
 
-	// Given
-	let mut ledger = StakingLedger::<Test>::new(123, 20);
-	ledger.total = 40;
-	ledger.unlocking = unlocking;
+		// Given
+		let mut ledger = StakingLedger::<Test>::new(123, 20);
+		ledger.total = 40;
+		ledger.unlocking = unlocking;
 
-	assert_eq!(BondingDuration::get(), 3);
+		assert_eq!(BondingDuration::get(), 3);
 
-	// should not slash more than the amount requested, by accidentally slashing the first chunk.
-	assert_eq!(ledger.slash(18, 1, 0), 18);
+		// should not slash more than the amount requested, by accidentally slashing the first
+		// chunk.
+		assert_eq!(ledger.slash(18, 1, 0), 18);
+	});
 }
 
 #[test]
 fn proportional_ledger_slash_works() {
-	let c = |era, value| UnlockChunk::<Balance> { era, value };
-	// Given
-	let mut ledger = StakingLedger::<Test>::new(123, 10);
-	assert_eq!(BondingDuration::get(), 3);
-
-	// When we slash a ledger with no unlocking chunks
-	assert_eq!(ledger.slash(5, 1, 0), 5);
-	// Then
-	assert_eq!(ledger.total, 5);
-	assert_eq!(ledger.active, 5);
-	assert_eq!(LedgerSlashPerEra::get().0, 5);
-	assert_eq!(LedgerSlashPerEra::get().1, Default::default());
-
-	// When we slash a ledger with no unlocking chunks and the slash amount is greater then the
-	// total
-	assert_eq!(ledger.slash(11, 1, 0), 5);
-	// Then
-	assert_eq!(ledger.total, 0);
-	assert_eq!(ledger.active, 0);
-	assert_eq!(LedgerSlashPerEra::get().0, 0);
-	assert_eq!(LedgerSlashPerEra::get().1, Default::default());
-
-	// Given
-	ledger.unlocking = bounded_vec![c(4, 10), c(5, 10)];
-	ledger.total = 2 * 10;
-	ledger.active = 0;
-	// When all the chunks overlap with the slash eras
-	assert_eq!(ledger.slash(20, 0, 0), 20);
-	// Then
-	assert_eq!(ledger.unlocking, vec![]);
-	assert_eq!(ledger.total, 0);
-	assert_eq!(LedgerSlashPerEra::get().0, 0);
-	assert_eq!(LedgerSlashPerEra::get().1, BTreeMap::from([(4, 0), (5, 0)]));
-
-	// Given
-	ledger.unlocking = bounded_vec![c(4, 100), c(5, 100), c(6, 100), c(7, 100)];
-	ledger.total = 4 * 100;
-	ledger.active = 0;
-	// When the first 2 chunks don't overlap with the affected range of unlock eras.
-	assert_eq!(ledger.slash(140, 0, 3), 140);
-	// Then
-	assert_eq!(ledger.unlocking, vec![c(4, 100), c(5, 100), c(6, 30), c(7, 30)]);
-	assert_eq!(ledger.total, 4 * 100 - 140);
-	assert_eq!(LedgerSlashPerEra::get().0, 0);
-	assert_eq!(LedgerSlashPerEra::get().1, BTreeMap::from([(6, 30), (7, 30)]));
-
-	// Given
-	ledger.unlocking = bounded_vec![c(4, 100), c(5, 100), c(6, 100), c(7, 100)];
-	ledger.total = 4 * 100;
-	ledger.active = 0;
-	// When the first 2 chunks don't overlap with the affected range of unlock eras.
-	assert_eq!(ledger.slash(15, 0, 3), 15);
-	// Then
-	assert_eq!(ledger.unlocking, vec![c(4, 100), c(5, 100), c(6, 100 - 8), c(7, 100 - 7)]);
-	assert_eq!(ledger.total, 4 * 100 - 15);
-	assert_eq!(LedgerSlashPerEra::get().0, 0);
-	assert_eq!(LedgerSlashPerEra::get().1, BTreeMap::from([(6, 92), (7, 93)]));
-
-	// Given
-	ledger.unlocking = bounded_vec![c(4, 40), c(5, 100), c(6, 10), c(7, 250)];
-	ledger.active = 500;
-	// 900
-	ledger.total = 40 + 10 + 100 + 250 + 500;
-	// When we have a partial slash that touches all chunks
-	assert_eq!(ledger.slash(900 / 2, 0, 0), 450);
-	// Then
-	assert_eq!(ledger.active, 500 / 2);
-	assert_eq!(ledger.unlocking, vec![c(4, 40 / 2), c(5, 100 / 2), c(6, 10 / 2), c(7, 250 / 2)]);
-	assert_eq!(ledger.total, 900 / 2);
-	assert_eq!(LedgerSlashPerEra::get().0, 500 / 2);
-	assert_eq!(
-		LedgerSlashPerEra::get().1,
-		BTreeMap::from([(4, 40 / 2), (5, 100 / 2), (6, 10 / 2), (7, 250 / 2)])
-	);
+	ExtBuilder::default().nominate(true).build_and_execute(|| {
+		let c = |era, value| UnlockChunk::<Balance> { era, value };
+		// Given
+		let mut ledger = StakingLedger::<Test>::new(123, 10);
+		assert_eq!(BondingDuration::get(), 3);
 
-	// slash 1/4th with not chunk.
-	ledger.unlocking = bounded_vec![];
-	ledger.active = 500;
-	ledger.total = 500;
-	// When we have a partial slash that touches all chunks
-	assert_eq!(ledger.slash(500 / 4, 0, 0), 500 / 4);
-	// Then
-	assert_eq!(ledger.active, 3 * 500 / 4);
-	assert_eq!(ledger.unlocking, vec![]);
-	assert_eq!(ledger.total, ledger.active);
-	assert_eq!(LedgerSlashPerEra::get().0, 3 * 500 / 4);
-	assert_eq!(LedgerSlashPerEra::get().1, Default::default());
-
-	// Given we have the same as above,
-	ledger.unlocking = bounded_vec![c(4, 40), c(5, 100), c(6, 10), c(7, 250)];
-	ledger.active = 500;
-	ledger.total = 40 + 10 + 100 + 250 + 500; // 900
-	assert_eq!(ledger.total, 900);
-	// When we have a higher min balance
-	assert_eq!(
-		ledger.slash(
-			900 / 2,
-			25, /* min balance - chunks with era 0 & 2 will be slashed to <=25, causing it to
-			     * get swept */
-			0
-		),
-		450
-	);
-	assert_eq!(ledger.active, 500 / 2);
-	// the last chunk was not slashed 50% like all the rest, because some other earlier chunks got
-	// dusted.
-	assert_eq!(ledger.unlocking, vec![c(5, 100 / 2), c(7, 150)]);
-	assert_eq!(ledger.total, 900 / 2);
-	assert_eq!(LedgerSlashPerEra::get().0, 500 / 2);
-	assert_eq!(
-		LedgerSlashPerEra::get().1,
-		BTreeMap::from([(4, 0), (5, 100 / 2), (6, 0), (7, 150)])
-	);
+		// When we slash a ledger with no unlocking chunks
+		assert_eq!(ledger.slash(5, 1, 0), 5);
+		// Then
+		assert_eq!(ledger.total, 5);
+		assert_eq!(ledger.active, 5);
+		assert_eq!(LedgerSlashPerEra::get().0, 5);
+		assert_eq!(LedgerSlashPerEra::get().1, Default::default());
+
+		// When we slash a ledger with no unlocking chunks and the slash amount is greater then the
+		// total
+		assert_eq!(ledger.slash(11, 1, 0), 5);
+		// Then
+		assert_eq!(ledger.total, 0);
+		assert_eq!(ledger.active, 0);
+		assert_eq!(LedgerSlashPerEra::get().0, 0);
+		assert_eq!(LedgerSlashPerEra::get().1, Default::default());
 
-	// Given
-	// slash order --------------------NA--------2----------0----------1----
-	ledger.unlocking = bounded_vec![c(4, 40), c(5, 100), c(6, 10), c(7, 250)];
-	ledger.active = 500;
-	ledger.total = 40 + 10 + 100 + 250 + 500; // 900
-	assert_eq!(
-		ledger.slash(
-			500 + 10 + 250 + 100 / 2, // active + era 6 + era 7 + era 5 / 2
-			0,
-			3 /* slash era 6 first, so the affected parts are era 6, era 7 and
-			   * ledge.active. This will cause the affected to go to zero, and then we will
-			   * start slashing older chunks */
-		),
-		500 + 250 + 10 + 100 / 2
-	);
-	// Then
-	assert_eq!(ledger.active, 0);
-	assert_eq!(ledger.unlocking, vec![c(4, 40), c(5, 100 / 2)]);
-	assert_eq!(ledger.total, 90);
-	assert_eq!(LedgerSlashPerEra::get().0, 0);
-	assert_eq!(LedgerSlashPerEra::get().1, BTreeMap::from([(5, 100 / 2), (6, 0), (7, 0)]));
-
-	// Given
-	// iteration order------------------NA---------2----------0----------1----
-	ledger.unlocking = bounded_vec![c(4, 100), c(5, 100), c(6, 100), c(7, 100)];
-	ledger.active = 100;
-	ledger.total = 5 * 100;
-	// When
-	assert_eq!(
-		ledger.slash(
-			351, // active + era 6 + era 7 + era 5 / 2 + 1
-			50,  // min balance - everything slashed below 50 will get dusted
-			3    /* slash era 3+3 first, so the affected parts are era 6, era 7 and
-			      * ledge.active. This will cause the affected to go to zero, and then we will
-			      * start slashing older chunks */
-		),
-		400
-	);
-	// Then
-	assert_eq!(ledger.active, 0);
-	assert_eq!(ledger.unlocking, vec![c(4, 100)]);
-	assert_eq!(ledger.total, 100);
-	assert_eq!(LedgerSlashPerEra::get().0, 0);
-	assert_eq!(LedgerSlashPerEra::get().1, BTreeMap::from([(5, 0), (6, 0), (7, 0)]));
-
-	// Tests for saturating arithmetic
-
-	// Given
-	let slash = u64::MAX as Balance * 2;
-	// The value of the other parts of ledger that will get slashed
-	let value = slash - (10 * 4);
-
-	ledger.active = 10;
-	ledger.unlocking = bounded_vec![c(4, 10), c(5, 10), c(6, 10), c(7, value)];
-	ledger.total = value + 40;
-	// When
-	let slash_amount = ledger.slash(slash, 0, 0);
-	assert_eq_error_rate!(slash_amount, slash, 5);
-	// Then
-	assert_eq!(ledger.active, 0); // slash of 9
-	assert_eq!(ledger.unlocking, vec![]);
-	assert_eq!(ledger.total, 0);
-	assert_eq!(LedgerSlashPerEra::get().0, 0);
-	assert_eq!(LedgerSlashPerEra::get().1, BTreeMap::from([(4, 0), (5, 0), (6, 0), (7, 0)]));
-
-	// Given
-	use sp_runtime::PerThing as _;
-	let slash = u64::MAX as Balance * 2;
-	let value = u64::MAX as Balance * 2;
-	let unit = 100;
-	// slash * value that will saturate
-	assert!(slash.checked_mul(value).is_none());
-	// but slash * unit won't.
-	assert!(slash.checked_mul(unit).is_some());
-	ledger.unlocking = bounded_vec![c(4, unit), c(5, value), c(6, unit), c(7, unit)];
-	//--------------------------------------note value^^^
-	ledger.active = unit;
-	ledger.total = unit * 4 + value;
-	// When
-	assert_eq!(ledger.slash(slash, 0, 0), slash);
-	// Then
-	// The amount slashed out of `unit`
-	let affected_balance = value + unit * 4;
-	let ratio =
-		Perquintill::from_rational_with_rounding(slash, affected_balance, Rounding::Up).unwrap();
-	// `unit` after the slash is applied
-	let unit_slashed = {
-		let unit_slash = ratio.mul_ceil(unit);
-		unit - unit_slash
-	};
-	let value_slashed = {
-		let value_slash = ratio.mul_ceil(value);
-		value - value_slash
-	};
-	assert_eq!(ledger.active, unit_slashed);
-	assert_eq!(ledger.unlocking, vec![c(5, value_slashed), c(7, 32)]);
-	assert_eq!(ledger.total, value_slashed + 32);
-	assert_eq!(LedgerSlashPerEra::get().0, 0);
-	assert_eq!(
-		LedgerSlashPerEra::get().1,
-		BTreeMap::from([(4, 0), (5, value_slashed), (6, 0), (7, 32)])
-	);
+		// Given
+		ledger.unlocking = bounded_vec![c(4, 10), c(5, 10)];
+		ledger.total = 2 * 10;
+		ledger.active = 0;
+		// When all the chunks overlap with the slash eras
+		assert_eq!(ledger.slash(20, 0, 0), 20);
+		// Then
+		assert_eq!(ledger.unlocking, vec![]);
+		assert_eq!(ledger.total, 0);
+		assert_eq!(LedgerSlashPerEra::get().0, 0);
+		assert_eq!(LedgerSlashPerEra::get().1, BTreeMap::from([(4, 0), (5, 0)]));
+
+		// Given
+		ledger.unlocking = bounded_vec![c(4, 100), c(5, 100), c(6, 100), c(7, 100)];
+		ledger.total = 4 * 100;
+		ledger.active = 0;
+		// When the first 2 chunks don't overlap with the affected range of unlock eras.
+		assert_eq!(ledger.slash(140, 0, 3), 140);
+		// Then
+		assert_eq!(ledger.unlocking, vec![c(4, 100), c(5, 100), c(6, 30), c(7, 30)]);
+		assert_eq!(ledger.total, 4 * 100 - 140);
+		assert_eq!(LedgerSlashPerEra::get().0, 0);
+		assert_eq!(LedgerSlashPerEra::get().1, BTreeMap::from([(6, 30), (7, 30)]));
+
+		// Given
+		ledger.unlocking = bounded_vec![c(4, 100), c(5, 100), c(6, 100), c(7, 100)];
+		ledger.total = 4 * 100;
+		ledger.active = 0;
+		// When the first 2 chunks don't overlap with the affected range of unlock eras.
+		assert_eq!(ledger.slash(15, 0, 3), 15);
+		// Then
+		assert_eq!(ledger.unlocking, vec![c(4, 100), c(5, 100), c(6, 100 - 8), c(7, 100 - 7)]);
+		assert_eq!(ledger.total, 4 * 100 - 15);
+		assert_eq!(LedgerSlashPerEra::get().0, 0);
+		assert_eq!(LedgerSlashPerEra::get().1, BTreeMap::from([(6, 92), (7, 93)]));
+
+		// Given
+		ledger.unlocking = bounded_vec![c(4, 40), c(5, 100), c(6, 10), c(7, 250)];
+		ledger.active = 500;
+		// 900
+		ledger.total = 40 + 10 + 100 + 250 + 500;
+		// When we have a partial slash that touches all chunks
+		assert_eq!(ledger.slash(900 / 2, 0, 0), 450);
+		// Then
+		assert_eq!(ledger.active, 500 / 2);
+		assert_eq!(
+			ledger.unlocking,
+			vec![c(4, 40 / 2), c(5, 100 / 2), c(6, 10 / 2), c(7, 250 / 2)]
+		);
+		assert_eq!(ledger.total, 900 / 2);
+		assert_eq!(LedgerSlashPerEra::get().0, 500 / 2);
+		assert_eq!(
+			LedgerSlashPerEra::get().1,
+			BTreeMap::from([(4, 40 / 2), (5, 100 / 2), (6, 10 / 2), (7, 250 / 2)])
+		);
+
+		// slash 1/4th with not chunk.
+		ledger.unlocking = bounded_vec![];
+		ledger.active = 500;
+		ledger.total = 500;
+		// When we have a partial slash that touches all chunks
+		assert_eq!(ledger.slash(500 / 4, 0, 0), 500 / 4);
+		// Then
+		assert_eq!(ledger.active, 3 * 500 / 4);
+		assert_eq!(ledger.unlocking, vec![]);
+		assert_eq!(ledger.total, ledger.active);
+		assert_eq!(LedgerSlashPerEra::get().0, 3 * 500 / 4);
+		assert_eq!(LedgerSlashPerEra::get().1, Default::default());
+
+		// Given we have the same as above,
+		ledger.unlocking = bounded_vec![c(4, 40), c(5, 100), c(6, 10), c(7, 250)];
+		ledger.active = 500;
+		ledger.total = 40 + 10 + 100 + 250 + 500; // 900
+		assert_eq!(ledger.total, 900);
+		// When we have a higher min balance
+		assert_eq!(
+			ledger.slash(
+				900 / 2,
+				25, /* min balance - chunks with era 0 & 2 will be slashed to <=25, causing it
+				     * to get swept */
+				0
+			),
+			450
+		);
+		assert_eq!(ledger.active, 500 / 2);
+		// the last chunk was not slashed 50% like all the rest, because some other earlier chunks
+		// got dusted.
+		assert_eq!(ledger.unlocking, vec![c(5, 100 / 2), c(7, 150)]);
+		assert_eq!(ledger.total, 900 / 2);
+		assert_eq!(LedgerSlashPerEra::get().0, 500 / 2);
+		assert_eq!(
+			LedgerSlashPerEra::get().1,
+			BTreeMap::from([(4, 0), (5, 100 / 2), (6, 0), (7, 150)])
+		);
+
+		// Given
+		// slash order --------------------NA--------2----------0----------1----
+		ledger.unlocking = bounded_vec![c(4, 40), c(5, 100), c(6, 10), c(7, 250)];
+		ledger.active = 500;
+		ledger.total = 40 + 10 + 100 + 250 + 500; // 900
+		assert_eq!(
+			ledger.slash(
+				500 + 10 + 250 + 100 / 2, // active + era 6 + era 7 + era 5 / 2
+				0,
+				3 /* slash era 6 first, so the affected parts are era 6, era 7 and
+				   * ledge.active. This will cause the affected to go to zero, and then we will
+				   * start slashing older chunks */
+			),
+			500 + 250 + 10 + 100 / 2
+		);
+		// Then
+		assert_eq!(ledger.active, 0);
+		assert_eq!(ledger.unlocking, vec![c(4, 40), c(5, 100 / 2)]);
+		assert_eq!(ledger.total, 90);
+		assert_eq!(LedgerSlashPerEra::get().0, 0);
+		assert_eq!(LedgerSlashPerEra::get().1, BTreeMap::from([(5, 100 / 2), (6, 0), (7, 0)]));
+
+		// Given
+		// iteration order------------------NA---------2----------0----------1----
+		ledger.unlocking = bounded_vec![c(4, 100), c(5, 100), c(6, 100), c(7, 100)];
+		ledger.active = 100;
+		ledger.total = 5 * 100;
+		// When
+		assert_eq!(
+			ledger.slash(
+				351, // active + era 6 + era 7 + era 5 / 2 + 1
+				50,  // min balance - everything slashed below 50 will get dusted
+				3    /* slash era 3+3 first, so the affected parts are era 6, era 7 and
+				      * ledge.active. This will cause the affected to go to zero, and then we
+				      * will start slashing older chunks */
+			),
+			400
+		);
+		// Then
+		assert_eq!(ledger.active, 0);
+		assert_eq!(ledger.unlocking, vec![c(4, 100)]);
+		assert_eq!(ledger.total, 100);
+		assert_eq!(LedgerSlashPerEra::get().0, 0);
+		assert_eq!(LedgerSlashPerEra::get().1, BTreeMap::from([(5, 0), (6, 0), (7, 0)]));
+
+		// Tests for saturating arithmetic
+
+		// Given
+		let slash = u64::MAX as Balance * 2;
+		// The value of the other parts of ledger that will get slashed
+		let value = slash - (10 * 4);
+
+		ledger.active = 10;
+		ledger.unlocking = bounded_vec![c(4, 10), c(5, 10), c(6, 10), c(7, value)];
+		ledger.total = value + 40;
+		// When
+		let slash_amount = ledger.slash(slash, 0, 0);
+		assert_eq_error_rate!(slash_amount, slash, 5);
+		// Then
+		assert_eq!(ledger.active, 0); // slash of 9
+		assert_eq!(ledger.unlocking, vec![]);
+		assert_eq!(ledger.total, 0);
+		assert_eq!(LedgerSlashPerEra::get().0, 0);
+		assert_eq!(LedgerSlashPerEra::get().1, BTreeMap::from([(4, 0), (5, 0), (6, 0), (7, 0)]));
+
+		// Given
+		use sp_runtime::PerThing as _;
+		let slash = u64::MAX as Balance * 2;
+		let value = u64::MAX as Balance * 2;
+		let unit = 100;
+		// slash * value that will saturate
+		assert!(slash.checked_mul(value).is_none());
+		// but slash * unit won't.
+		assert!(slash.checked_mul(unit).is_some());
+		ledger.unlocking = bounded_vec![c(4, unit), c(5, value), c(6, unit), c(7, unit)];
+		//--------------------------------------note value^^^
+		ledger.active = unit;
+		ledger.total = unit * 4 + value;
+		// When
+		assert_eq!(ledger.slash(slash, 0, 0), slash);
+		// Then
+		// The amount slashed out of `unit`
+		let affected_balance = value + unit * 4;
+		let ratio = Perquintill::from_rational_with_rounding(slash, affected_balance, Rounding::Up)
+			.unwrap();
+		// `unit` after the slash is applied
+		let unit_slashed = {
+			let unit_slash = ratio.mul_ceil(unit);
+			unit - unit_slash
+		};
+		let value_slashed = {
+			let value_slash = ratio.mul_ceil(value);
+			value - value_slash
+		};
+		assert_eq!(ledger.active, unit_slashed);
+		assert_eq!(ledger.unlocking, vec![c(5, value_slashed), c(7, 32)]);
+		assert_eq!(ledger.total, value_slashed + 32);
+		assert_eq!(LedgerSlashPerEra::get().0, 0);
+		assert_eq!(
+			LedgerSlashPerEra::get().1,
+			BTreeMap::from([(4, 0), (5, value_slashed), (6, 0), (7, 32)])
+		);
+	});
 }
 
 #[test]
@@ -7126,7 +7203,7 @@ mod staking_unchecked {
 	fn virtual_bond_does_not_lock() {
 		ExtBuilder::default().build_and_execute(|| {
 			mock::start_active_era(1);
-			assert_eq!(asset::stakeable_balance::<Test>(&10), 1);
+			assert_eq!(asset::total_balance::<Test>(&10), 1);
 			// 10 can bond more than its balance amount since we do not require lock for virtual
 			// bonding.
 			assert_ok!(<Staking as StakingUnchecked>::virtual_bond(&10, 100, &15));
@@ -7265,7 +7342,7 @@ mod staking_unchecked {
 			assert_eq!(asset::staked::<Test>(&200), 1000);
 
 			// migrate them to virtual staker
-			<Staking as StakingUnchecked>::migrate_to_virtual_staker(&200);
+			assert_ok!(<Staking as StakingUnchecked>::migrate_to_virtual_staker(&200));
 			// payee needs to be updated to a non-stash account.
 			assert_ok!(<Staking as StakingInterface>::set_payee(&200, &201));
 
@@ -7292,7 +7369,7 @@ mod staking_unchecked {
 				// 101 is a nominator for 11
 				assert_eq!(initial_exposure.others.first().unwrap().who, 101);
 				// make 101 a virtual nominator
-				<Staking as StakingUnchecked>::migrate_to_virtual_staker(&101);
+				assert_ok!(<Staking as StakingUnchecked>::migrate_to_virtual_staker(&101));
 				// set payee different to self.
 				assert_ok!(<Staking as StakingInterface>::set_payee(&101, &102));
 
@@ -7367,7 +7444,7 @@ mod staking_unchecked {
 				// 101 is a nominator for 11
 				assert_eq!(initial_exposure.others.first().unwrap().who, 101);
 				// make 101 a virtual nominator
-				<Staking as StakingUnchecked>::migrate_to_virtual_staker(&101);
+				assert_ok!(<Staking as StakingUnchecked>::migrate_to_virtual_staker(&101));
 				// set payee different to self.
 				assert_ok!(<Staking as StakingInterface>::set_payee(&101, &102));
 
@@ -7423,7 +7500,7 @@ mod staking_unchecked {
 			// 333 is corrupted
 			assert_eq!(Staking::inspect_bond_state(&333).unwrap(), LedgerIntegrityState::Corrupted);
 			// migrate to virtual staker.
-			<Staking as StakingUnchecked>::migrate_to_virtual_staker(&333);
+			assert_ok!(<Staking as StakingUnchecked>::migrate_to_virtual_staker(&333));
 
 			// recover the ledger won't work for virtual staker
 			assert_noop!(
@@ -8034,8 +8111,7 @@ mod ledger_recovery {
 			// side effects on 333 - ledger, bonded, payee, lock should be intact.
 			assert_eq!(asset::staked::<Test>(&333), lock_333_before); // OK
 			assert_eq!(Bonded::<Test>::get(&333), Some(444)); // OK
-			assert!(Payee::<Test>::get(&333).is_some()); // OK
-
+			assert!(Payee::<Test>::get(&333).is_some());
 			// however, ledger associated with its controller was killed.
 			assert!(Ledger::<Test>::get(&444).is_none()); // NOK
 
@@ -9081,3 +9157,249 @@ mod getters {
 		});
 	}
 }
+
+mod hold_migration {
+	use super::*;
+	use sp_staking::{Stake, StakingInterface};
+
+	#[test]
+	fn ledger_update_creates_hold() {
+		ExtBuilder::default().has_stakers(true).build_and_execute(|| {
+			// GIVEN alice who is a nominator with old currency
+			let alice = 300;
+			bond_nominator(alice, 1000, vec![11]);
+			assert_eq!(asset::staked::<Test>(&alice), 1000);
+			assert_eq!(Balances::balance_locked(STAKING_ID, &alice), 0);
+			// migrate alice currency to legacy locks
+			testing_utils::migrate_to_old_currency::<Test>(alice);
+			// no more holds
+			assert_eq!(asset::staked::<Test>(&alice), 0);
+			assert_eq!(Balances::balance_locked(STAKING_ID, &alice), 1000);
+			assert_eq!(
+				<Staking as StakingInterface>::stake(&alice),
+				Ok(Stake { total: 1000, active: 1000 })
+			);
+
+			// any ledger mutation should create a hold
+			hypothetically!({
+				// give some extra balance to alice.
+				let _ = asset::mint_into_existing::<Test>(&alice, 100);
+
+				// WHEN new fund is bonded to ledger.
+				assert_ok!(Staking::bond_extra(RuntimeOrigin::signed(alice), 100));
+
+				// THEN new hold is created
+				assert_eq!(asset::staked::<Test>(&alice), 1000 + 100);
+				assert_eq!(
+					<Staking as StakingInterface>::stake(&alice),
+					Ok(Stake { total: 1100, active: 1100 })
+				);
+
+				// old locked balance is untouched
+				assert_eq!(Balances::balance_locked(STAKING_ID, &alice), 1000);
+			});
+
+			hypothetically!({
+				// WHEN new fund is unbonded from ledger.
+				assert_ok!(Staking::unbond(RuntimeOrigin::signed(alice), 100));
+
+				// THEN hold is updated.
+				assert_eq!(asset::staked::<Test>(&alice), 1000);
+				assert_eq!(
+					<Staking as StakingInterface>::stake(&alice),
+					Ok(Stake { total: 1000, active: 900 })
+				);
+
+				// old locked balance is untouched
+				assert_eq!(Balances::balance_locked(STAKING_ID, &alice), 1000);
+			});
+
+			// WHEN alice currency is migrated.
+			assert_ok!(Staking::migrate_currency(RuntimeOrigin::signed(1), alice));
+
+			// THEN hold is updated.
+			assert_eq!(asset::staked::<Test>(&alice), 1000);
+			assert_eq!(
+				<Staking as StakingInterface>::stake(&alice),
+				Ok(Stake { total: 1000, active: 1000 })
+			);
+
+			// ensure cannot migrate again.
+			assert_noop!(
+				Staking::migrate_currency(RuntimeOrigin::signed(1), alice),
+				Error::<Test>::AlreadyMigrated
+			);
+
+			// locked balance is removed
+			assert_eq!(Balances::balance_locked(STAKING_ID, &alice), 0);
+		});
+	}
+
+	#[test]
+	fn migrate_removes_old_lock() {
+		ExtBuilder::default().has_stakers(true).build_and_execute(|| {
+			// GIVEN alice who is a nominator with old currency
+			let alice = 300;
+			bond_nominator(alice, 1000, vec![11]);
+			testing_utils::migrate_to_old_currency::<Test>(alice);
+			assert_eq!(asset::staked::<Test>(&alice), 0);
+			assert_eq!(Balances::balance_locked(STAKING_ID, &alice), 1000);
+			let pre_migrate_consumer = System::consumers(&alice);
+			System::reset_events();
+
+			// WHEN alice currency is migrated.
+			assert_ok!(Staking::migrate_currency(RuntimeOrigin::signed(1), alice));
+
+			// THEN
+			// the extra consumer from old code is removed.
+			assert_eq!(System::consumers(&alice), pre_migrate_consumer - 1);
+			// ensure no lock
+			assert_eq!(Balances::balance_locked(STAKING_ID, &alice), 0);
+			// ensure stake and hold are same.
+			assert_eq!(
+				<Staking as StakingInterface>::stake(&alice),
+				Ok(Stake { total: 1000, active: 1000 })
+			);
+			assert_eq!(asset::staked::<Test>(&alice), 1000);
+			// ensure events are emitted.
+			assert_eq!(
+				staking_events_since_last_call(),
+				vec![Event::CurrencyMigrated { stash: alice, force_withdraw: 0 }]
+			);
+
+			// ensure cannot migrate again.
+			assert_noop!(
+				Staking::migrate_currency(RuntimeOrigin::signed(1), alice),
+				Error::<Test>::AlreadyMigrated
+			);
+		});
+	}
+	#[test]
+	fn cannot_hold_all_stake() {
+		// When there is not enough funds to hold all stake, part of the stake if force withdrawn.
+		// At end of the migration, the stake and hold should be same.
+		ExtBuilder::default().has_stakers(true).build_and_execute(|| {
+			// GIVEN alice who is a nominator with old currency.
+			let alice = 300;
+			let stake = 1000;
+			bond_nominator(alice, stake, vec![11]);
+			testing_utils::migrate_to_old_currency::<Test>(alice);
+			assert_eq!(asset::staked::<Test>(&alice), 0);
+			assert_eq!(Balances::balance_locked(STAKING_ID, &alice), stake);
+			// ledger has 1000 staked.
+			assert_eq!(
+				<Staking as StakingInterface>::stake(&alice),
+				Ok(Stake { total: stake, active: stake })
+			);
+
+			// Get rid of the extra ED to emulate all their balance including ED is staked.
+			assert_ok!(Balances::transfer_allow_death(
+				RuntimeOrigin::signed(alice),
+				10,
+				ExistentialDeposit::get()
+			));
+
+			let expected_force_withdraw = ExistentialDeposit::get();
+
+			// ledger mutation would fail in this case before migration because of failing hold.
+			assert_noop!(
+				Staking::unbond(RuntimeOrigin::signed(alice), 100),
+				Error::<Test>::NotEnoughFunds
+			);
+
+			// clear events
+			System::reset_events();
+
+			// WHEN alice currency is migrated.
+			assert_ok!(Staking::migrate_currency(RuntimeOrigin::signed(1), alice));
+
+			// THEN
+			let expected_hold = stake - expected_force_withdraw;
+			// ensure no lock
+			assert_eq!(Balances::balance_locked(STAKING_ID, &alice), 0);
+			// ensure stake and hold are same.
+			assert_eq!(
+				<Staking as StakingInterface>::stake(&alice),
+				Ok(Stake { total: expected_hold, active: expected_hold })
+			);
+			assert_eq!(asset::staked::<Test>(&alice), expected_hold);
+			// ensure events are emitted.
+			assert_eq!(
+				staking_events_since_last_call(),
+				vec![Event::CurrencyMigrated {
+					stash: alice,
+					force_withdraw: expected_force_withdraw
+				}]
+			);
+
+			// ensure cannot migrate again.
+			assert_noop!(
+				Staking::migrate_currency(RuntimeOrigin::signed(1), alice),
+				Error::<Test>::AlreadyMigrated
+			);
+
+			// unbond works after migration.
+			assert_ok!(Staking::unbond(RuntimeOrigin::signed(alice), 100));
+		});
+	}
+
+	#[test]
+	fn virtual_staker_consumer_provider_dec() {
+		// Ensure virtual stakers consumer and provider count is decremented.
+		ExtBuilder::default().has_stakers(true).build_and_execute(|| {
+			// 200 virtual bonds
+			bond_virtual_nominator(200, 201, 500, vec![11, 21]);
+
+			// previously the virtual nominator had a provider inc by the delegation system as
+			// well as a consumer by this pallet.
+			System::inc_providers(&200);
+			System::inc_consumers(&200).expect("has provider, can consume");
+
+			hypothetically!({
+				// migrate 200
+				assert_ok!(Staking::migrate_currency(RuntimeOrigin::signed(1), 200));
+
+				// ensure account does not exist in system anymore.
+				assert_eq!(System::consumers(&200), 0);
+				assert_eq!(System::providers(&200), 0);
+				assert!(!System::account_exists(&200));
+
+				// ensure cannot migrate again.
+				assert_noop!(
+					Staking::migrate_currency(RuntimeOrigin::signed(1), 200),
+					Error::<Test>::AlreadyMigrated
+				);
+			});
+
+			hypothetically!({
+				// 200 has an erroneously extra provider
+				System::inc_providers(&200);
+
+				// causes migration to fail.
+				assert_noop!(
+					Staking::migrate_currency(RuntimeOrigin::signed(1), 200),
+					Error::<Test>::BadState
+				);
+			});
+
+			// 200 is funded for more than ED by a random account.
+			assert_ok!(Balances::transfer_allow_death(RuntimeOrigin::signed(999), 200, 10));
+
+			// it has an extra provider now.
+			assert_eq!(System::providers(&200), 2);
+
+			// migrate 200
+			assert_ok!(Staking::migrate_currency(RuntimeOrigin::signed(1), 200));
+
+			// 1 provider is left, consumers is 0.
+			assert_eq!(System::providers(&200), 1);
+			assert_eq!(System::consumers(&200), 0);
+
+			// ensure cannot migrate again.
+			assert_noop!(
+				Staking::migrate_currency(RuntimeOrigin::signed(1), 200),
+				Error::<Test>::AlreadyMigrated
+			);
+		});
+	}
+}
diff --git a/substrate/frame/staking/src/weights.rs b/substrate/frame/staking/src/weights.rs
index 56f561679cfc78aee1cca7f1a5b759e17b2f93d9..02ccdacb01c4295f29e4735cda32704e2438f828 100644
--- a/substrate/frame/staking/src/weights.rs
+++ b/substrate/frame/staking/src/weights.rs
@@ -18,27 +18,25 @@
 //! Autogenerated weights for `pallet_staking`
 //!
 //! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 32.0.0
-//! DATE: 2024-04-09, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]`
+//! DATE: 2024-09-17, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]`
 //! WORST CASE MAP SIZE: `1000000`
-//! HOSTNAME: `runner-anb7yjbi-project-674-concurrent-0`, CPU: `Intel(R) Xeon(R) CPU @ 2.60GHz`
+//! HOSTNAME: `runner-obbyq9g6-project-674-concurrent-0`, CPU: `Intel(R) Xeon(R) CPU @ 2.60GHz`
 //! WASM-EXECUTION: `Compiled`, CHAIN: `Some("dev")`, DB CACHE: `1024`
 
 // Executed Command:
-// ./target/production/substrate-node
+// target/production/substrate-node
 // benchmark
 // pallet
-// --chain=dev
 // --steps=50
 // --repeat=20
-// --pallet=pallet_staking
-// --no-storage-info
-// --no-median-slopes
-// --no-min-squares
 // --extrinsic=*
 // --wasm-execution=compiled
 // --heap-pages=4096
-// --output=./substrate/frame/staking/src/weights.rs
+// --json-file=/builds/parity/mirrors/polkadot-sdk/.git/.artifacts/bench.json
+// --pallet=pallet_staking
+// --chain=dev
 // --header=./substrate/HEADER-APACHE2
+// --output=./substrate/frame/staking/src/weights.rs
 // --template=./substrate/.maintain/frame-weight-template.hbs
 
 #![cfg_attr(rustfmt, rustfmt_skip)]
@@ -83,6 +81,7 @@ pub trait WeightInfo {
 	fn force_apply_min_commission() -> Weight;
 	fn set_min_commission() -> Weight;
 	fn restore_ledger() -> Weight;
+	fn migrate_currency() -> Weight;
 }
 
 /// Weights for `pallet_staking` using the Substrate node and recommended hardware.
@@ -92,18 +91,18 @@ impl<T: frame_system::Config> WeightInfo for SubstrateWeight<T> {
 	/// Proof: `Staking::Bonded` (`max_values`: None, `max_size`: Some(72), added: 2547, mode: `MaxEncodedLen`)
 	/// Storage: `Staking::Ledger` (r:1 w:1)
 	/// Proof: `Staking::Ledger` (`max_values`: None, `max_size`: Some(1091), added: 3566, mode: `MaxEncodedLen`)
-	/// Storage: `Balances::Locks` (r:1 w:1)
-	/// Proof: `Balances::Locks` (`max_values`: None, `max_size`: Some(1299), added: 3774, mode: `MaxEncodedLen`)
-	/// Storage: `Balances::Freezes` (r:1 w:0)
-	/// Proof: `Balances::Freezes` (`max_values`: None, `max_size`: Some(67), added: 2542, mode: `MaxEncodedLen`)
+	/// Storage: `Balances::Holds` (r:1 w:1)
+	/// Proof: `Balances::Holds` (`max_values`: None, `max_size`: Some(355), added: 2830, mode: `MaxEncodedLen`)
+	/// Storage: `Staking::VirtualStakers` (r:1 w:0)
+	/// Proof: `Staking::VirtualStakers` (`max_values`: None, `max_size`: Some(40), added: 2515, mode: `MaxEncodedLen`)
 	/// Storage: `Staking::Payee` (r:0 w:1)
 	/// Proof: `Staking::Payee` (`max_values`: None, `max_size`: Some(73), added: 2548, mode: `MaxEncodedLen`)
 	fn bond() -> Weight {
 		// Proof Size summary in bytes:
-		//  Measured:  `1042`
-		//  Estimated: `4764`
-		// Minimum execution time: 46_504_000 picoseconds.
-		Weight::from_parts(48_459_000, 4764)
+		//  Measured:  `1068`
+		//  Estimated: `4556`
+		// Minimum execution time: 71_854_000 picoseconds.
+		Weight::from_parts(73_408_000, 4556)
 			.saturating_add(T::DbWeight::get().reads(4_u64))
 			.saturating_add(T::DbWeight::get().writes(4_u64))
 	}
@@ -111,20 +110,20 @@ impl<T: frame_system::Config> WeightInfo for SubstrateWeight<T> {
 	/// Proof: `Staking::Bonded` (`max_values`: None, `max_size`: Some(72), added: 2547, mode: `MaxEncodedLen`)
 	/// Storage: `Staking::Ledger` (r:1 w:1)
 	/// Proof: `Staking::Ledger` (`max_values`: None, `max_size`: Some(1091), added: 3566, mode: `MaxEncodedLen`)
-	/// Storage: `Balances::Locks` (r:1 w:1)
-	/// Proof: `Balances::Locks` (`max_values`: None, `max_size`: Some(1299), added: 3774, mode: `MaxEncodedLen`)
-	/// Storage: `Balances::Freezes` (r:1 w:0)
-	/// Proof: `Balances::Freezes` (`max_values`: None, `max_size`: Some(67), added: 2542, mode: `MaxEncodedLen`)
+	/// Storage: `Staking::VirtualStakers` (r:1 w:0)
+	/// Proof: `Staking::VirtualStakers` (`max_values`: None, `max_size`: Some(40), added: 2515, mode: `MaxEncodedLen`)
+	/// Storage: `Balances::Holds` (r:1 w:1)
+	/// Proof: `Balances::Holds` (`max_values`: None, `max_size`: Some(355), added: 2830, mode: `MaxEncodedLen`)
 	/// Storage: `VoterList::ListNodes` (r:3 w:3)
 	/// Proof: `VoterList::ListNodes` (`max_values`: None, `max_size`: Some(154), added: 2629, mode: `MaxEncodedLen`)
 	/// Storage: `VoterList::ListBags` (r:2 w:2)
 	/// Proof: `VoterList::ListBags` (`max_values`: None, `max_size`: Some(82), added: 2557, mode: `MaxEncodedLen`)
 	fn bond_extra() -> Weight {
 		// Proof Size summary in bytes:
-		//  Measured:  `1990`
+		//  Measured:  `2049`
 		//  Estimated: `8877`
-		// Minimum execution time: 90_475_000 picoseconds.
-		Weight::from_parts(93_619_000, 8877)
+		// Minimum execution time: 127_442_000 picoseconds.
+		Weight::from_parts(130_845_000, 8877)
 			.saturating_add(T::DbWeight::get().reads(9_u64))
 			.saturating_add(T::DbWeight::get().writes(7_u64))
 	}
@@ -138,22 +137,22 @@ impl<T: frame_system::Config> WeightInfo for SubstrateWeight<T> {
 	/// Proof: `Staking::MinNominatorBond` (`max_values`: Some(1), `max_size`: Some(16), added: 511, mode: `MaxEncodedLen`)
 	/// Storage: `Staking::CurrentEra` (r:1 w:0)
 	/// Proof: `Staking::CurrentEra` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`)
-	/// Storage: `Balances::Locks` (r:1 w:1)
-	/// Proof: `Balances::Locks` (`max_values`: None, `max_size`: Some(1299), added: 3774, mode: `MaxEncodedLen`)
-	/// Storage: `Balances::Freezes` (r:1 w:0)
-	/// Proof: `Balances::Freezes` (`max_values`: None, `max_size`: Some(67), added: 2542, mode: `MaxEncodedLen`)
+	/// Storage: `Staking::VirtualStakers` (r:1 w:0)
+	/// Proof: `Staking::VirtualStakers` (`max_values`: None, `max_size`: Some(40), added: 2515, mode: `MaxEncodedLen`)
+	/// Storage: `Balances::Holds` (r:1 w:0)
+	/// Proof: `Balances::Holds` (`max_values`: None, `max_size`: Some(355), added: 2830, mode: `MaxEncodedLen`)
 	/// Storage: `VoterList::ListNodes` (r:3 w:3)
 	/// Proof: `VoterList::ListNodes` (`max_values`: None, `max_size`: Some(154), added: 2629, mode: `MaxEncodedLen`)
 	/// Storage: `VoterList::ListBags` (r:2 w:2)
 	/// Proof: `VoterList::ListBags` (`max_values`: None, `max_size`: Some(82), added: 2557, mode: `MaxEncodedLen`)
 	fn unbond() -> Weight {
 		// Proof Size summary in bytes:
-		//  Measured:  `2195`
+		//  Measured:  `2151`
 		//  Estimated: `8877`
-		// Minimum execution time: 99_335_000 picoseconds.
-		Weight::from_parts(101_440_000, 8877)
+		// Minimum execution time: 105_259_000 picoseconds.
+		Weight::from_parts(107_112_000, 8877)
 			.saturating_add(T::DbWeight::get().reads(12_u64))
-			.saturating_add(T::DbWeight::get().writes(7_u64))
+			.saturating_add(T::DbWeight::get().writes(6_u64))
 	}
 	/// Storage: `Staking::Ledger` (r:1 w:1)
 	/// Proof: `Staking::Ledger` (`max_values`: None, `max_size`: Some(1091), added: 3566, mode: `MaxEncodedLen`)
@@ -161,21 +160,21 @@ impl<T: frame_system::Config> WeightInfo for SubstrateWeight<T> {
 	/// Proof: `Staking::Bonded` (`max_values`: None, `max_size`: Some(72), added: 2547, mode: `MaxEncodedLen`)
 	/// Storage: `Staking::CurrentEra` (r:1 w:0)
 	/// Proof: `Staking::CurrentEra` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`)
-	/// Storage: `Balances::Locks` (r:1 w:1)
-	/// Proof: `Balances::Locks` (`max_values`: None, `max_size`: Some(1299), added: 3774, mode: `MaxEncodedLen`)
-	/// Storage: `Balances::Freezes` (r:1 w:0)
-	/// Proof: `Balances::Freezes` (`max_values`: None, `max_size`: Some(67), added: 2542, mode: `MaxEncodedLen`)
+	/// Storage: `Staking::VirtualStakers` (r:1 w:0)
+	/// Proof: `Staking::VirtualStakers` (`max_values`: None, `max_size`: Some(40), added: 2515, mode: `MaxEncodedLen`)
+	/// Storage: `Balances::Holds` (r:1 w:1)
+	/// Proof: `Balances::Holds` (`max_values`: None, `max_size`: Some(355), added: 2830, mode: `MaxEncodedLen`)
 	/// Storage: `NominationPools::ReversePoolIdLookup` (r:1 w:0)
 	/// Proof: `NominationPools::ReversePoolIdLookup` (`max_values`: None, `max_size`: Some(44), added: 2519, mode: `MaxEncodedLen`)
 	/// The range of component `s` is `[0, 100]`.
 	fn withdraw_unbonded_update(s: u32, ) -> Weight {
 		// Proof Size summary in bytes:
-		//  Measured:  `1297`
-		//  Estimated: `4764`
-		// Minimum execution time: 50_067_000 picoseconds.
-		Weight::from_parts(52_396_327, 4764)
-			// Standard Error: 1_419
-			.saturating_add(Weight::from_parts(51_406, 0).saturating_mul(s.into()))
+		//  Measured:  `1393`
+		//  Estimated: `4556`
+		// Minimum execution time: 77_158_000 picoseconds.
+		Weight::from_parts(79_140_122, 4556)
+			// Standard Error: 1_688
+			.saturating_add(Weight::from_parts(62_663, 0).saturating_mul(s.into()))
 			.saturating_add(T::DbWeight::get().reads(6_u64))
 			.saturating_add(T::DbWeight::get().writes(2_u64))
 	}
@@ -187,10 +186,10 @@ impl<T: frame_system::Config> WeightInfo for SubstrateWeight<T> {
 	/// Proof: `Staking::CurrentEra` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`)
 	/// Storage: `Staking::SlashingSpans` (r:1 w:1)
 	/// Proof: `Staking::SlashingSpans` (`max_values`: None, `max_size`: None, mode: `Measured`)
-	/// Storage: `Balances::Locks` (r:1 w:1)
-	/// Proof: `Balances::Locks` (`max_values`: None, `max_size`: Some(1299), added: 3774, mode: `MaxEncodedLen`)
-	/// Storage: `Balances::Freezes` (r:1 w:0)
-	/// Proof: `Balances::Freezes` (`max_values`: None, `max_size`: Some(67), added: 2542, mode: `MaxEncodedLen`)
+	/// Storage: `Staking::VirtualStakers` (r:1 w:1)
+	/// Proof: `Staking::VirtualStakers` (`max_values`: None, `max_size`: Some(40), added: 2515, mode: `MaxEncodedLen`)
+	/// Storage: `Balances::Holds` (r:1 w:1)
+	/// Proof: `Balances::Holds` (`max_values`: None, `max_size`: Some(355), added: 2830, mode: `MaxEncodedLen`)
 	/// Storage: `Staking::Validators` (r:1 w:0)
 	/// Proof: `Staking::Validators` (`max_values`: None, `max_size`: Some(45), added: 2520, mode: `MaxEncodedLen`)
 	/// Storage: `Staking::Nominators` (r:1 w:1)
@@ -210,14 +209,14 @@ impl<T: frame_system::Config> WeightInfo for SubstrateWeight<T> {
 	/// The range of component `s` is `[0, 100]`.
 	fn withdraw_unbonded_kill(s: u32, ) -> Weight {
 		// Proof Size summary in bytes:
-		//  Measured:  `2196 + s * (4 ±0)`
+		//  Measured:  `2255 + s * (4 ±0)`
 		//  Estimated: `6248 + s * (4 ±0)`
-		// Minimum execution time: 92_931_000 picoseconds.
-		Weight::from_parts(101_398_156, 6248)
-			// Standard Error: 4_180
-			.saturating_add(Weight::from_parts(1_377_850, 0).saturating_mul(s.into()))
+		// Minimum execution time: 125_396_000 picoseconds.
+		Weight::from_parts(134_915_543, 6248)
+			// Standard Error: 3_660
+			.saturating_add(Weight::from_parts(1_324_736, 0).saturating_mul(s.into()))
 			.saturating_add(T::DbWeight::get().reads(13_u64))
-			.saturating_add(T::DbWeight::get().writes(11_u64))
+			.saturating_add(T::DbWeight::get().writes(12_u64))
 			.saturating_add(T::DbWeight::get().writes((1_u64).saturating_mul(s.into())))
 			.saturating_add(Weight::from_parts(0, 4).saturating_mul(s.into()))
 	}
@@ -245,10 +244,10 @@ impl<T: frame_system::Config> WeightInfo for SubstrateWeight<T> {
 	/// Proof: `Staking::CounterForValidators` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`)
 	fn validate() -> Weight {
 		// Proof Size summary in bytes:
-		//  Measured:  `1372`
+		//  Measured:  `1438`
 		//  Estimated: `4556`
-		// Minimum execution time: 56_291_000 picoseconds.
-		Weight::from_parts(58_372_000, 4556)
+		// Minimum execution time: 68_826_000 picoseconds.
+		Weight::from_parts(71_261_000, 4556)
 			.saturating_add(T::DbWeight::get().reads(11_u64))
 			.saturating_add(T::DbWeight::get().writes(5_u64))
 	}
@@ -261,12 +260,12 @@ impl<T: frame_system::Config> WeightInfo for SubstrateWeight<T> {
 	/// The range of component `k` is `[1, 128]`.
 	fn kick(k: u32, ) -> Weight {
 		// Proof Size summary in bytes:
-		//  Measured:  `1815 + k * (572 ±0)`
+		//  Measured:  `1848 + k * (572 ±0)`
 		//  Estimated: `4556 + k * (3033 ±0)`
-		// Minimum execution time: 36_218_000 picoseconds.
-		Weight::from_parts(38_811_308, 4556)
-			// Standard Error: 8_352
-			.saturating_add(Weight::from_parts(6_527_398, 0).saturating_mul(k.into()))
+		// Minimum execution time: 46_082_000 picoseconds.
+		Weight::from_parts(49_541_374, 4556)
+			// Standard Error: 7_218
+			.saturating_add(Weight::from_parts(7_281_079, 0).saturating_mul(k.into()))
 			.saturating_add(T::DbWeight::get().reads(2_u64))
 			.saturating_add(T::DbWeight::get().reads((1_u64).saturating_mul(k.into())))
 			.saturating_add(T::DbWeight::get().writes((1_u64).saturating_mul(k.into())))
@@ -297,12 +296,12 @@ impl<T: frame_system::Config> WeightInfo for SubstrateWeight<T> {
 	/// The range of component `n` is `[1, 16]`.
 	fn nominate(n: u32, ) -> Weight {
 		// Proof Size summary in bytes:
-		//  Measured:  `1866 + n * (102 ±0)`
+		//  Measured:  `1932 + n * (102 ±0)`
 		//  Estimated: `6248 + n * (2520 ±0)`
-		// Minimum execution time: 68_607_000 picoseconds.
-		Weight::from_parts(66_831_185, 6248)
-			// Standard Error: 14_014
-			.saturating_add(Weight::from_parts(4_031_635, 0).saturating_mul(n.into()))
+		// Minimum execution time: 83_854_000 picoseconds.
+		Weight::from_parts(81_387_241, 6248)
+			// Standard Error: 16_811
+			.saturating_add(Weight::from_parts(4_900_554, 0).saturating_mul(n.into()))
 			.saturating_add(T::DbWeight::get().reads(12_u64))
 			.saturating_add(T::DbWeight::get().reads((1_u64).saturating_mul(n.into())))
 			.saturating_add(T::DbWeight::get().writes(6_u64))
@@ -326,10 +325,10 @@ impl<T: frame_system::Config> WeightInfo for SubstrateWeight<T> {
 	/// Proof: `VoterList::CounterForListNodes` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`)
 	fn chill() -> Weight {
 		// Proof Size summary in bytes:
-		//  Measured:  `1816`
+		//  Measured:  `1882`
 		//  Estimated: `6248`
-		// Minimum execution time: 60_088_000 picoseconds.
-		Weight::from_parts(62_471_000, 6248)
+		// Minimum execution time: 73_939_000 picoseconds.
+		Weight::from_parts(75_639_000, 6248)
 			.saturating_add(T::DbWeight::get().reads(9_u64))
 			.saturating_add(T::DbWeight::get().writes(6_u64))
 	}
@@ -341,10 +340,10 @@ impl<T: frame_system::Config> WeightInfo for SubstrateWeight<T> {
 	/// Proof: `Staking::Payee` (`max_values`: None, `max_size`: Some(73), added: 2548, mode: `MaxEncodedLen`)
 	fn set_payee() -> Weight {
 		// Proof Size summary in bytes:
-		//  Measured:  `902`
+		//  Measured:  `935`
 		//  Estimated: `4556`
-		// Minimum execution time: 19_777_000 picoseconds.
-		Weight::from_parts(20_690_000, 4556)
+		// Minimum execution time: 24_592_000 picoseconds.
+		Weight::from_parts(25_092_000, 4556)
 			.saturating_add(T::DbWeight::get().reads(2_u64))
 			.saturating_add(T::DbWeight::get().writes(1_u64))
 	}
@@ -356,10 +355,10 @@ impl<T: frame_system::Config> WeightInfo for SubstrateWeight<T> {
 	/// Proof: `Staking::Payee` (`max_values`: None, `max_size`: Some(73), added: 2548, mode: `MaxEncodedLen`)
 	fn update_payee() -> Weight {
 		// Proof Size summary in bytes:
-		//  Measured:  `969`
+		//  Measured:  `1002`
 		//  Estimated: `4556`
-		// Minimum execution time: 23_705_000 picoseconds.
-		Weight::from_parts(24_409_000, 4556)
+		// Minimum execution time: 29_735_000 picoseconds.
+		Weight::from_parts(30_546_000, 4556)
 			.saturating_add(T::DbWeight::get().reads(3_u64))
 			.saturating_add(T::DbWeight::get().writes(1_u64))
 	}
@@ -369,10 +368,10 @@ impl<T: frame_system::Config> WeightInfo for SubstrateWeight<T> {
 	/// Proof: `Staking::Ledger` (`max_values`: None, `max_size`: Some(1091), added: 3566, mode: `MaxEncodedLen`)
 	fn set_controller() -> Weight {
 		// Proof Size summary in bytes:
-		//  Measured:  `902`
+		//  Measured:  `935`
 		//  Estimated: `8122`
-		// Minimum execution time: 23_479_000 picoseconds.
-		Weight::from_parts(24_502_000, 8122)
+		// Minimum execution time: 28_728_000 picoseconds.
+		Weight::from_parts(29_709_000, 8122)
 			.saturating_add(T::DbWeight::get().reads(3_u64))
 			.saturating_add(T::DbWeight::get().writes(3_u64))
 	}
@@ -382,8 +381,8 @@ impl<T: frame_system::Config> WeightInfo for SubstrateWeight<T> {
 		// Proof Size summary in bytes:
 		//  Measured:  `0`
 		//  Estimated: `0`
-		// Minimum execution time: 2_675_000 picoseconds.
-		Weight::from_parts(2_802_000, 0)
+		// Minimum execution time: 2_519_000 picoseconds.
+		Weight::from_parts(2_673_000, 0)
 			.saturating_add(T::DbWeight::get().writes(1_u64))
 	}
 	/// Storage: `Staking::ForceEra` (r:0 w:1)
@@ -392,8 +391,8 @@ impl<T: frame_system::Config> WeightInfo for SubstrateWeight<T> {
 		// Proof Size summary in bytes:
 		//  Measured:  `0`
 		//  Estimated: `0`
-		// Minimum execution time: 7_067_000 picoseconds.
-		Weight::from_parts(7_413_000, 0)
+		// Minimum execution time: 8_050_000 picoseconds.
+		Weight::from_parts(8_268_000, 0)
 			.saturating_add(T::DbWeight::get().writes(1_u64))
 	}
 	/// Storage: `Staking::ForceEra` (r:0 w:1)
@@ -402,8 +401,8 @@ impl<T: frame_system::Config> WeightInfo for SubstrateWeight<T> {
 		// Proof Size summary in bytes:
 		//  Measured:  `0`
 		//  Estimated: `0`
-		// Minimum execution time: 6_977_000 picoseconds.
-		Weight::from_parts(7_353_000, 0)
+		// Minimum execution time: 8_131_000 picoseconds.
+		Weight::from_parts(8_349_000, 0)
 			.saturating_add(T::DbWeight::get().writes(1_u64))
 	}
 	/// Storage: `Staking::ForceEra` (r:0 w:1)
@@ -412,8 +411,8 @@ impl<T: frame_system::Config> WeightInfo for SubstrateWeight<T> {
 		// Proof Size summary in bytes:
 		//  Measured:  `0`
 		//  Estimated: `0`
-		// Minimum execution time: 7_071_000 picoseconds.
-		Weight::from_parts(7_463_000, 0)
+		// Minimum execution time: 8_104_000 picoseconds.
+		Weight::from_parts(8_317_000, 0)
 			.saturating_add(T::DbWeight::get().writes(1_u64))
 	}
 	/// Storage: `Staking::Invulnerables` (r:0 w:1)
@@ -423,10 +422,10 @@ impl<T: frame_system::Config> WeightInfo for SubstrateWeight<T> {
 		// Proof Size summary in bytes:
 		//  Measured:  `0`
 		//  Estimated: `0`
-		// Minimum execution time: 2_833_000 picoseconds.
-		Weight::from_parts(3_328_130, 0)
-			// Standard Error: 30
-			.saturating_add(Weight::from_parts(10_058, 0).saturating_mul(v.into()))
+		// Minimum execution time: 2_669_000 picoseconds.
+		Weight::from_parts(3_013_436, 0)
+			// Standard Error: 31
+			.saturating_add(Weight::from_parts(10_704, 0).saturating_mul(v.into()))
 			.saturating_add(T::DbWeight::get().writes(1_u64))
 	}
 	/// Storage: `Staking::Ledger` (r:11800 w:11800)
@@ -438,12 +437,12 @@ impl<T: frame_system::Config> WeightInfo for SubstrateWeight<T> {
 	/// The range of component `i` is `[0, 5900]`.
 	fn deprecate_controller_batch(i: u32, ) -> Weight {
 		// Proof Size summary in bytes:
-		//  Measured:  `1746 + i * (229 ±0)`
+		//  Measured:  `1779 + i * (229 ±0)`
 		//  Estimated: `990 + i * (7132 ±0)`
-		// Minimum execution time: 5_300_000 picoseconds.
-		Weight::from_parts(5_437_000, 990)
-			// Standard Error: 66_261
-			.saturating_add(Weight::from_parts(30_172_457, 0).saturating_mul(i.into()))
+		// Minimum execution time: 5_101_000 picoseconds.
+		Weight::from_parts(5_368_000, 990)
+			// Standard Error: 75_180
+			.saturating_add(Weight::from_parts(33_781_643, 0).saturating_mul(i.into()))
 			.saturating_add(T::DbWeight::get().reads((4_u64).saturating_mul(i.into())))
 			.saturating_add(T::DbWeight::get().writes((3_u64).saturating_mul(i.into())))
 			.saturating_add(Weight::from_parts(0, 7132).saturating_mul(i.into()))
@@ -454,10 +453,10 @@ impl<T: frame_system::Config> WeightInfo for SubstrateWeight<T> {
 	/// Proof: `Staking::Bonded` (`max_values`: None, `max_size`: Some(72), added: 2547, mode: `MaxEncodedLen`)
 	/// Storage: `Staking::Ledger` (r:1 w:1)
 	/// Proof: `Staking::Ledger` (`max_values`: None, `max_size`: Some(1091), added: 3566, mode: `MaxEncodedLen`)
-	/// Storage: `Balances::Locks` (r:1 w:1)
-	/// Proof: `Balances::Locks` (`max_values`: None, `max_size`: Some(1299), added: 3774, mode: `MaxEncodedLen`)
-	/// Storage: `Balances::Freezes` (r:1 w:0)
-	/// Proof: `Balances::Freezes` (`max_values`: None, `max_size`: Some(67), added: 2542, mode: `MaxEncodedLen`)
+	/// Storage: `Staking::VirtualStakers` (r:1 w:1)
+	/// Proof: `Staking::VirtualStakers` (`max_values`: None, `max_size`: Some(40), added: 2515, mode: `MaxEncodedLen`)
+	/// Storage: `Balances::Holds` (r:1 w:1)
+	/// Proof: `Balances::Holds` (`max_values`: None, `max_size`: Some(355), added: 2830, mode: `MaxEncodedLen`)
 	/// Storage: `System::Account` (r:1 w:1)
 	/// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`)
 	/// Storage: `Staking::Validators` (r:1 w:0)
@@ -479,14 +478,14 @@ impl<T: frame_system::Config> WeightInfo for SubstrateWeight<T> {
 	/// The range of component `s` is `[0, 100]`.
 	fn force_unstake(s: u32, ) -> Weight {
 		// Proof Size summary in bytes:
-		//  Measured:  `2196 + s * (4 ±0)`
+		//  Measured:  `2255 + s * (4 ±0)`
 		//  Estimated: `6248 + s * (4 ±0)`
-		// Minimum execution time: 87_677_000 picoseconds.
-		Weight::from_parts(96_386_462, 6248)
-			// Standard Error: 3_717
-			.saturating_add(Weight::from_parts(1_370_585, 0).saturating_mul(s.into()))
+		// Minimum execution time: 119_955_000 picoseconds.
+		Weight::from_parts(128_392_032, 6248)
+			// Standard Error: 3_773
+			.saturating_add(Weight::from_parts(1_302_488, 0).saturating_mul(s.into()))
 			.saturating_add(T::DbWeight::get().reads(13_u64))
-			.saturating_add(T::DbWeight::get().writes(12_u64))
+			.saturating_add(T::DbWeight::get().writes(13_u64))
 			.saturating_add(T::DbWeight::get().writes((1_u64).saturating_mul(s.into())))
 			.saturating_add(Weight::from_parts(0, 4).saturating_mul(s.into()))
 	}
@@ -495,12 +494,12 @@ impl<T: frame_system::Config> WeightInfo for SubstrateWeight<T> {
 	/// The range of component `s` is `[1, 1000]`.
 	fn cancel_deferred_slash(s: u32, ) -> Weight {
 		// Proof Size summary in bytes:
-		//  Measured:  `66672`
-		//  Estimated: `70137`
-		// Minimum execution time: 105_086_000 picoseconds.
-		Weight::from_parts(1_167_895_222, 70137)
-			// Standard Error: 77_022
-			.saturating_add(Weight::from_parts(6_487_305, 0).saturating_mul(s.into()))
+		//  Measured:  `66705`
+		//  Estimated: `70170`
+		// Minimum execution time: 139_290_000 picoseconds.
+		Weight::from_parts(959_667_494, 70170)
+			// Standard Error: 56_271
+			.saturating_add(Weight::from_parts(4_798_293, 0).saturating_mul(s.into()))
 			.saturating_add(T::DbWeight::get().reads(1_u64))
 			.saturating_add(T::DbWeight::get().writes(1_u64))
 	}
@@ -518,12 +517,10 @@ impl<T: frame_system::Config> WeightInfo for SubstrateWeight<T> {
 	/// Proof: `Staking::CurrentEra` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`)
 	/// Storage: `Staking::ErasValidatorReward` (r:1 w:0)
 	/// Proof: `Staking::ErasValidatorReward` (`max_values`: None, `max_size`: Some(28), added: 2503, mode: `MaxEncodedLen`)
-	/// Storage: `Balances::Locks` (r:257 w:257)
-	/// Proof: `Balances::Locks` (`max_values`: None, `max_size`: Some(1299), added: 3774, mode: `MaxEncodedLen`)
-	/// Storage: `Balances::Freezes` (r:257 w:0)
-	/// Proof: `Balances::Freezes` (`max_values`: None, `max_size`: Some(67), added: 2542, mode: `MaxEncodedLen`)
-	/// Storage: `System::Account` (r:257 w:257)
-	/// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`)
+	/// Storage: `Staking::VirtualStakers` (r:257 w:0)
+	/// Proof: `Staking::VirtualStakers` (`max_values`: None, `max_size`: Some(40), added: 2515, mode: `MaxEncodedLen`)
+	/// Storage: `Balances::Holds` (r:257 w:257)
+	/// Proof: `Balances::Holds` (`max_values`: None, `max_size`: Some(355), added: 2830, mode: `MaxEncodedLen`)
 	/// Storage: `Staking::ErasStakersPaged` (r:1 w:0)
 	/// Proof: `Staking::ErasStakersPaged` (`max_values`: None, `max_size`: None, mode: `Measured`)
 	/// Storage: `Staking::ErasRewardPoints` (r:1 w:0)
@@ -532,29 +529,31 @@ impl<T: frame_system::Config> WeightInfo for SubstrateWeight<T> {
 	/// Proof: `Staking::ErasValidatorPrefs` (`max_values`: None, `max_size`: Some(57), added: 2532, mode: `MaxEncodedLen`)
 	/// Storage: `Staking::Payee` (r:257 w:0)
 	/// Proof: `Staking::Payee` (`max_values`: None, `max_size`: Some(73), added: 2548, mode: `MaxEncodedLen`)
+	/// Storage: `System::Account` (r:257 w:257)
+	/// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`)
 	/// The range of component `n` is `[0, 256]`.
 	fn payout_stakers_alive_staked(n: u32, ) -> Weight {
 		// Proof Size summary in bytes:
-		//  Measured:  `33297 + n * (377 ±0)`
-		//  Estimated: `30944 + n * (3774 ±3)`
-		// Minimum execution time: 154_210_000 picoseconds.
-		Weight::from_parts(192_836_012, 30944)
-			// Standard Error: 40_441
-			.saturating_add(Weight::from_parts(47_646_642, 0).saturating_mul(n.into()))
+		//  Measured:  `33283 + n * (370 ±0)`
+		//  Estimated: `30958 + n * (3566 ±0)`
+		// Minimum execution time: 193_068_000 picoseconds.
+		Weight::from_parts(252_762_568, 30958)
+			// Standard Error: 22_743
+			.saturating_add(Weight::from_parts(81_185_306, 0).saturating_mul(n.into()))
 			.saturating_add(T::DbWeight::get().reads(14_u64))
 			.saturating_add(T::DbWeight::get().reads((6_u64).saturating_mul(n.into())))
 			.saturating_add(T::DbWeight::get().writes(4_u64))
 			.saturating_add(T::DbWeight::get().writes((3_u64).saturating_mul(n.into())))
-			.saturating_add(Weight::from_parts(0, 3774).saturating_mul(n.into()))
+			.saturating_add(Weight::from_parts(0, 3566).saturating_mul(n.into()))
 	}
 	/// Storage: `Staking::Ledger` (r:1 w:1)
 	/// Proof: `Staking::Ledger` (`max_values`: None, `max_size`: Some(1091), added: 3566, mode: `MaxEncodedLen`)
 	/// Storage: `Staking::Bonded` (r:1 w:0)
 	/// Proof: `Staking::Bonded` (`max_values`: None, `max_size`: Some(72), added: 2547, mode: `MaxEncodedLen`)
-	/// Storage: `Balances::Locks` (r:1 w:1)
-	/// Proof: `Balances::Locks` (`max_values`: None, `max_size`: Some(1299), added: 3774, mode: `MaxEncodedLen`)
-	/// Storage: `Balances::Freezes` (r:1 w:0)
-	/// Proof: `Balances::Freezes` (`max_values`: None, `max_size`: Some(67), added: 2542, mode: `MaxEncodedLen`)
+	/// Storage: `Staking::VirtualStakers` (r:1 w:0)
+	/// Proof: `Staking::VirtualStakers` (`max_values`: None, `max_size`: Some(40), added: 2515, mode: `MaxEncodedLen`)
+	/// Storage: `Balances::Holds` (r:1 w:0)
+	/// Proof: `Balances::Holds` (`max_values`: None, `max_size`: Some(355), added: 2830, mode: `MaxEncodedLen`)
 	/// Storage: `VoterList::ListNodes` (r:3 w:3)
 	/// Proof: `VoterList::ListNodes` (`max_values`: None, `max_size`: Some(154), added: 2629, mode: `MaxEncodedLen`)
 	/// Storage: `VoterList::ListBags` (r:2 w:2)
@@ -562,25 +561,25 @@ impl<T: frame_system::Config> WeightInfo for SubstrateWeight<T> {
 	/// The range of component `l` is `[1, 32]`.
 	fn rebond(l: u32, ) -> Weight {
 		// Proof Size summary in bytes:
-		//  Measured:  `1991 + l * (7 ±0)`
+		//  Measured:  `1947 + l * (7 ±0)`
 		//  Estimated: `8877`
-		// Minimum execution time: 88_337_000 picoseconds.
-		Weight::from_parts(91_391_254, 8877)
-			// Standard Error: 4_485
-			.saturating_add(Weight::from_parts(103_443, 0).saturating_mul(l.into()))
+		// Minimum execution time: 91_151_000 picoseconds.
+		Weight::from_parts(93_596_096, 8877)
+			// Standard Error: 5_313
+			.saturating_add(Weight::from_parts(124_684, 0).saturating_mul(l.into()))
 			.saturating_add(T::DbWeight::get().reads(9_u64))
-			.saturating_add(T::DbWeight::get().writes(7_u64))
+			.saturating_add(T::DbWeight::get().writes(6_u64))
 	}
+	/// Storage: `Staking::VirtualStakers` (r:1 w:1)
+	/// Proof: `Staking::VirtualStakers` (`max_values`: None, `max_size`: Some(40), added: 2515, mode: `MaxEncodedLen`)
 	/// Storage: `Staking::Bonded` (r:1 w:1)
 	/// Proof: `Staking::Bonded` (`max_values`: None, `max_size`: Some(72), added: 2547, mode: `MaxEncodedLen`)
 	/// Storage: `Staking::Ledger` (r:1 w:1)
 	/// Proof: `Staking::Ledger` (`max_values`: None, `max_size`: Some(1091), added: 3566, mode: `MaxEncodedLen`)
 	/// Storage: `Staking::SlashingSpans` (r:1 w:1)
 	/// Proof: `Staking::SlashingSpans` (`max_values`: None, `max_size`: None, mode: `Measured`)
-	/// Storage: `Balances::Locks` (r:1 w:1)
-	/// Proof: `Balances::Locks` (`max_values`: None, `max_size`: Some(1299), added: 3774, mode: `MaxEncodedLen`)
-	/// Storage: `Balances::Freezes` (r:1 w:0)
-	/// Proof: `Balances::Freezes` (`max_values`: None, `max_size`: Some(67), added: 2542, mode: `MaxEncodedLen`)
+	/// Storage: `Balances::Holds` (r:1 w:1)
+	/// Proof: `Balances::Holds` (`max_values`: None, `max_size`: Some(355), added: 2830, mode: `MaxEncodedLen`)
 	/// Storage: `Staking::Validators` (r:1 w:0)
 	/// Proof: `Staking::Validators` (`max_values`: None, `max_size`: Some(45), added: 2520, mode: `MaxEncodedLen`)
 	/// Storage: `Staking::Nominators` (r:1 w:1)
@@ -600,14 +599,14 @@ impl<T: frame_system::Config> WeightInfo for SubstrateWeight<T> {
 	/// The range of component `s` is `[1, 100]`.
 	fn reap_stash(s: u32, ) -> Weight {
 		// Proof Size summary in bytes:
-		//  Measured:  `2196 + s * (4 ±0)`
+		//  Measured:  `2255 + s * (4 ±0)`
 		//  Estimated: `6248 + s * (4 ±0)`
-		// Minimum execution time: 98_014_000 picoseconds.
-		Weight::from_parts(102_537_670, 6248)
-			// Standard Error: 3_324
-			.saturating_add(Weight::from_parts(1_353_142, 0).saturating_mul(s.into()))
+		// Minimum execution time: 133_214_000 picoseconds.
+		Weight::from_parts(137_290_527, 6248)
+			// Standard Error: 4_153
+			.saturating_add(Weight::from_parts(1_291_007, 0).saturating_mul(s.into()))
 			.saturating_add(T::DbWeight::get().reads(12_u64))
-			.saturating_add(T::DbWeight::get().writes(11_u64))
+			.saturating_add(T::DbWeight::get().writes(12_u64))
 			.saturating_add(T::DbWeight::get().writes((1_u64).saturating_mul(s.into())))
 			.saturating_add(Weight::from_parts(0, 4).saturating_mul(s.into()))
 	}
@@ -651,12 +650,12 @@ impl<T: frame_system::Config> WeightInfo for SubstrateWeight<T> {
 		// Proof Size summary in bytes:
 		//  Measured:  `0 + n * (720 ±0) + v * (3598 ±0)`
 		//  Estimated: `512390 + n * (3566 ±0) + v * (3566 ±0)`
-		// Minimum execution time: 608_575_000 picoseconds.
-		Weight::from_parts(613_663_000, 512390)
-			// Standard Error: 2_286_521
-			.saturating_add(Weight::from_parts(72_108_001, 0).saturating_mul(v.into()))
-			// Standard Error: 227_839
-			.saturating_add(Weight::from_parts(20_314_085, 0).saturating_mul(n.into()))
+		// Minimum execution time: 692_301_000 picoseconds.
+		Weight::from_parts(708_732_000, 512390)
+			// Standard Error: 2_117_299
+			.saturating_add(Weight::from_parts(70_087_600, 0).saturating_mul(v.into()))
+			// Standard Error: 210_977
+			.saturating_add(Weight::from_parts(22_953_405, 0).saturating_mul(n.into()))
 			.saturating_add(T::DbWeight::get().reads(206_u64))
 			.saturating_add(T::DbWeight::get().reads((5_u64).saturating_mul(v.into())))
 			.saturating_add(T::DbWeight::get().reads((4_u64).saturating_mul(n.into())))
@@ -685,14 +684,14 @@ impl<T: frame_system::Config> WeightInfo for SubstrateWeight<T> {
 	/// The range of component `n` is `[500, 1000]`.
 	fn get_npos_voters(v: u32, n: u32, ) -> Weight {
 		// Proof Size summary in bytes:
-		//  Measured:  `3175 + n * (911 ±0) + v * (395 ±0)`
+		//  Measured:  `3241 + n * (911 ±0) + v * (395 ±0)`
 		//  Estimated: `512390 + n * (3566 ±0) + v * (3566 ±0)`
-		// Minimum execution time: 37_173_756_000 picoseconds.
-		Weight::from_parts(37_488_937_000, 512390)
-			// Standard Error: 467_413
-			.saturating_add(Weight::from_parts(8_086_367, 0).saturating_mul(v.into()))
-			// Standard Error: 467_413
-			.saturating_add(Weight::from_parts(3_108_193, 0).saturating_mul(n.into()))
+		// Minimum execution time: 43_708_472_000 picoseconds.
+		Weight::from_parts(44_048_436_000, 512390)
+			// Standard Error: 493_244
+			.saturating_add(Weight::from_parts(6_697_278, 0).saturating_mul(v.into()))
+			// Standard Error: 493_244
+			.saturating_add(Weight::from_parts(4_559_779, 0).saturating_mul(n.into()))
 			.saturating_add(T::DbWeight::get().reads(201_u64))
 			.saturating_add(T::DbWeight::get().reads((5_u64).saturating_mul(v.into())))
 			.saturating_add(T::DbWeight::get().reads((4_u64).saturating_mul(n.into())))
@@ -707,12 +706,12 @@ impl<T: frame_system::Config> WeightInfo for SubstrateWeight<T> {
 	/// The range of component `v` is `[500, 1000]`.
 	fn get_npos_targets(v: u32, ) -> Weight {
 		// Proof Size summary in bytes:
-		//  Measured:  `979 + v * (50 ±0)`
+		//  Measured:  `1012 + v * (50 ±0)`
 		//  Estimated: `3510 + v * (2520 ±0)`
-		// Minimum execution time: 2_641_258_000 picoseconds.
-		Weight::from_parts(382_882_595, 3510)
-			// Standard Error: 11_991
-			.saturating_add(Weight::from_parts(4_695_820, 0).saturating_mul(v.into()))
+		// Minimum execution time: 2_917_165_000 picoseconds.
+		Weight::from_parts(2_948_999_000, 3510)
+			// Standard Error: 33_372
+			.saturating_add(Weight::from_parts(2_126_909, 0).saturating_mul(v.into()))
 			.saturating_add(T::DbWeight::get().reads(2_u64))
 			.saturating_add(T::DbWeight::get().reads((1_u64).saturating_mul(v.into())))
 			.saturating_add(Weight::from_parts(0, 2520).saturating_mul(v.into()))
@@ -735,8 +734,8 @@ impl<T: frame_system::Config> WeightInfo for SubstrateWeight<T> {
 		// Proof Size summary in bytes:
 		//  Measured:  `0`
 		//  Estimated: `0`
-		// Minimum execution time: 5_753_000 picoseconds.
-		Weight::from_parts(6_529_000, 0)
+		// Minimum execution time: 4_748_000 picoseconds.
+		Weight::from_parts(5_052_000, 0)
 			.saturating_add(T::DbWeight::get().writes(7_u64))
 	}
 	/// Storage: `Staking::MinCommission` (r:0 w:1)
@@ -757,8 +756,8 @@ impl<T: frame_system::Config> WeightInfo for SubstrateWeight<T> {
 		// Proof Size summary in bytes:
 		//  Measured:  `0`
 		//  Estimated: `0`
-		// Minimum execution time: 5_212_000 picoseconds.
-		Weight::from_parts(5_451_000, 0)
+		// Minimum execution time: 4_316_000 picoseconds.
+		Weight::from_parts(4_526_000, 0)
 			.saturating_add(T::DbWeight::get().writes(7_u64))
 	}
 	/// Storage: `Staking::Bonded` (r:1 w:0)
@@ -785,10 +784,10 @@ impl<T: frame_system::Config> WeightInfo for SubstrateWeight<T> {
 	/// Proof: `VoterList::CounterForListNodes` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`)
 	fn chill_other() -> Weight {
 		// Proof Size summary in bytes:
-		//  Measured:  `1939`
+		//  Measured:  `2005`
 		//  Estimated: `6248`
-		// Minimum execution time: 73_000_000 picoseconds.
-		Weight::from_parts(75_184_000, 6248)
+		// Minimum execution time: 87_374_000 picoseconds.
+		Weight::from_parts(89_848_000, 6248)
 			.saturating_add(T::DbWeight::get().reads(12_u64))
 			.saturating_add(T::DbWeight::get().writes(6_u64))
 	}
@@ -798,10 +797,10 @@ impl<T: frame_system::Config> WeightInfo for SubstrateWeight<T> {
 	/// Proof: `Staking::Validators` (`max_values`: None, `max_size`: Some(45), added: 2520, mode: `MaxEncodedLen`)
 	fn force_apply_min_commission() -> Weight {
 		// Proof Size summary in bytes:
-		//  Measured:  `691`
+		//  Measured:  `724`
 		//  Estimated: `3510`
-		// Minimum execution time: 13_056_000 picoseconds.
-		Weight::from_parts(13_517_000, 3510)
+		// Minimum execution time: 15_529_000 picoseconds.
+		Weight::from_parts(16_094_000, 3510)
 			.saturating_add(T::DbWeight::get().reads(2_u64))
 			.saturating_add(T::DbWeight::get().writes(1_u64))
 	}
@@ -811,28 +810,51 @@ impl<T: frame_system::Config> WeightInfo for SubstrateWeight<T> {
 		// Proof Size summary in bytes:
 		//  Measured:  `0`
 		//  Estimated: `0`
-		// Minimum execution time: 3_201_000 picoseconds.
-		Weight::from_parts(3_442_000, 0)
+		// Minimum execution time: 2_533_000 picoseconds.
+		Weight::from_parts(2_817_000, 0)
 			.saturating_add(T::DbWeight::get().writes(1_u64))
 	}
-	/// Storage: `Balances::Locks` (r:1 w:1)
-	/// Proof: `Balances::Locks` (`max_values`: None, `max_size`: Some(1299), added: 3774, mode: `MaxEncodedLen`)
-	/// Storage: `System::Account` (r:1 w:1)
+	/// Storage: `Staking::VirtualStakers` (r:1 w:0)
+	/// Proof: `Staking::VirtualStakers` (`max_values`: None, `max_size`: Some(40), added: 2515, mode: `MaxEncodedLen`)
+	/// Storage: `Balances::Holds` (r:1 w:0)
+	/// Proof: `Balances::Holds` (`max_values`: None, `max_size`: Some(355), added: 2830, mode: `MaxEncodedLen`)
+	/// Storage: `System::Account` (r:1 w:0)
 	/// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`)
+	/// Storage: `Balances::Locks` (r:1 w:0)
+	/// Proof: `Balances::Locks` (`max_values`: None, `max_size`: Some(1299), added: 3774, mode: `MaxEncodedLen`)
 	/// Storage: `Staking::Bonded` (r:1 w:1)
 	/// Proof: `Staking::Bonded` (`max_values`: None, `max_size`: Some(72), added: 2547, mode: `MaxEncodedLen`)
 	/// Storage: `Staking::Ledger` (r:1 w:1)
 	/// Proof: `Staking::Ledger` (`max_values`: None, `max_size`: Some(1091), added: 3566, mode: `MaxEncodedLen`)
+	fn restore_ledger() -> Weight {
+		// Proof Size summary in bytes:
+		//  Measured:  `1110`
+		//  Estimated: `4764`
+		// Minimum execution time: 50_105_000 picoseconds.
+		Weight::from_parts(50_966_000, 4764)
+			.saturating_add(T::DbWeight::get().reads(6_u64))
+			.saturating_add(T::DbWeight::get().writes(2_u64))
+	}
+	/// Storage: `Staking::VirtualStakers` (r:1 w:0)
+	/// Proof: `Staking::VirtualStakers` (`max_values`: None, `max_size`: Some(40), added: 2515, mode: `MaxEncodedLen`)
+	/// Storage: `Staking::Bonded` (r:1 w:0)
+	/// Proof: `Staking::Bonded` (`max_values`: None, `max_size`: Some(72), added: 2547, mode: `MaxEncodedLen`)
+	/// Storage: `Staking::Ledger` (r:1 w:0)
+	/// Proof: `Staking::Ledger` (`max_values`: None, `max_size`: Some(1091), added: 3566, mode: `MaxEncodedLen`)
+	/// Storage: `Balances::Locks` (r:1 w:1)
+	/// Proof: `Balances::Locks` (`max_values`: None, `max_size`: Some(1299), added: 3774, mode: `MaxEncodedLen`)
+	/// Storage: `Balances::Holds` (r:1 w:1)
+	/// Proof: `Balances::Holds` (`max_values`: None, `max_size`: Some(355), added: 2830, mode: `MaxEncodedLen`)
 	/// Storage: `Balances::Freezes` (r:1 w:0)
 	/// Proof: `Balances::Freezes` (`max_values`: None, `max_size`: Some(67), added: 2542, mode: `MaxEncodedLen`)
-	fn restore_ledger() -> Weight {
+	fn migrate_currency() -> Weight {
 		// Proof Size summary in bytes:
-		//  Measured:  `1047`
+		//  Measured:  `1246`
 		//  Estimated: `4764`
-		// Minimum execution time: 44_671_000 picoseconds.
-		Weight::from_parts(45_611_000, 4764)
-			.saturating_add(T::DbWeight::get().reads(5_u64))
-			.saturating_add(T::DbWeight::get().writes(4_u64))
+		// Minimum execution time: 94_054_000 picoseconds.
+		Weight::from_parts(96_272_000, 4764)
+			.saturating_add(T::DbWeight::get().reads(6_u64))
+			.saturating_add(T::DbWeight::get().writes(2_u64))
 	}
 }
 
@@ -842,18 +864,18 @@ impl WeightInfo for () {
 	/// Proof: `Staking::Bonded` (`max_values`: None, `max_size`: Some(72), added: 2547, mode: `MaxEncodedLen`)
 	/// Storage: `Staking::Ledger` (r:1 w:1)
 	/// Proof: `Staking::Ledger` (`max_values`: None, `max_size`: Some(1091), added: 3566, mode: `MaxEncodedLen`)
-	/// Storage: `Balances::Locks` (r:1 w:1)
-	/// Proof: `Balances::Locks` (`max_values`: None, `max_size`: Some(1299), added: 3774, mode: `MaxEncodedLen`)
-	/// Storage: `Balances::Freezes` (r:1 w:0)
-	/// Proof: `Balances::Freezes` (`max_values`: None, `max_size`: Some(67), added: 2542, mode: `MaxEncodedLen`)
+	/// Storage: `Balances::Holds` (r:1 w:1)
+	/// Proof: `Balances::Holds` (`max_values`: None, `max_size`: Some(355), added: 2830, mode: `MaxEncodedLen`)
+	/// Storage: `Staking::VirtualStakers` (r:1 w:0)
+	/// Proof: `Staking::VirtualStakers` (`max_values`: None, `max_size`: Some(40), added: 2515, mode: `MaxEncodedLen`)
 	/// Storage: `Staking::Payee` (r:0 w:1)
 	/// Proof: `Staking::Payee` (`max_values`: None, `max_size`: Some(73), added: 2548, mode: `MaxEncodedLen`)
 	fn bond() -> Weight {
 		// Proof Size summary in bytes:
-		//  Measured:  `1042`
-		//  Estimated: `4764`
-		// Minimum execution time: 46_504_000 picoseconds.
-		Weight::from_parts(48_459_000, 4764)
+		//  Measured:  `1068`
+		//  Estimated: `4556`
+		// Minimum execution time: 71_854_000 picoseconds.
+		Weight::from_parts(73_408_000, 4556)
 			.saturating_add(RocksDbWeight::get().reads(4_u64))
 			.saturating_add(RocksDbWeight::get().writes(4_u64))
 	}
@@ -861,20 +883,20 @@ impl WeightInfo for () {
 	/// Proof: `Staking::Bonded` (`max_values`: None, `max_size`: Some(72), added: 2547, mode: `MaxEncodedLen`)
 	/// Storage: `Staking::Ledger` (r:1 w:1)
 	/// Proof: `Staking::Ledger` (`max_values`: None, `max_size`: Some(1091), added: 3566, mode: `MaxEncodedLen`)
-	/// Storage: `Balances::Locks` (r:1 w:1)
-	/// Proof: `Balances::Locks` (`max_values`: None, `max_size`: Some(1299), added: 3774, mode: `MaxEncodedLen`)
-	/// Storage: `Balances::Freezes` (r:1 w:0)
-	/// Proof: `Balances::Freezes` (`max_values`: None, `max_size`: Some(67), added: 2542, mode: `MaxEncodedLen`)
+	/// Storage: `Staking::VirtualStakers` (r:1 w:0)
+	/// Proof: `Staking::VirtualStakers` (`max_values`: None, `max_size`: Some(40), added: 2515, mode: `MaxEncodedLen`)
+	/// Storage: `Balances::Holds` (r:1 w:1)
+	/// Proof: `Balances::Holds` (`max_values`: None, `max_size`: Some(355), added: 2830, mode: `MaxEncodedLen`)
 	/// Storage: `VoterList::ListNodes` (r:3 w:3)
 	/// Proof: `VoterList::ListNodes` (`max_values`: None, `max_size`: Some(154), added: 2629, mode: `MaxEncodedLen`)
 	/// Storage: `VoterList::ListBags` (r:2 w:2)
 	/// Proof: `VoterList::ListBags` (`max_values`: None, `max_size`: Some(82), added: 2557, mode: `MaxEncodedLen`)
 	fn bond_extra() -> Weight {
 		// Proof Size summary in bytes:
-		//  Measured:  `1990`
+		//  Measured:  `2049`
 		//  Estimated: `8877`
-		// Minimum execution time: 90_475_000 picoseconds.
-		Weight::from_parts(93_619_000, 8877)
+		// Minimum execution time: 127_442_000 picoseconds.
+		Weight::from_parts(130_845_000, 8877)
 			.saturating_add(RocksDbWeight::get().reads(9_u64))
 			.saturating_add(RocksDbWeight::get().writes(7_u64))
 	}
@@ -888,22 +910,22 @@ impl WeightInfo for () {
 	/// Proof: `Staking::MinNominatorBond` (`max_values`: Some(1), `max_size`: Some(16), added: 511, mode: `MaxEncodedLen`)
 	/// Storage: `Staking::CurrentEra` (r:1 w:0)
 	/// Proof: `Staking::CurrentEra` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`)
-	/// Storage: `Balances::Locks` (r:1 w:1)
-	/// Proof: `Balances::Locks` (`max_values`: None, `max_size`: Some(1299), added: 3774, mode: `MaxEncodedLen`)
-	/// Storage: `Balances::Freezes` (r:1 w:0)
-	/// Proof: `Balances::Freezes` (`max_values`: None, `max_size`: Some(67), added: 2542, mode: `MaxEncodedLen`)
+	/// Storage: `Staking::VirtualStakers` (r:1 w:0)
+	/// Proof: `Staking::VirtualStakers` (`max_values`: None, `max_size`: Some(40), added: 2515, mode: `MaxEncodedLen`)
+	/// Storage: `Balances::Holds` (r:1 w:0)
+	/// Proof: `Balances::Holds` (`max_values`: None, `max_size`: Some(355), added: 2830, mode: `MaxEncodedLen`)
 	/// Storage: `VoterList::ListNodes` (r:3 w:3)
 	/// Proof: `VoterList::ListNodes` (`max_values`: None, `max_size`: Some(154), added: 2629, mode: `MaxEncodedLen`)
 	/// Storage: `VoterList::ListBags` (r:2 w:2)
 	/// Proof: `VoterList::ListBags` (`max_values`: None, `max_size`: Some(82), added: 2557, mode: `MaxEncodedLen`)
 	fn unbond() -> Weight {
 		// Proof Size summary in bytes:
-		//  Measured:  `2195`
+		//  Measured:  `2151`
 		//  Estimated: `8877`
-		// Minimum execution time: 99_335_000 picoseconds.
-		Weight::from_parts(101_440_000, 8877)
+		// Minimum execution time: 105_259_000 picoseconds.
+		Weight::from_parts(107_112_000, 8877)
 			.saturating_add(RocksDbWeight::get().reads(12_u64))
-			.saturating_add(RocksDbWeight::get().writes(7_u64))
+			.saturating_add(RocksDbWeight::get().writes(6_u64))
 	}
 	/// Storage: `Staking::Ledger` (r:1 w:1)
 	/// Proof: `Staking::Ledger` (`max_values`: None, `max_size`: Some(1091), added: 3566, mode: `MaxEncodedLen`)
@@ -911,21 +933,21 @@ impl WeightInfo for () {
 	/// Proof: `Staking::Bonded` (`max_values`: None, `max_size`: Some(72), added: 2547, mode: `MaxEncodedLen`)
 	/// Storage: `Staking::CurrentEra` (r:1 w:0)
 	/// Proof: `Staking::CurrentEra` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`)
-	/// Storage: `Balances::Locks` (r:1 w:1)
-	/// Proof: `Balances::Locks` (`max_values`: None, `max_size`: Some(1299), added: 3774, mode: `MaxEncodedLen`)
-	/// Storage: `Balances::Freezes` (r:1 w:0)
-	/// Proof: `Balances::Freezes` (`max_values`: None, `max_size`: Some(67), added: 2542, mode: `MaxEncodedLen`)
+	/// Storage: `Staking::VirtualStakers` (r:1 w:0)
+	/// Proof: `Staking::VirtualStakers` (`max_values`: None, `max_size`: Some(40), added: 2515, mode: `MaxEncodedLen`)
+	/// Storage: `Balances::Holds` (r:1 w:1)
+	/// Proof: `Balances::Holds` (`max_values`: None, `max_size`: Some(355), added: 2830, mode: `MaxEncodedLen`)
 	/// Storage: `NominationPools::ReversePoolIdLookup` (r:1 w:0)
 	/// Proof: `NominationPools::ReversePoolIdLookup` (`max_values`: None, `max_size`: Some(44), added: 2519, mode: `MaxEncodedLen`)
 	/// The range of component `s` is `[0, 100]`.
 	fn withdraw_unbonded_update(s: u32, ) -> Weight {
 		// Proof Size summary in bytes:
-		//  Measured:  `1297`
-		//  Estimated: `4764`
-		// Minimum execution time: 50_067_000 picoseconds.
-		Weight::from_parts(52_396_327, 4764)
-			// Standard Error: 1_419
-			.saturating_add(Weight::from_parts(51_406, 0).saturating_mul(s.into()))
+		//  Measured:  `1393`
+		//  Estimated: `4556`
+		// Minimum execution time: 77_158_000 picoseconds.
+		Weight::from_parts(79_140_122, 4556)
+			// Standard Error: 1_688
+			.saturating_add(Weight::from_parts(62_663, 0).saturating_mul(s.into()))
 			.saturating_add(RocksDbWeight::get().reads(6_u64))
 			.saturating_add(RocksDbWeight::get().writes(2_u64))
 	}
@@ -937,10 +959,10 @@ impl WeightInfo for () {
 	/// Proof: `Staking::CurrentEra` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`)
 	/// Storage: `Staking::SlashingSpans` (r:1 w:1)
 	/// Proof: `Staking::SlashingSpans` (`max_values`: None, `max_size`: None, mode: `Measured`)
-	/// Storage: `Balances::Locks` (r:1 w:1)
-	/// Proof: `Balances::Locks` (`max_values`: None, `max_size`: Some(1299), added: 3774, mode: `MaxEncodedLen`)
-	/// Storage: `Balances::Freezes` (r:1 w:0)
-	/// Proof: `Balances::Freezes` (`max_values`: None, `max_size`: Some(67), added: 2542, mode: `MaxEncodedLen`)
+	/// Storage: `Staking::VirtualStakers` (r:1 w:1)
+	/// Proof: `Staking::VirtualStakers` (`max_values`: None, `max_size`: Some(40), added: 2515, mode: `MaxEncodedLen`)
+	/// Storage: `Balances::Holds` (r:1 w:1)
+	/// Proof: `Balances::Holds` (`max_values`: None, `max_size`: Some(355), added: 2830, mode: `MaxEncodedLen`)
 	/// Storage: `Staking::Validators` (r:1 w:0)
 	/// Proof: `Staking::Validators` (`max_values`: None, `max_size`: Some(45), added: 2520, mode: `MaxEncodedLen`)
 	/// Storage: `Staking::Nominators` (r:1 w:1)
@@ -960,14 +982,14 @@ impl WeightInfo for () {
 	/// The range of component `s` is `[0, 100]`.
 	fn withdraw_unbonded_kill(s: u32, ) -> Weight {
 		// Proof Size summary in bytes:
-		//  Measured:  `2196 + s * (4 ±0)`
+		//  Measured:  `2255 + s * (4 ±0)`
 		//  Estimated: `6248 + s * (4 ±0)`
-		// Minimum execution time: 92_931_000 picoseconds.
-		Weight::from_parts(101_398_156, 6248)
-			// Standard Error: 4_180
-			.saturating_add(Weight::from_parts(1_377_850, 0).saturating_mul(s.into()))
+		// Minimum execution time: 125_396_000 picoseconds.
+		Weight::from_parts(134_915_543, 6248)
+			// Standard Error: 3_660
+			.saturating_add(Weight::from_parts(1_324_736, 0).saturating_mul(s.into()))
 			.saturating_add(RocksDbWeight::get().reads(13_u64))
-			.saturating_add(RocksDbWeight::get().writes(11_u64))
+			.saturating_add(RocksDbWeight::get().writes(12_u64))
 			.saturating_add(RocksDbWeight::get().writes((1_u64).saturating_mul(s.into())))
 			.saturating_add(Weight::from_parts(0, 4).saturating_mul(s.into()))
 	}
@@ -995,10 +1017,10 @@ impl WeightInfo for () {
 	/// Proof: `Staking::CounterForValidators` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`)
 	fn validate() -> Weight {
 		// Proof Size summary in bytes:
-		//  Measured:  `1372`
+		//  Measured:  `1438`
 		//  Estimated: `4556`
-		// Minimum execution time: 56_291_000 picoseconds.
-		Weight::from_parts(58_372_000, 4556)
+		// Minimum execution time: 68_826_000 picoseconds.
+		Weight::from_parts(71_261_000, 4556)
 			.saturating_add(RocksDbWeight::get().reads(11_u64))
 			.saturating_add(RocksDbWeight::get().writes(5_u64))
 	}
@@ -1011,12 +1033,12 @@ impl WeightInfo for () {
 	/// The range of component `k` is `[1, 128]`.
 	fn kick(k: u32, ) -> Weight {
 		// Proof Size summary in bytes:
-		//  Measured:  `1815 + k * (572 ±0)`
+		//  Measured:  `1848 + k * (572 ±0)`
 		//  Estimated: `4556 + k * (3033 ±0)`
-		// Minimum execution time: 36_218_000 picoseconds.
-		Weight::from_parts(38_811_308, 4556)
-			// Standard Error: 8_352
-			.saturating_add(Weight::from_parts(6_527_398, 0).saturating_mul(k.into()))
+		// Minimum execution time: 46_082_000 picoseconds.
+		Weight::from_parts(49_541_374, 4556)
+			// Standard Error: 7_218
+			.saturating_add(Weight::from_parts(7_281_079, 0).saturating_mul(k.into()))
 			.saturating_add(RocksDbWeight::get().reads(2_u64))
 			.saturating_add(RocksDbWeight::get().reads((1_u64).saturating_mul(k.into())))
 			.saturating_add(RocksDbWeight::get().writes((1_u64).saturating_mul(k.into())))
@@ -1047,12 +1069,12 @@ impl WeightInfo for () {
 	/// The range of component `n` is `[1, 16]`.
 	fn nominate(n: u32, ) -> Weight {
 		// Proof Size summary in bytes:
-		//  Measured:  `1866 + n * (102 ±0)`
+		//  Measured:  `1932 + n * (102 ±0)`
 		//  Estimated: `6248 + n * (2520 ±0)`
-		// Minimum execution time: 68_607_000 picoseconds.
-		Weight::from_parts(66_831_185, 6248)
-			// Standard Error: 14_014
-			.saturating_add(Weight::from_parts(4_031_635, 0).saturating_mul(n.into()))
+		// Minimum execution time: 83_854_000 picoseconds.
+		Weight::from_parts(81_387_241, 6248)
+			// Standard Error: 16_811
+			.saturating_add(Weight::from_parts(4_900_554, 0).saturating_mul(n.into()))
 			.saturating_add(RocksDbWeight::get().reads(12_u64))
 			.saturating_add(RocksDbWeight::get().reads((1_u64).saturating_mul(n.into())))
 			.saturating_add(RocksDbWeight::get().writes(6_u64))
@@ -1076,10 +1098,10 @@ impl WeightInfo for () {
 	/// Proof: `VoterList::CounterForListNodes` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`)
 	fn chill() -> Weight {
 		// Proof Size summary in bytes:
-		//  Measured:  `1816`
+		//  Measured:  `1882`
 		//  Estimated: `6248`
-		// Minimum execution time: 60_088_000 picoseconds.
-		Weight::from_parts(62_471_000, 6248)
+		// Minimum execution time: 73_939_000 picoseconds.
+		Weight::from_parts(75_639_000, 6248)
 			.saturating_add(RocksDbWeight::get().reads(9_u64))
 			.saturating_add(RocksDbWeight::get().writes(6_u64))
 	}
@@ -1091,10 +1113,10 @@ impl WeightInfo for () {
 	/// Proof: `Staking::Payee` (`max_values`: None, `max_size`: Some(73), added: 2548, mode: `MaxEncodedLen`)
 	fn set_payee() -> Weight {
 		// Proof Size summary in bytes:
-		//  Measured:  `902`
+		//  Measured:  `935`
 		//  Estimated: `4556`
-		// Minimum execution time: 19_777_000 picoseconds.
-		Weight::from_parts(20_690_000, 4556)
+		// Minimum execution time: 24_592_000 picoseconds.
+		Weight::from_parts(25_092_000, 4556)
 			.saturating_add(RocksDbWeight::get().reads(2_u64))
 			.saturating_add(RocksDbWeight::get().writes(1_u64))
 	}
@@ -1106,10 +1128,10 @@ impl WeightInfo for () {
 	/// Proof: `Staking::Payee` (`max_values`: None, `max_size`: Some(73), added: 2548, mode: `MaxEncodedLen`)
 	fn update_payee() -> Weight {
 		// Proof Size summary in bytes:
-		//  Measured:  `969`
+		//  Measured:  `1002`
 		//  Estimated: `4556`
-		// Minimum execution time: 23_705_000 picoseconds.
-		Weight::from_parts(24_409_000, 4556)
+		// Minimum execution time: 29_735_000 picoseconds.
+		Weight::from_parts(30_546_000, 4556)
 			.saturating_add(RocksDbWeight::get().reads(3_u64))
 			.saturating_add(RocksDbWeight::get().writes(1_u64))
 	}
@@ -1119,10 +1141,10 @@ impl WeightInfo for () {
 	/// Proof: `Staking::Ledger` (`max_values`: None, `max_size`: Some(1091), added: 3566, mode: `MaxEncodedLen`)
 	fn set_controller() -> Weight {
 		// Proof Size summary in bytes:
-		//  Measured:  `902`
+		//  Measured:  `935`
 		//  Estimated: `8122`
-		// Minimum execution time: 23_479_000 picoseconds.
-		Weight::from_parts(24_502_000, 8122)
+		// Minimum execution time: 28_728_000 picoseconds.
+		Weight::from_parts(29_709_000, 8122)
 			.saturating_add(RocksDbWeight::get().reads(3_u64))
 			.saturating_add(RocksDbWeight::get().writes(3_u64))
 	}
@@ -1132,8 +1154,8 @@ impl WeightInfo for () {
 		// Proof Size summary in bytes:
 		//  Measured:  `0`
 		//  Estimated: `0`
-		// Minimum execution time: 2_675_000 picoseconds.
-		Weight::from_parts(2_802_000, 0)
+		// Minimum execution time: 2_519_000 picoseconds.
+		Weight::from_parts(2_673_000, 0)
 			.saturating_add(RocksDbWeight::get().writes(1_u64))
 	}
 	/// Storage: `Staking::ForceEra` (r:0 w:1)
@@ -1142,8 +1164,8 @@ impl WeightInfo for () {
 		// Proof Size summary in bytes:
 		//  Measured:  `0`
 		//  Estimated: `0`
-		// Minimum execution time: 7_067_000 picoseconds.
-		Weight::from_parts(7_413_000, 0)
+		// Minimum execution time: 8_050_000 picoseconds.
+		Weight::from_parts(8_268_000, 0)
 			.saturating_add(RocksDbWeight::get().writes(1_u64))
 	}
 	/// Storage: `Staking::ForceEra` (r:0 w:1)
@@ -1152,8 +1174,8 @@ impl WeightInfo for () {
 		// Proof Size summary in bytes:
 		//  Measured:  `0`
 		//  Estimated: `0`
-		// Minimum execution time: 6_977_000 picoseconds.
-		Weight::from_parts(7_353_000, 0)
+		// Minimum execution time: 8_131_000 picoseconds.
+		Weight::from_parts(8_349_000, 0)
 			.saturating_add(RocksDbWeight::get().writes(1_u64))
 	}
 	/// Storage: `Staking::ForceEra` (r:0 w:1)
@@ -1162,8 +1184,8 @@ impl WeightInfo for () {
 		// Proof Size summary in bytes:
 		//  Measured:  `0`
 		//  Estimated: `0`
-		// Minimum execution time: 7_071_000 picoseconds.
-		Weight::from_parts(7_463_000, 0)
+		// Minimum execution time: 8_104_000 picoseconds.
+		Weight::from_parts(8_317_000, 0)
 			.saturating_add(RocksDbWeight::get().writes(1_u64))
 	}
 	/// Storage: `Staking::Invulnerables` (r:0 w:1)
@@ -1173,10 +1195,10 @@ impl WeightInfo for () {
 		// Proof Size summary in bytes:
 		//  Measured:  `0`
 		//  Estimated: `0`
-		// Minimum execution time: 2_833_000 picoseconds.
-		Weight::from_parts(3_328_130, 0)
-			// Standard Error: 30
-			.saturating_add(Weight::from_parts(10_058, 0).saturating_mul(v.into()))
+		// Minimum execution time: 2_669_000 picoseconds.
+		Weight::from_parts(3_013_436, 0)
+			// Standard Error: 31
+			.saturating_add(Weight::from_parts(10_704, 0).saturating_mul(v.into()))
 			.saturating_add(RocksDbWeight::get().writes(1_u64))
 	}
 	/// Storage: `Staking::Ledger` (r:11800 w:11800)
@@ -1188,12 +1210,12 @@ impl WeightInfo for () {
 	/// The range of component `i` is `[0, 5900]`.
 	fn deprecate_controller_batch(i: u32, ) -> Weight {
 		// Proof Size summary in bytes:
-		//  Measured:  `1746 + i * (229 ±0)`
+		//  Measured:  `1779 + i * (229 ±0)`
 		//  Estimated: `990 + i * (7132 ±0)`
-		// Minimum execution time: 5_300_000 picoseconds.
-		Weight::from_parts(5_437_000, 990)
-			// Standard Error: 66_261
-			.saturating_add(Weight::from_parts(30_172_457, 0).saturating_mul(i.into()))
+		// Minimum execution time: 5_101_000 picoseconds.
+		Weight::from_parts(5_368_000, 990)
+			// Standard Error: 75_180
+			.saturating_add(Weight::from_parts(33_781_643, 0).saturating_mul(i.into()))
 			.saturating_add(RocksDbWeight::get().reads((4_u64).saturating_mul(i.into())))
 			.saturating_add(RocksDbWeight::get().writes((3_u64).saturating_mul(i.into())))
 			.saturating_add(Weight::from_parts(0, 7132).saturating_mul(i.into()))
@@ -1204,10 +1226,10 @@ impl WeightInfo for () {
 	/// Proof: `Staking::Bonded` (`max_values`: None, `max_size`: Some(72), added: 2547, mode: `MaxEncodedLen`)
 	/// Storage: `Staking::Ledger` (r:1 w:1)
 	/// Proof: `Staking::Ledger` (`max_values`: None, `max_size`: Some(1091), added: 3566, mode: `MaxEncodedLen`)
-	/// Storage: `Balances::Locks` (r:1 w:1)
-	/// Proof: `Balances::Locks` (`max_values`: None, `max_size`: Some(1299), added: 3774, mode: `MaxEncodedLen`)
-	/// Storage: `Balances::Freezes` (r:1 w:0)
-	/// Proof: `Balances::Freezes` (`max_values`: None, `max_size`: Some(67), added: 2542, mode: `MaxEncodedLen`)
+	/// Storage: `Staking::VirtualStakers` (r:1 w:1)
+	/// Proof: `Staking::VirtualStakers` (`max_values`: None, `max_size`: Some(40), added: 2515, mode: `MaxEncodedLen`)
+	/// Storage: `Balances::Holds` (r:1 w:1)
+	/// Proof: `Balances::Holds` (`max_values`: None, `max_size`: Some(355), added: 2830, mode: `MaxEncodedLen`)
 	/// Storage: `System::Account` (r:1 w:1)
 	/// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`)
 	/// Storage: `Staking::Validators` (r:1 w:0)
@@ -1229,14 +1251,14 @@ impl WeightInfo for () {
 	/// The range of component `s` is `[0, 100]`.
 	fn force_unstake(s: u32, ) -> Weight {
 		// Proof Size summary in bytes:
-		//  Measured:  `2196 + s * (4 ±0)`
+		//  Measured:  `2255 + s * (4 ±0)`
 		//  Estimated: `6248 + s * (4 ±0)`
-		// Minimum execution time: 87_677_000 picoseconds.
-		Weight::from_parts(96_386_462, 6248)
-			// Standard Error: 3_717
-			.saturating_add(Weight::from_parts(1_370_585, 0).saturating_mul(s.into()))
+		// Minimum execution time: 119_955_000 picoseconds.
+		Weight::from_parts(128_392_032, 6248)
+			// Standard Error: 3_773
+			.saturating_add(Weight::from_parts(1_302_488, 0).saturating_mul(s.into()))
 			.saturating_add(RocksDbWeight::get().reads(13_u64))
-			.saturating_add(RocksDbWeight::get().writes(12_u64))
+			.saturating_add(RocksDbWeight::get().writes(13_u64))
 			.saturating_add(RocksDbWeight::get().writes((1_u64).saturating_mul(s.into())))
 			.saturating_add(Weight::from_parts(0, 4).saturating_mul(s.into()))
 	}
@@ -1245,12 +1267,12 @@ impl WeightInfo for () {
 	/// The range of component `s` is `[1, 1000]`.
 	fn cancel_deferred_slash(s: u32, ) -> Weight {
 		// Proof Size summary in bytes:
-		//  Measured:  `66672`
-		//  Estimated: `70137`
-		// Minimum execution time: 105_086_000 picoseconds.
-		Weight::from_parts(1_167_895_222, 70137)
-			// Standard Error: 77_022
-			.saturating_add(Weight::from_parts(6_487_305, 0).saturating_mul(s.into()))
+		//  Measured:  `66705`
+		//  Estimated: `70170`
+		// Minimum execution time: 139_290_000 picoseconds.
+		Weight::from_parts(959_667_494, 70170)
+			// Standard Error: 56_271
+			.saturating_add(Weight::from_parts(4_798_293, 0).saturating_mul(s.into()))
 			.saturating_add(RocksDbWeight::get().reads(1_u64))
 			.saturating_add(RocksDbWeight::get().writes(1_u64))
 	}
@@ -1268,12 +1290,10 @@ impl WeightInfo for () {
 	/// Proof: `Staking::CurrentEra` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`)
 	/// Storage: `Staking::ErasValidatorReward` (r:1 w:0)
 	/// Proof: `Staking::ErasValidatorReward` (`max_values`: None, `max_size`: Some(28), added: 2503, mode: `MaxEncodedLen`)
-	/// Storage: `Balances::Locks` (r:257 w:257)
-	/// Proof: `Balances::Locks` (`max_values`: None, `max_size`: Some(1299), added: 3774, mode: `MaxEncodedLen`)
-	/// Storage: `Balances::Freezes` (r:257 w:0)
-	/// Proof: `Balances::Freezes` (`max_values`: None, `max_size`: Some(67), added: 2542, mode: `MaxEncodedLen`)
-	/// Storage: `System::Account` (r:257 w:257)
-	/// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`)
+	/// Storage: `Staking::VirtualStakers` (r:257 w:0)
+	/// Proof: `Staking::VirtualStakers` (`max_values`: None, `max_size`: Some(40), added: 2515, mode: `MaxEncodedLen`)
+	/// Storage: `Balances::Holds` (r:257 w:257)
+	/// Proof: `Balances::Holds` (`max_values`: None, `max_size`: Some(355), added: 2830, mode: `MaxEncodedLen`)
 	/// Storage: `Staking::ErasStakersPaged` (r:1 w:0)
 	/// Proof: `Staking::ErasStakersPaged` (`max_values`: None, `max_size`: None, mode: `Measured`)
 	/// Storage: `Staking::ErasRewardPoints` (r:1 w:0)
@@ -1282,29 +1302,31 @@ impl WeightInfo for () {
 	/// Proof: `Staking::ErasValidatorPrefs` (`max_values`: None, `max_size`: Some(57), added: 2532, mode: `MaxEncodedLen`)
 	/// Storage: `Staking::Payee` (r:257 w:0)
 	/// Proof: `Staking::Payee` (`max_values`: None, `max_size`: Some(73), added: 2548, mode: `MaxEncodedLen`)
+	/// Storage: `System::Account` (r:257 w:257)
+	/// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`)
 	/// The range of component `n` is `[0, 256]`.
 	fn payout_stakers_alive_staked(n: u32, ) -> Weight {
 		// Proof Size summary in bytes:
-		//  Measured:  `33297 + n * (377 ±0)`
-		//  Estimated: `30944 + n * (3774 ±3)`
-		// Minimum execution time: 154_210_000 picoseconds.
-		Weight::from_parts(192_836_012, 30944)
-			// Standard Error: 40_441
-			.saturating_add(Weight::from_parts(47_646_642, 0).saturating_mul(n.into()))
+		//  Measured:  `33283 + n * (370 ±0)`
+		//  Estimated: `30958 + n * (3566 ±0)`
+		// Minimum execution time: 193_068_000 picoseconds.
+		Weight::from_parts(252_762_568, 30958)
+			// Standard Error: 22_743
+			.saturating_add(Weight::from_parts(81_185_306, 0).saturating_mul(n.into()))
 			.saturating_add(RocksDbWeight::get().reads(14_u64))
 			.saturating_add(RocksDbWeight::get().reads((6_u64).saturating_mul(n.into())))
 			.saturating_add(RocksDbWeight::get().writes(4_u64))
 			.saturating_add(RocksDbWeight::get().writes((3_u64).saturating_mul(n.into())))
-			.saturating_add(Weight::from_parts(0, 3774).saturating_mul(n.into()))
+			.saturating_add(Weight::from_parts(0, 3566).saturating_mul(n.into()))
 	}
 	/// Storage: `Staking::Ledger` (r:1 w:1)
 	/// Proof: `Staking::Ledger` (`max_values`: None, `max_size`: Some(1091), added: 3566, mode: `MaxEncodedLen`)
 	/// Storage: `Staking::Bonded` (r:1 w:0)
 	/// Proof: `Staking::Bonded` (`max_values`: None, `max_size`: Some(72), added: 2547, mode: `MaxEncodedLen`)
-	/// Storage: `Balances::Locks` (r:1 w:1)
-	/// Proof: `Balances::Locks` (`max_values`: None, `max_size`: Some(1299), added: 3774, mode: `MaxEncodedLen`)
-	/// Storage: `Balances::Freezes` (r:1 w:0)
-	/// Proof: `Balances::Freezes` (`max_values`: None, `max_size`: Some(67), added: 2542, mode: `MaxEncodedLen`)
+	/// Storage: `Staking::VirtualStakers` (r:1 w:0)
+	/// Proof: `Staking::VirtualStakers` (`max_values`: None, `max_size`: Some(40), added: 2515, mode: `MaxEncodedLen`)
+	/// Storage: `Balances::Holds` (r:1 w:0)
+	/// Proof: `Balances::Holds` (`max_values`: None, `max_size`: Some(355), added: 2830, mode: `MaxEncodedLen`)
 	/// Storage: `VoterList::ListNodes` (r:3 w:3)
 	/// Proof: `VoterList::ListNodes` (`max_values`: None, `max_size`: Some(154), added: 2629, mode: `MaxEncodedLen`)
 	/// Storage: `VoterList::ListBags` (r:2 w:2)
@@ -1312,25 +1334,25 @@ impl WeightInfo for () {
 	/// The range of component `l` is `[1, 32]`.
 	fn rebond(l: u32, ) -> Weight {
 		// Proof Size summary in bytes:
-		//  Measured:  `1991 + l * (7 ±0)`
+		//  Measured:  `1947 + l * (7 ±0)`
 		//  Estimated: `8877`
-		// Minimum execution time: 88_337_000 picoseconds.
-		Weight::from_parts(91_391_254, 8877)
-			// Standard Error: 4_485
-			.saturating_add(Weight::from_parts(103_443, 0).saturating_mul(l.into()))
+		// Minimum execution time: 91_151_000 picoseconds.
+		Weight::from_parts(93_596_096, 8877)
+			// Standard Error: 5_313
+			.saturating_add(Weight::from_parts(124_684, 0).saturating_mul(l.into()))
 			.saturating_add(RocksDbWeight::get().reads(9_u64))
-			.saturating_add(RocksDbWeight::get().writes(7_u64))
+			.saturating_add(RocksDbWeight::get().writes(6_u64))
 	}
+	/// Storage: `Staking::VirtualStakers` (r:1 w:1)
+	/// Proof: `Staking::VirtualStakers` (`max_values`: None, `max_size`: Some(40), added: 2515, mode: `MaxEncodedLen`)
 	/// Storage: `Staking::Bonded` (r:1 w:1)
 	/// Proof: `Staking::Bonded` (`max_values`: None, `max_size`: Some(72), added: 2547, mode: `MaxEncodedLen`)
 	/// Storage: `Staking::Ledger` (r:1 w:1)
 	/// Proof: `Staking::Ledger` (`max_values`: None, `max_size`: Some(1091), added: 3566, mode: `MaxEncodedLen`)
 	/// Storage: `Staking::SlashingSpans` (r:1 w:1)
 	/// Proof: `Staking::SlashingSpans` (`max_values`: None, `max_size`: None, mode: `Measured`)
-	/// Storage: `Balances::Locks` (r:1 w:1)
-	/// Proof: `Balances::Locks` (`max_values`: None, `max_size`: Some(1299), added: 3774, mode: `MaxEncodedLen`)
-	/// Storage: `Balances::Freezes` (r:1 w:0)
-	/// Proof: `Balances::Freezes` (`max_values`: None, `max_size`: Some(67), added: 2542, mode: `MaxEncodedLen`)
+	/// Storage: `Balances::Holds` (r:1 w:1)
+	/// Proof: `Balances::Holds` (`max_values`: None, `max_size`: Some(355), added: 2830, mode: `MaxEncodedLen`)
 	/// Storage: `Staking::Validators` (r:1 w:0)
 	/// Proof: `Staking::Validators` (`max_values`: None, `max_size`: Some(45), added: 2520, mode: `MaxEncodedLen`)
 	/// Storage: `Staking::Nominators` (r:1 w:1)
@@ -1350,14 +1372,14 @@ impl WeightInfo for () {
 	/// The range of component `s` is `[1, 100]`.
 	fn reap_stash(s: u32, ) -> Weight {
 		// Proof Size summary in bytes:
-		//  Measured:  `2196 + s * (4 ±0)`
+		//  Measured:  `2255 + s * (4 ±0)`
 		//  Estimated: `6248 + s * (4 ±0)`
-		// Minimum execution time: 98_014_000 picoseconds.
-		Weight::from_parts(102_537_670, 6248)
-			// Standard Error: 3_324
-			.saturating_add(Weight::from_parts(1_353_142, 0).saturating_mul(s.into()))
+		// Minimum execution time: 133_214_000 picoseconds.
+		Weight::from_parts(137_290_527, 6248)
+			// Standard Error: 4_153
+			.saturating_add(Weight::from_parts(1_291_007, 0).saturating_mul(s.into()))
 			.saturating_add(RocksDbWeight::get().reads(12_u64))
-			.saturating_add(RocksDbWeight::get().writes(11_u64))
+			.saturating_add(RocksDbWeight::get().writes(12_u64))
 			.saturating_add(RocksDbWeight::get().writes((1_u64).saturating_mul(s.into())))
 			.saturating_add(Weight::from_parts(0, 4).saturating_mul(s.into()))
 	}
@@ -1401,12 +1423,12 @@ impl WeightInfo for () {
 		// Proof Size summary in bytes:
 		//  Measured:  `0 + n * (720 ±0) + v * (3598 ±0)`
 		//  Estimated: `512390 + n * (3566 ±0) + v * (3566 ±0)`
-		// Minimum execution time: 608_575_000 picoseconds.
-		Weight::from_parts(613_663_000, 512390)
-			// Standard Error: 2_286_521
-			.saturating_add(Weight::from_parts(72_108_001, 0).saturating_mul(v.into()))
-			// Standard Error: 227_839
-			.saturating_add(Weight::from_parts(20_314_085, 0).saturating_mul(n.into()))
+		// Minimum execution time: 692_301_000 picoseconds.
+		Weight::from_parts(708_732_000, 512390)
+			// Standard Error: 2_117_299
+			.saturating_add(Weight::from_parts(70_087_600, 0).saturating_mul(v.into()))
+			// Standard Error: 210_977
+			.saturating_add(Weight::from_parts(22_953_405, 0).saturating_mul(n.into()))
 			.saturating_add(RocksDbWeight::get().reads(206_u64))
 			.saturating_add(RocksDbWeight::get().reads((5_u64).saturating_mul(v.into())))
 			.saturating_add(RocksDbWeight::get().reads((4_u64).saturating_mul(n.into())))
@@ -1435,14 +1457,14 @@ impl WeightInfo for () {
 	/// The range of component `n` is `[500, 1000]`.
 	fn get_npos_voters(v: u32, n: u32, ) -> Weight {
 		// Proof Size summary in bytes:
-		//  Measured:  `3175 + n * (911 ±0) + v * (395 ±0)`
+		//  Measured:  `3241 + n * (911 ±0) + v * (395 ±0)`
 		//  Estimated: `512390 + n * (3566 ±0) + v * (3566 ±0)`
-		// Minimum execution time: 37_173_756_000 picoseconds.
-		Weight::from_parts(37_488_937_000, 512390)
-			// Standard Error: 467_413
-			.saturating_add(Weight::from_parts(8_086_367, 0).saturating_mul(v.into()))
-			// Standard Error: 467_413
-			.saturating_add(Weight::from_parts(3_108_193, 0).saturating_mul(n.into()))
+		// Minimum execution time: 43_708_472_000 picoseconds.
+		Weight::from_parts(44_048_436_000, 512390)
+			// Standard Error: 493_244
+			.saturating_add(Weight::from_parts(6_697_278, 0).saturating_mul(v.into()))
+			// Standard Error: 493_244
+			.saturating_add(Weight::from_parts(4_559_779, 0).saturating_mul(n.into()))
 			.saturating_add(RocksDbWeight::get().reads(201_u64))
 			.saturating_add(RocksDbWeight::get().reads((5_u64).saturating_mul(v.into())))
 			.saturating_add(RocksDbWeight::get().reads((4_u64).saturating_mul(n.into())))
@@ -1457,12 +1479,12 @@ impl WeightInfo for () {
 	/// The range of component `v` is `[500, 1000]`.
 	fn get_npos_targets(v: u32, ) -> Weight {
 		// Proof Size summary in bytes:
-		//  Measured:  `979 + v * (50 ±0)`
+		//  Measured:  `1012 + v * (50 ±0)`
 		//  Estimated: `3510 + v * (2520 ±0)`
-		// Minimum execution time: 2_641_258_000 picoseconds.
-		Weight::from_parts(382_882_595, 3510)
-			// Standard Error: 11_991
-			.saturating_add(Weight::from_parts(4_695_820, 0).saturating_mul(v.into()))
+		// Minimum execution time: 2_917_165_000 picoseconds.
+		Weight::from_parts(2_948_999_000, 3510)
+			// Standard Error: 33_372
+			.saturating_add(Weight::from_parts(2_126_909, 0).saturating_mul(v.into()))
 			.saturating_add(RocksDbWeight::get().reads(2_u64))
 			.saturating_add(RocksDbWeight::get().reads((1_u64).saturating_mul(v.into())))
 			.saturating_add(Weight::from_parts(0, 2520).saturating_mul(v.into()))
@@ -1485,8 +1507,8 @@ impl WeightInfo for () {
 		// Proof Size summary in bytes:
 		//  Measured:  `0`
 		//  Estimated: `0`
-		// Minimum execution time: 5_753_000 picoseconds.
-		Weight::from_parts(6_529_000, 0)
+		// Minimum execution time: 4_748_000 picoseconds.
+		Weight::from_parts(5_052_000, 0)
 			.saturating_add(RocksDbWeight::get().writes(7_u64))
 	}
 	/// Storage: `Staking::MinCommission` (r:0 w:1)
@@ -1507,8 +1529,8 @@ impl WeightInfo for () {
 		// Proof Size summary in bytes:
 		//  Measured:  `0`
 		//  Estimated: `0`
-		// Minimum execution time: 5_212_000 picoseconds.
-		Weight::from_parts(5_451_000, 0)
+		// Minimum execution time: 4_316_000 picoseconds.
+		Weight::from_parts(4_526_000, 0)
 			.saturating_add(RocksDbWeight::get().writes(7_u64))
 	}
 	/// Storage: `Staking::Bonded` (r:1 w:0)
@@ -1535,10 +1557,10 @@ impl WeightInfo for () {
 	/// Proof: `VoterList::CounterForListNodes` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`)
 	fn chill_other() -> Weight {
 		// Proof Size summary in bytes:
-		//  Measured:  `1939`
+		//  Measured:  `2005`
 		//  Estimated: `6248`
-		// Minimum execution time: 73_000_000 picoseconds.
-		Weight::from_parts(75_184_000, 6248)
+		// Minimum execution time: 87_374_000 picoseconds.
+		Weight::from_parts(89_848_000, 6248)
 			.saturating_add(RocksDbWeight::get().reads(12_u64))
 			.saturating_add(RocksDbWeight::get().writes(6_u64))
 	}
@@ -1548,10 +1570,10 @@ impl WeightInfo for () {
 	/// Proof: `Staking::Validators` (`max_values`: None, `max_size`: Some(45), added: 2520, mode: `MaxEncodedLen`)
 	fn force_apply_min_commission() -> Weight {
 		// Proof Size summary in bytes:
-		//  Measured:  `691`
+		//  Measured:  `724`
 		//  Estimated: `3510`
-		// Minimum execution time: 13_056_000 picoseconds.
-		Weight::from_parts(13_517_000, 3510)
+		// Minimum execution time: 15_529_000 picoseconds.
+		Weight::from_parts(16_094_000, 3510)
 			.saturating_add(RocksDbWeight::get().reads(2_u64))
 			.saturating_add(RocksDbWeight::get().writes(1_u64))
 	}
@@ -1561,27 +1583,50 @@ impl WeightInfo for () {
 		// Proof Size summary in bytes:
 		//  Measured:  `0`
 		//  Estimated: `0`
-		// Minimum execution time: 3_201_000 picoseconds.
-		Weight::from_parts(3_442_000, 0)
+		// Minimum execution time: 2_533_000 picoseconds.
+		Weight::from_parts(2_817_000, 0)
 			.saturating_add(RocksDbWeight::get().writes(1_u64))
 	}
-	/// Storage: `Balances::Locks` (r:1 w:1)
-	/// Proof: `Balances::Locks` (`max_values`: None, `max_size`: Some(1299), added: 3774, mode: `MaxEncodedLen`)
-	/// Storage: `System::Account` (r:1 w:1)
+	/// Storage: `Staking::VirtualStakers` (r:1 w:0)
+	/// Proof: `Staking::VirtualStakers` (`max_values`: None, `max_size`: Some(40), added: 2515, mode: `MaxEncodedLen`)
+	/// Storage: `Balances::Holds` (r:1 w:0)
+	/// Proof: `Balances::Holds` (`max_values`: None, `max_size`: Some(355), added: 2830, mode: `MaxEncodedLen`)
+	/// Storage: `System::Account` (r:1 w:0)
 	/// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`)
+	/// Storage: `Balances::Locks` (r:1 w:0)
+	/// Proof: `Balances::Locks` (`max_values`: None, `max_size`: Some(1299), added: 3774, mode: `MaxEncodedLen`)
 	/// Storage: `Staking::Bonded` (r:1 w:1)
 	/// Proof: `Staking::Bonded` (`max_values`: None, `max_size`: Some(72), added: 2547, mode: `MaxEncodedLen`)
 	/// Storage: `Staking::Ledger` (r:1 w:1)
 	/// Proof: `Staking::Ledger` (`max_values`: None, `max_size`: Some(1091), added: 3566, mode: `MaxEncodedLen`)
+	fn restore_ledger() -> Weight {
+		// Proof Size summary in bytes:
+		//  Measured:  `1110`
+		//  Estimated: `4764`
+		// Minimum execution time: 50_105_000 picoseconds.
+		Weight::from_parts(50_966_000, 4764)
+			.saturating_add(RocksDbWeight::get().reads(6_u64))
+			.saturating_add(RocksDbWeight::get().writes(2_u64))
+	}
+	/// Storage: `Staking::VirtualStakers` (r:1 w:0)
+	/// Proof: `Staking::VirtualStakers` (`max_values`: None, `max_size`: Some(40), added: 2515, mode: `MaxEncodedLen`)
+	/// Storage: `Staking::Bonded` (r:1 w:0)
+	/// Proof: `Staking::Bonded` (`max_values`: None, `max_size`: Some(72), added: 2547, mode: `MaxEncodedLen`)
+	/// Storage: `Staking::Ledger` (r:1 w:0)
+	/// Proof: `Staking::Ledger` (`max_values`: None, `max_size`: Some(1091), added: 3566, mode: `MaxEncodedLen`)
+	/// Storage: `Balances::Locks` (r:1 w:1)
+	/// Proof: `Balances::Locks` (`max_values`: None, `max_size`: Some(1299), added: 3774, mode: `MaxEncodedLen`)
+	/// Storage: `Balances::Holds` (r:1 w:1)
+	/// Proof: `Balances::Holds` (`max_values`: None, `max_size`: Some(355), added: 2830, mode: `MaxEncodedLen`)
 	/// Storage: `Balances::Freezes` (r:1 w:0)
 	/// Proof: `Balances::Freezes` (`max_values`: None, `max_size`: Some(67), added: 2542, mode: `MaxEncodedLen`)
-	fn restore_ledger() -> Weight {
+	fn migrate_currency() -> Weight {
 		// Proof Size summary in bytes:
-		//  Measured:  `1047`
+		//  Measured:  `1246`
 		//  Estimated: `4764`
-		// Minimum execution time: 44_671_000 picoseconds.
-		Weight::from_parts(45_611_000, 4764)
-			.saturating_add(RocksDbWeight::get().reads(5_u64))
-			.saturating_add(RocksDbWeight::get().writes(4_u64))
+		// Minimum execution time: 94_054_000 picoseconds.
+		Weight::from_parts(96_272_000, 4764)
+			.saturating_add(RocksDbWeight::get().reads(6_u64))
+			.saturating_add(RocksDbWeight::get().writes(2_u64))
 	}
 }
\ No newline at end of file
diff --git a/substrate/frame/support/src/traits.rs b/substrate/frame/support/src/traits.rs
index 728426cc84c71087015cea70dd5a23a50d49c982..4a83c809a6a5ece39a9818f7111c92e6e4b7b100 100644
--- a/substrate/frame/support/src/traits.rs
+++ b/substrate/frame/support/src/traits.rs
@@ -96,8 +96,9 @@ mod storage;
 #[cfg(feature = "experimental")]
 pub use storage::MaybeConsideration;
 pub use storage::{
-	Consideration, Footprint, Incrementable, Instance, LinearStoragePrice, PartialStorageInfoTrait,
-	StorageInfo, StorageInfoTrait, StorageInstance, TrackedStorageKey, WhitelistedStorageKeys,
+	Consideration, ConstantStoragePrice, Footprint, Incrementable, Instance, LinearStoragePrice,
+	PartialStorageInfoTrait, StorageInfo, StorageInfoTrait, StorageInstance, TrackedStorageKey,
+	WhitelistedStorageKeys,
 };
 
 mod dispatch;
diff --git a/substrate/frame/support/src/traits/storage.rs b/substrate/frame/support/src/traits/storage.rs
index 2b8e437073894cc03634bbd950313f541b59fe76..676b73e03d3c4472ebdb1ab024b6c9accb430ac1 100644
--- a/substrate/frame/support/src/traits/storage.rs
+++ b/substrate/frame/support/src/traits/storage.rs
@@ -200,6 +200,18 @@ where
 	}
 }
 
+/// Constant `Price` regardless of the given [`Footprint`].
+pub struct ConstantStoragePrice<Price, Balance>(PhantomData<(Price, Balance)>);
+impl<Price, Balance> Convert<Footprint, Balance> for ConstantStoragePrice<Price, Balance>
+where
+	Price: Get<Balance>,
+	Balance: From<u64> + sp_runtime::Saturating,
+{
+	fn convert(_: Footprint) -> Balance {
+		Price::get()
+	}
+}
+
 /// Some sort of cost taken from account temporarily in order to offset the cost to the chain of
 /// holding some data [`Footprint`] in state.
 ///
diff --git a/substrate/frame/support/test/stg_frame_crate/Cargo.toml b/substrate/frame/support/test/stg_frame_crate/Cargo.toml
index f627d29cd563067cdad9b48e4dc5a7ac9bdefbd8..157361dbd5d6dc1df062ebd2e4632da830b7362b 100644
--- a/substrate/frame/support/test/stg_frame_crate/Cargo.toml
+++ b/substrate/frame/support/test/stg_frame_crate/Cargo.toml
@@ -16,7 +16,7 @@ targets = ["x86_64-unknown-linux-gnu"]
 
 [dependencies]
 codec = { features = ["derive"], workspace = true }
-frame = { features = ["experimental", "runtime"], workspace = true }
+frame = { features = ["runtime"], workspace = true }
 scale-info = { features = ["derive"], workspace = true }
 
 [features]
diff --git a/substrate/frame/transaction-payment/asset-tx-payment/src/lib.rs b/substrate/frame/transaction-payment/asset-tx-payment/src/lib.rs
index dd752989c3662d2123c9d7783786bcb3d9de80d2..4a96cbcacb5804541aa6130702869b05cbd32637 100644
--- a/substrate/frame/transaction-payment/asset-tx-payment/src/lib.rs
+++ b/substrate/frame/transaction-payment/asset-tx-payment/src/lib.rs
@@ -202,7 +202,7 @@ where
 		debug_assert!(self.tip <= fee, "tip should be included in the computed fee");
 		if fee.is_zero() {
 			Ok((fee, InitialPayment::Nothing))
-		} else if let Some(asset_id) = self.asset_id {
+		} else if let Some(asset_id) = self.asset_id.clone() {
 			T::OnChargeAssetTransaction::withdraw_fee(
 				who,
 				call,
@@ -233,7 +233,7 @@ where
 		debug_assert!(self.tip <= fee, "tip should be included in the computed fee");
 		if fee.is_zero() {
 			Ok(())
-		} else if let Some(asset_id) = self.asset_id {
+		} else if let Some(asset_id) = self.asset_id.clone() {
 			T::OnChargeAssetTransaction::can_withdraw_fee(
 				who,
 				call,
@@ -358,7 +358,7 @@ where
 					tip,
 					who,
 					initial_payment,
-					asset_id: self.asset_id,
+					asset_id: self.asset_id.clone(),
 					weight: self.weight(call),
 				})
 			},
diff --git a/substrate/frame/transaction-payment/asset-tx-payment/src/payment.rs b/substrate/frame/transaction-payment/asset-tx-payment/src/payment.rs
index 2074b1476f45ae9187778a3c846e0b21a1097176..7b7ae855bf8f34bea492b98a912c2094fdd8343d 100644
--- a/substrate/frame/transaction-payment/asset-tx-payment/src/payment.rs
+++ b/substrate/frame/transaction-payment/asset-tx-payment/src/payment.rs
@@ -40,7 +40,7 @@ pub trait OnChargeAssetTransaction<T: Config> {
 	/// The underlying integer type in which fees are calculated.
 	type Balance: Balance;
 	/// The type used to identify the assets used for transaction payment.
-	type AssetId: FullCodec + Copy + MaybeSerializeDeserialize + Debug + Default + Eq + TypeInfo;
+	type AssetId: FullCodec + Clone + MaybeSerializeDeserialize + Debug + Default + Eq + TypeInfo;
 	/// The type used to store the intermediate values between pre- and post-dispatch.
 	type LiquidityInfo;
 
@@ -112,7 +112,7 @@ where
 	T: Config,
 	CON: ConversionToAssetBalance<BalanceOf<T>, AssetIdOf<T>, AssetBalanceOf<T>>,
 	HC: HandleCredit<T::AccountId, T::Fungibles>,
-	AssetIdOf<T>: FullCodec + Copy + MaybeSerializeDeserialize + Debug + Default + Eq + TypeInfo,
+	AssetIdOf<T>: FullCodec + Clone + MaybeSerializeDeserialize + Debug + Default + Eq + TypeInfo,
 {
 	type Balance = BalanceOf<T>;
 	type AssetId = AssetIdOf<T>;
@@ -133,11 +133,14 @@ where
 		// less than one (e.g. 0.5) but gets rounded down by integer division we introduce a minimum
 		// fee.
 		let min_converted_fee = if fee.is_zero() { Zero::zero() } else { One::one() };
-		let converted_fee = CON::to_asset_balance(fee, asset_id)
+		let converted_fee = CON::to_asset_balance(fee, asset_id.clone())
 			.map_err(|_| TransactionValidityError::from(InvalidTransaction::Payment))?
 			.max(min_converted_fee);
-		let can_withdraw =
-			<T::Fungibles as Inspect<T::AccountId>>::can_withdraw(asset_id, who, converted_fee);
+		let can_withdraw = <T::Fungibles as Inspect<T::AccountId>>::can_withdraw(
+			asset_id.clone(),
+			who,
+			converted_fee,
+		);
 		if can_withdraw != WithdrawConsequence::Success {
 			return Err(InvalidTransaction::Payment.into())
 		}
@@ -167,7 +170,7 @@ where
 		// less than one (e.g. 0.5) but gets rounded down by integer division we introduce a minimum
 		// fee.
 		let min_converted_fee = if fee.is_zero() { Zero::zero() } else { One::one() };
-		let converted_fee = CON::to_asset_balance(fee, asset_id)
+		let converted_fee = CON::to_asset_balance(fee, asset_id.clone())
 			.map_err(|_| TransactionValidityError::from(InvalidTransaction::Payment))?
 			.max(min_converted_fee);
 		let can_withdraw =
diff --git a/substrate/primitives/staking/src/lib.rs b/substrate/primitives/staking/src/lib.rs
index 17010a8907fc23325d2726e59285313f5fea53a6..8e23c6800a9d53a240261b51a7f87dd20017b9e0 100644
--- a/substrate/primitives/staking/src/lib.rs
+++ b/substrate/primitives/staking/src/lib.rs
@@ -325,7 +325,7 @@ pub trait StakingUnchecked: StakingInterface {
 	/// Migrate an existing staker to a virtual staker.
 	///
 	/// It would release all funds held by the implementation pallet.
-	fn migrate_to_virtual_staker(who: &Self::AccountId);
+	fn migrate_to_virtual_staker(who: &Self::AccountId) -> DispatchResult;
 
 	/// Book-keep a new bond for `keyless_who` without applying any locks (hence virtual).
 	///
diff --git a/umbrella/Cargo.toml b/umbrella/Cargo.toml
index 17a7c02e82592c5765f7290183e8ee6711e44aff..fc0b2d5a140ed18419a3cece320f03f6fab8b711 100644
--- a/umbrella/Cargo.toml
+++ b/umbrella/Cargo.toml
@@ -57,6 +57,7 @@ std = [
 	"pallet-asset-conversion-tx-payment?/std",
 	"pallet-asset-conversion?/std",
 	"pallet-asset-rate?/std",
+	"pallet-asset-rewards?/std",
 	"pallet-asset-tx-payment?/std",
 	"pallet-assets-freezer?/std",
 	"pallet-assets?/std",
@@ -256,6 +257,7 @@ runtime-benchmarks = [
 	"pallet-asset-conversion-tx-payment?/runtime-benchmarks",
 	"pallet-asset-conversion?/runtime-benchmarks",
 	"pallet-asset-rate?/runtime-benchmarks",
+	"pallet-asset-rewards?/runtime-benchmarks",
 	"pallet-asset-tx-payment?/runtime-benchmarks",
 	"pallet-assets-freezer?/runtime-benchmarks",
 	"pallet-assets?/runtime-benchmarks",
@@ -386,6 +388,7 @@ try-runtime = [
 	"pallet-asset-conversion-tx-payment?/try-runtime",
 	"pallet-asset-conversion?/try-runtime",
 	"pallet-asset-rate?/try-runtime",
+	"pallet-asset-rewards?/try-runtime",
 	"pallet-asset-tx-payment?/try-runtime",
 	"pallet-assets-freezer?/try-runtime",
 	"pallet-assets?/try-runtime",
@@ -543,7 +546,7 @@ with-tracing = [
 	"sp-tracing?/with-tracing",
 	"sp-tracing?/with-tracing",
 ]
-runtime-full = ["assets-common", "binary-merkle-tree", "bp-header-chain", "bp-messages", "bp-parachains", "bp-polkadot", "bp-polkadot-core", "bp-relayers", "bp-runtime", "bp-test-utils", "bp-xcm-bridge-hub", "bp-xcm-bridge-hub-router", "bridge-hub-common", "bridge-runtime-common", "cumulus-pallet-aura-ext", "cumulus-pallet-dmp-queue", "cumulus-pallet-parachain-system", "cumulus-pallet-parachain-system-proc-macro", "cumulus-pallet-session-benchmarking", "cumulus-pallet-solo-to-para", "cumulus-pallet-weight-reclaim", "cumulus-pallet-xcm", "cumulus-pallet-xcmp-queue", "cumulus-ping", "cumulus-primitives-aura", "cumulus-primitives-core", "cumulus-primitives-parachain-inherent", "cumulus-primitives-proof-size-hostfunction", "cumulus-primitives-storage-weight-reclaim", "cumulus-primitives-timestamp", "cumulus-primitives-utility", "frame-benchmarking", "frame-benchmarking-pallet-pov", "frame-election-provider-solution-type", "frame-election-provider-support", "frame-executive", "frame-metadata-hash-extension", "frame-support", "frame-support-procedural", "frame-support-procedural-tools-derive", "frame-system", "frame-system-benchmarking", "frame-system-rpc-runtime-api", "frame-try-runtime", "pallet-alliance", "pallet-asset-conversion", "pallet-asset-conversion-ops", "pallet-asset-conversion-tx-payment", "pallet-asset-rate", "pallet-asset-tx-payment", "pallet-assets", "pallet-assets-freezer", "pallet-atomic-swap", "pallet-aura", "pallet-authority-discovery", "pallet-authorship", "pallet-babe", "pallet-bags-list", "pallet-balances", "pallet-beefy", "pallet-beefy-mmr", "pallet-bounties", "pallet-bridge-grandpa", "pallet-bridge-messages", "pallet-bridge-parachains", "pallet-bridge-relayers", "pallet-broker", "pallet-child-bounties", "pallet-collator-selection", "pallet-collective", "pallet-collective-content", "pallet-contracts", "pallet-contracts-proc-macro", "pallet-contracts-uapi", "pallet-conviction-voting", "pallet-core-fellowship", "pallet-delegated-staking", "pallet-democracy", "pallet-dev-mode", "pallet-election-provider-multi-phase", "pallet-election-provider-support-benchmarking", "pallet-elections-phragmen", "pallet-fast-unstake", "pallet-glutton", "pallet-grandpa", "pallet-identity", "pallet-im-online", "pallet-indices", "pallet-insecure-randomness-collective-flip", "pallet-lottery", "pallet-membership", "pallet-message-queue", "pallet-migrations", "pallet-mixnet", "pallet-mmr", "pallet-multisig", "pallet-nft-fractionalization", "pallet-nfts", "pallet-nfts-runtime-api", "pallet-nis", "pallet-node-authorization", "pallet-nomination-pools", "pallet-nomination-pools-benchmarking", "pallet-nomination-pools-runtime-api", "pallet-offences", "pallet-offences-benchmarking", "pallet-paged-list", "pallet-parameters", "pallet-preimage", "pallet-proxy", "pallet-ranked-collective", "pallet-recovery", "pallet-referenda", "pallet-remark", "pallet-revive", "pallet-revive-proc-macro", "pallet-revive-uapi", "pallet-root-offences", "pallet-root-testing", "pallet-safe-mode", "pallet-salary", "pallet-scheduler", "pallet-scored-pool", "pallet-session", "pallet-session-benchmarking", "pallet-skip-feeless-payment", "pallet-society", "pallet-staking", "pallet-staking-reward-curve", "pallet-staking-reward-fn", "pallet-staking-runtime-api", "pallet-state-trie-migration", "pallet-statement", "pallet-sudo", "pallet-timestamp", "pallet-tips", "pallet-transaction-payment", "pallet-transaction-payment-rpc-runtime-api", "pallet-transaction-storage", "pallet-treasury", "pallet-tx-pause", "pallet-uniques", "pallet-utility", "pallet-verify-signature", "pallet-vesting", "pallet-whitelist", "pallet-xcm", "pallet-xcm-benchmarks", "pallet-xcm-bridge-hub", "pallet-xcm-bridge-hub-router", "parachains-common", "polkadot-core-primitives", "polkadot-parachain-primitives", "polkadot-primitives", "polkadot-runtime-common", "polkadot-runtime-metrics", "polkadot-runtime-parachains", "polkadot-sdk-frame", "sc-chain-spec-derive", "sc-tracing-proc-macro", "slot-range-helper", "snowbridge-beacon-primitives", "snowbridge-core", "snowbridge-ethereum", "snowbridge-outbound-queue-merkle-tree", "snowbridge-outbound-queue-runtime-api", "snowbridge-pallet-ethereum-client", "snowbridge-pallet-ethereum-client-fixtures", "snowbridge-pallet-inbound-queue", "snowbridge-pallet-inbound-queue-fixtures", "snowbridge-pallet-outbound-queue", "snowbridge-pallet-system", "snowbridge-router-primitives", "snowbridge-runtime-common", "snowbridge-system-runtime-api", "sp-api", "sp-api-proc-macro", "sp-application-crypto", "sp-arithmetic", "sp-authority-discovery", "sp-block-builder", "sp-consensus-aura", "sp-consensus-babe", "sp-consensus-beefy", "sp-consensus-grandpa", "sp-consensus-pow", "sp-consensus-slots", "sp-core", "sp-crypto-ec-utils", "sp-crypto-hashing", "sp-crypto-hashing-proc-macro", "sp-debug-derive", "sp-externalities", "sp-genesis-builder", "sp-inherents", "sp-io", "sp-keyring", "sp-keystore", "sp-metadata-ir", "sp-mixnet", "sp-mmr-primitives", "sp-npos-elections", "sp-offchain", "sp-runtime", "sp-runtime-interface", "sp-runtime-interface-proc-macro", "sp-session", "sp-staking", "sp-state-machine", "sp-statement-store", "sp-std", "sp-storage", "sp-timestamp", "sp-tracing", "sp-transaction-pool", "sp-transaction-storage-proof", "sp-trie", "sp-version", "sp-version-proc-macro", "sp-wasm-interface", "sp-weights", "staging-parachain-info", "staging-xcm", "staging-xcm-builder", "staging-xcm-executor", "substrate-bip39", "testnet-parachains-constants", "tracing-gum-proc-macro", "xcm-procedural", "xcm-runtime-apis"]
+runtime-full = ["assets-common", "binary-merkle-tree", "bp-header-chain", "bp-messages", "bp-parachains", "bp-polkadot", "bp-polkadot-core", "bp-relayers", "bp-runtime", "bp-test-utils", "bp-xcm-bridge-hub", "bp-xcm-bridge-hub-router", "bridge-hub-common", "bridge-runtime-common", "cumulus-pallet-aura-ext", "cumulus-pallet-dmp-queue", "cumulus-pallet-parachain-system", "cumulus-pallet-parachain-system-proc-macro", "cumulus-pallet-session-benchmarking", "cumulus-pallet-solo-to-para", "cumulus-pallet-weight-reclaim", "cumulus-pallet-xcm", "cumulus-pallet-xcmp-queue", "cumulus-ping", "cumulus-primitives-aura", "cumulus-primitives-core", "cumulus-primitives-parachain-inherent", "cumulus-primitives-proof-size-hostfunction", "cumulus-primitives-storage-weight-reclaim", "cumulus-primitives-timestamp", "cumulus-primitives-utility", "frame-benchmarking", "frame-benchmarking-pallet-pov", "frame-election-provider-solution-type", "frame-election-provider-support", "frame-executive", "frame-metadata-hash-extension", "frame-support", "frame-support-procedural", "frame-support-procedural-tools-derive", "frame-system", "frame-system-benchmarking", "frame-system-rpc-runtime-api", "frame-try-runtime", "pallet-alliance", "pallet-asset-conversion", "pallet-asset-conversion-ops", "pallet-asset-conversion-tx-payment", "pallet-asset-rate", "pallet-asset-rewards", "pallet-asset-tx-payment", "pallet-assets", "pallet-assets-freezer", "pallet-atomic-swap", "pallet-aura", "pallet-authority-discovery", "pallet-authorship", "pallet-babe", "pallet-bags-list", "pallet-balances", "pallet-beefy", "pallet-beefy-mmr", "pallet-bounties", "pallet-bridge-grandpa", "pallet-bridge-messages", "pallet-bridge-parachains", "pallet-bridge-relayers", "pallet-broker", "pallet-child-bounties", "pallet-collator-selection", "pallet-collective", "pallet-collective-content", "pallet-contracts", "pallet-contracts-proc-macro", "pallet-contracts-uapi", "pallet-conviction-voting", "pallet-core-fellowship", "pallet-delegated-staking", "pallet-democracy", "pallet-dev-mode", "pallet-election-provider-multi-phase", "pallet-election-provider-support-benchmarking", "pallet-elections-phragmen", "pallet-fast-unstake", "pallet-glutton", "pallet-grandpa", "pallet-identity", "pallet-im-online", "pallet-indices", "pallet-insecure-randomness-collective-flip", "pallet-lottery", "pallet-membership", "pallet-message-queue", "pallet-migrations", "pallet-mixnet", "pallet-mmr", "pallet-multisig", "pallet-nft-fractionalization", "pallet-nfts", "pallet-nfts-runtime-api", "pallet-nis", "pallet-node-authorization", "pallet-nomination-pools", "pallet-nomination-pools-benchmarking", "pallet-nomination-pools-runtime-api", "pallet-offences", "pallet-offences-benchmarking", "pallet-paged-list", "pallet-parameters", "pallet-preimage", "pallet-proxy", "pallet-ranked-collective", "pallet-recovery", "pallet-referenda", "pallet-remark", "pallet-revive", "pallet-revive-proc-macro", "pallet-revive-uapi", "pallet-root-offences", "pallet-root-testing", "pallet-safe-mode", "pallet-salary", "pallet-scheduler", "pallet-scored-pool", "pallet-session", "pallet-session-benchmarking", "pallet-skip-feeless-payment", "pallet-society", "pallet-staking", "pallet-staking-reward-curve", "pallet-staking-reward-fn", "pallet-staking-runtime-api", "pallet-state-trie-migration", "pallet-statement", "pallet-sudo", "pallet-timestamp", "pallet-tips", "pallet-transaction-payment", "pallet-transaction-payment-rpc-runtime-api", "pallet-transaction-storage", "pallet-treasury", "pallet-tx-pause", "pallet-uniques", "pallet-utility", "pallet-verify-signature", "pallet-vesting", "pallet-whitelist", "pallet-xcm", "pallet-xcm-benchmarks", "pallet-xcm-bridge-hub", "pallet-xcm-bridge-hub-router", "parachains-common", "polkadot-core-primitives", "polkadot-parachain-primitives", "polkadot-primitives", "polkadot-runtime-common", "polkadot-runtime-metrics", "polkadot-runtime-parachains", "polkadot-sdk-frame", "sc-chain-spec-derive", "sc-tracing-proc-macro", "slot-range-helper", "snowbridge-beacon-primitives", "snowbridge-core", "snowbridge-ethereum", "snowbridge-outbound-queue-merkle-tree", "snowbridge-outbound-queue-runtime-api", "snowbridge-pallet-ethereum-client", "snowbridge-pallet-ethereum-client-fixtures", "snowbridge-pallet-inbound-queue", "snowbridge-pallet-inbound-queue-fixtures", "snowbridge-pallet-outbound-queue", "snowbridge-pallet-system", "snowbridge-router-primitives", "snowbridge-runtime-common", "snowbridge-system-runtime-api", "sp-api", "sp-api-proc-macro", "sp-application-crypto", "sp-arithmetic", "sp-authority-discovery", "sp-block-builder", "sp-consensus-aura", "sp-consensus-babe", "sp-consensus-beefy", "sp-consensus-grandpa", "sp-consensus-pow", "sp-consensus-slots", "sp-core", "sp-crypto-ec-utils", "sp-crypto-hashing", "sp-crypto-hashing-proc-macro", "sp-debug-derive", "sp-externalities", "sp-genesis-builder", "sp-inherents", "sp-io", "sp-keyring", "sp-keystore", "sp-metadata-ir", "sp-mixnet", "sp-mmr-primitives", "sp-npos-elections", "sp-offchain", "sp-runtime", "sp-runtime-interface", "sp-runtime-interface-proc-macro", "sp-session", "sp-staking", "sp-state-machine", "sp-statement-store", "sp-std", "sp-storage", "sp-timestamp", "sp-tracing", "sp-transaction-pool", "sp-transaction-storage-proof", "sp-trie", "sp-version", "sp-version-proc-macro", "sp-wasm-interface", "sp-weights", "staging-parachain-info", "staging-xcm", "staging-xcm-builder", "staging-xcm-executor", "substrate-bip39", "testnet-parachains-constants", "tracing-gum-proc-macro", "xcm-procedural", "xcm-runtime-apis"]
 runtime = [
 	"frame-benchmarking",
 	"frame-benchmarking-pallet-pov",
@@ -870,6 +873,11 @@ default-features = false
 optional = true
 path = "../substrate/frame/asset-rate"
 
+[dependencies.pallet-asset-rewards]
+default-features = false
+optional = true
+path = "../substrate/frame/asset-rewards"
+
 [dependencies.pallet-asset-tx-payment]
 default-features = false
 optional = true
diff --git a/umbrella/src/lib.rs b/umbrella/src/lib.rs
index 3504f081f2957b5a9a8eccaea15343281ee98292..a132f16a2c33f617ee1a916deb585534d3e6f99c 100644
--- a/umbrella/src/lib.rs
+++ b/umbrella/src/lib.rs
@@ -312,6 +312,10 @@ pub use pallet_asset_conversion_tx_payment;
 #[cfg(feature = "pallet-asset-rate")]
 pub use pallet_asset_rate;
 
+/// FRAME asset rewards pallet.
+#[cfg(feature = "pallet-asset-rewards")]
+pub use pallet_asset_rewards;
+
 /// pallet to manage transaction payments in assets.
 #[cfg(feature = "pallet-asset-tx-payment")]
 pub use pallet_asset_tx_payment;