diff --git a/.github/workflows/branch-frontend.yml b/.github/workflows/branch-frontend.yml index 8ede7d67c..2751c830d 100644 --- a/.github/workflows/branch-frontend.yml +++ b/.github/workflows/branch-frontend.yml @@ -18,7 +18,7 @@ concurrency: jobs: init: name: Set global version - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 outputs: GLOBAL_VERSION: ${{ steps.set_vars.outputs.GLOBAL_VERSION }} steps: diff --git a/.github/workflows/branch-main.yml b/.github/workflows/branch-main.yml index 664b4ccfc..70964d994 100644 --- a/.github/workflows/branch-main.yml +++ b/.github/workflows/branch-main.yml @@ -2,13 +2,14 @@ name: main branch workflow on: push: - branches: [main, feat/add-main-branch-ci-pipeline-MGX-834] + branches: [main] permissions: contents: write id-token: write deployments: write checks: write + security-events: write # The following concurrency group queus in-progress jobs concurrency: @@ -18,25 +19,94 @@ concurrency: jobs: init: name: Set global version - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 outputs: GLOBAL_VERSION: ${{ steps.set_vars.outputs.GLOBAL_VERSION }} + GIT_BRANCH: ${{ steps.set_vars.outputs.GIT_BRANCH }} + GIT_BRANCH_UNFORMATTED: ${{ steps.branch-name.outputs.current_branch }} steps: + - name: Get branch name + id: branch-name + uses: tj-actions/branch-names@v8 - name: Set global version id: set_vars - run: echo "GLOBAL_VERSION=${{ github.sha }}" >> $GITHUB_OUTPUT + run: | + echo "GLOBAL_VERSION=${{ github.sha }}" >> $GITHUB_OUTPUT + echo "GIT_BRANCH=${{ steps.branch-name.outputs.current_branch }}" | sed "s@/@-@g" >> $GITHUB_OUTPUT + echo "IMAGE_TAG=${{ github.sha }}" >> $GITHUB_STEP_SUMMARY build-and-test: needs: [init] - name: Build + name: Build AVS services uses: ./.github/workflows/reusable-build-and-test.yml secrets: inherit with: version: ${{ needs.init.outputs.GLOBAL_VERSION }} + + build-and-test-gasp-node: + needs: [init] + name: Build gasp-node + uses: ./.github/workflows/reusable-gasp-node-build-and-test.yml + secrets: inherit + permissions: + contents: read + actions: read + checks: write + id-token: write + with: + version: ${{ needs.init.outputs.GLOBAL_VERSION }} + branch: ${{ needs.init.outputs.GIT_BRANCH }} + + gasp-node-unit-tests-and-coverage: + name: '[gasp-node] Run unit tests and coverage' + needs: [init] + uses: ./.github/workflows/reusable-gasp-node-unit-tests-and-coverage.yml + secrets: inherit + permissions: + contents: read + actions: read + checks: write + id-token: write + with: + version: ${{ needs.init.outputs.GLOBAL_VERSION }} + branch: ${{ needs.init.outputs.GIT_BRANCH }} + + gasp-node-generate-types: + name: '[gasp-node] Generate types' + needs: [init, build-and-test-gasp-node] + uses: ./.github/workflows/reusable-gasp-node-generate-types.yml + secrets: inherit + with: + branch: ${{ needs.init.outputs.GIT_BRANCH_UNFORMATTED }} + globalVersion: ${{ needs.init.outputs.GLOBAL_VERSION }} + + run-e2e-test: + name: Run e2e tests + needs: [init, build-and-test] + uses: ./.github/workflows/reusable-e2e-tests.yml + secrets: inherit + permissions: + contents: read + actions: read + checks: write + with: + globalVersion: ${{ needs.init.outputs.GLOBAL_VERSION }} + + run-e2e-test-gasp-node: + name: '[gasp-node] Run e2e tests' + needs: [init, build-and-test-gasp-node, gasp-node-generate-types] + uses: ./.github/workflows/reusable-gasp-node-e2e-tests.yml + secrets: inherit + permissions: + contents: read + actions: read + checks: write + with: + globalVersion: ${{ needs.init.outputs.GLOBAL_VERSION }} deploy-dev: name: Deploy `dev` environment - needs: [init, build-and-test] + needs: [init, build-and-test, build-and-test-gasp-node, gasp-node-unit-tests-and-coverage] uses: ./.github/workflows/reusable-deploy.yml secrets: inherit with: diff --git a/.github/workflows/deploy-rollup-holesky.yml b/.github/workflows/deploy-rollup-holesky.yml index 582c2cb8d..e651d090b 100644 --- a/.github/workflows/deploy-rollup-holesky.yml +++ b/.github/workflows/deploy-rollup-holesky.yml @@ -11,7 +11,7 @@ permissions: jobs: init: name: Set global version - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 outputs: GLOBAL_VERSION: ${{ steps.set_vars.outputs.GLOBAL_VERSION }} steps: @@ -29,7 +29,7 @@ jobs: deploy-rollup-holesky: needs: [init, build-and-test] - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 steps: - uses: actions/checkout@v4 - uses: jkroepke/setup-vals@v1.1.3 diff --git a/.github/workflows/stash.yml b/.github/workflows/deploy-stash.yml similarity index 92% rename from .github/workflows/stash.yml rename to .github/workflows/deploy-stash.yml index 8afec359f..202a4da5b 100644 --- a/.github/workflows/stash.yml +++ b/.github/workflows/deploy-stash.yml @@ -1,4 +1,4 @@ -name: stash service workflow +name: '[stash] Deploy `stash` to Google App Engine' on: workflow_dispatch: inputs: @@ -11,7 +11,6 @@ on: - frontend - holesky - prod - pull_request: push: branches: - main @@ -26,7 +25,7 @@ permissions: jobs: build: name: Build Project - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 defaults: run: working-directory: stash @@ -53,9 +52,8 @@ jobs: deploy-frontend: name: Deploy `stash` to `frontend` environment if: github.ref == 'refs/heads/main' || (github.event_name == 'workflow_dispatch' && github.event.inputs.environment == 'frontend') - runs-on: ubuntu-latest - needs: build - environment: stash-frontend + runs-on: ubuntu-24.04 + environment: "stash-${{ github.event.inputs.environment}}" defaults: run: working-directory: stash @@ -82,12 +80,12 @@ jobs: deploy-holesky: name: Deploy `stash` to `holesky` environment if: (github.event_name == 'workflow_dispatch' && github.event.inputs.environment == 'holesky') && (github.ref == 'refs/heads/main' || startsWith(github.ref, 'refs/heads/release/')) - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 needs: build defaults: run: working-directory: stash - environment: stash-holesky + environment: "stash-${{ github.event.inputs.environment}}" env: ENVIRONMENT: holesky steps: @@ -111,12 +109,12 @@ jobs: deploy-prod: name: Deploy `stash` to `prod` environment if: (github.event_name == 'workflow_dispatch' && github.event.inputs.environment == 'prod') && (github.ref == 'refs/heads/main' || startsWith(github.ref, 'refs/heads/release/')) - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 needs: build defaults: run: working-directory: stash - environment: stash-prod + environment: "stash-${{ github.event.inputs.environment}}" env: ENVIRONMENT: prod steps: diff --git a/.github/workflows/faucet-deploy-app-engine.yml b/.github/workflows/faucet-deploy-app-engine.yml index 857a9d611..82d329abf 100644 --- a/.github/workflows/faucet-deploy-app-engine.yml +++ b/.github/workflows/faucet-deploy-app-engine.yml @@ -17,7 +17,7 @@ env: jobs: build-and-deploy: - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 steps: - name: Checkout repository diff --git a/.github/workflows/pull-request.yml b/.github/workflows/pull-request.yml index 3f794eb67..358946295 100644 --- a/.github/workflows/pull-request.yml +++ b/.github/workflows/pull-request.yml @@ -8,6 +8,7 @@ permissions: id-token: write deployments: write checks: write + security-events: write # The following concurrency group cancels in-progress jobs or runs on pull_request events only # https://docs.github.com/en/actions/using-jobs/using-concurrency#example-using-a-fallback-value @@ -19,35 +20,66 @@ jobs: init: name: Set global version if: github.event.action != 'unlabeled' && github.event.action != 'closed' - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 outputs: GLOBAL_VERSION: ${{ steps.set_vars.outputs.GLOBAL_VERSION }} + GIT_BRANCH: ${{ steps.set_vars.outputs.GIT_BRANCH }} + GIT_BRANCH_UNFORMATTED: ${{ steps.branch-name.outputs.current_branch }} steps: + - name: Get branch name + id: branch-name + uses: tj-actions/branch-names@v8 - name: Set global version id: set_vars run: | echo "GLOBAL_VERSION=${{ github.sha }}" >> $GITHUB_OUTPUT - echo "IMAGE TAG=${{ github.sha }}" >> $GITHUB_STEP_SUMMARY + echo "GIT_BRANCH=${{ steps.branch-name.outputs.current_branch }}" | sed "s@/@-@g" >> $GITHUB_OUTPUT + echo "IMAGE_TAG=${{ github.sha }}" >> $GITHUB_STEP_SUMMARY build-and-test: needs: [init] - name: Build + name: '[AVS services] Build' uses: ./.github/workflows/reusable-build-and-test.yml secrets: inherit with: version: ${{ needs.init.outputs.GLOBAL_VERSION }} - - deploy-fungible: - name: Deploy fungible environment - if: | - (github.event_name == 'pull_request' && github.event.action == 'labeled' && github.event.label.name == ':rocket: deploy_fungible') || - (github.event_name == 'pull_request' && github.event.action != 'labeled' && contains(github.event.pull_request.labels.*.name, ':rocket: deploy_fungible')) - needs: [init, build-and-test] - uses: ./.github/workflows/reusable-deploy.yml + + build-and-test-gasp-node: + needs: [init] + name: '[gasp-node] Build' + uses: ./.github/workflows/reusable-gasp-node-build-and-test.yml + secrets: inherit + permissions: + contents: read + actions: read + checks: write + id-token: write + with: + version: ${{ needs.init.outputs.GLOBAL_VERSION }} + branch: ${{ needs.init.outputs.GIT_BRANCH }} + + gasp-node-unit-tests-and-coverage: + name: '[gasp-node] Run unit tests and coverage' + needs: [init] + uses: ./.github/workflows/reusable-gasp-node-unit-tests-and-coverage.yml secrets: inherit + permissions: + contents: read + actions: read + checks: write + id-token: write with: - env: fungible version: ${{ needs.init.outputs.GLOBAL_VERSION }} + branch: ${{ needs.init.outputs.GIT_BRANCH }} + + gasp-node-generate-types: + name: '[gasp-node] Generate types' + needs: [init, build-and-test-gasp-node] + uses: ./.github/workflows/reusable-gasp-node-generate-types.yml + secrets: inherit + with: + branch: ${{ needs.init.outputs.GIT_BRANCH_UNFORMATTED }} + globalVersion: ${{ needs.init.outputs.GLOBAL_VERSION }} run-e2e-test: name: Run e2e tests @@ -60,13 +92,37 @@ jobs: checks: write with: globalVersion: ${{ needs.init.outputs.GLOBAL_VERSION }} + + run-e2e-test-gasp-node: + name: '[gasp-node] Run e2e tests' + needs: [init, build-and-test-gasp-node, gasp-node-generate-types] + uses: ./.github/workflows/reusable-gasp-node-e2e-tests.yml + secrets: inherit + permissions: + contents: read + actions: read + checks: write + with: + globalVersion: ${{ needs.init.outputs.GLOBAL_VERSION }} + + deploy-fungible: + name: Deploy fungible environment + if: | + (github.event_name == 'pull_request' && github.event.action == 'labeled' && github.event.label.name == ':rocket: deploy_fungible') || + (github.event_name == 'pull_request' && github.event.action != 'labeled' && contains(github.event.pull_request.labels.*.name, ':rocket: deploy_fungible')) + needs: [init, build-and-test, build-and-test-gasp-node, gasp-node-unit-tests-and-coverage] + uses: ./.github/workflows/reusable-deploy.yml + secrets: inherit + with: + env: fungible + version: ${{ needs.init.outputs.GLOBAL_VERSION }} clean-up-fungible: name: Delete fungible environment if: | (github.event_name == 'pull_request' && github.event.action == 'unlabeled' && github.event.label.name == ':rocket: deploy_fungible') || (github.event_name == 'pull_request' && github.event.action == 'closed' && contains(github.event.pull_request.labels.*.name, ':rocket: deploy_fungible')) - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 steps: - uses: actions/checkout@v4 - uses: google-github-actions/auth@v2 @@ -74,7 +130,7 @@ jobs: workload_identity_provider: ${{ secrets.GCP_WORKLOAD_IDENTITY_PROVIDER }} service_account: ${{ secrets.GCP_SERVICE_ACCOUNT }} - name: Set up GKE credentials - uses: google-github-actions/get-gke-credentials@v0.8.2 + uses: google-github-actions/get-gke-credentials@v2.3.0 with: cluster_name: mangata-dev-alpha location: europe-west1 @@ -84,7 +140,7 @@ jobs: - name: Delete GitHub Deployment environment if: always() - uses: bobheadxi/deployments@v1.4.0 + uses: bobheadxi/deployments@v1.5.0 with: step: deactivate-env token: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/reusable-build-and-test.yml b/.github/workflows/reusable-build-and-test.yml index 5490039dd..9173331da 100644 --- a/.github/workflows/reusable-build-and-test.yml +++ b/.github/workflows/reusable-build-and-test.yml @@ -11,29 +11,97 @@ on: permissions: contents: read id-token: write + security-events: write jobs: - foundry-tests: - name: Run Foundry tests - runs-on: ubuntu-latest + contracts-tests: + name: '[contracts] Run Foundry checks and tests' + runs-on: ubuntu-24.04 + defaults: + run: + working-directory: contracts steps: - uses: actions/checkout@v4 with: - submodules: true - + submodules: recursive + - uses: foundry-rs/foundry-toolchain@v1 - - name: Install `contracts` deps - working-directory: contracts - run: forge install + + - uses: actions/setup-python@v5 + with: + python-version-file: 'contracts/.python-version' + cache: pip + - run: pip install -r requirements.txt + + - uses: oven-sh/setup-bun@v2 + - run: bun install + + - run: bun run compile + - run: bun run size + - run: bun run format + - run: bun run lint + + - name: Run unit tests + run: bun run test + + - name: Create gas report + run: bun run gas + + - name: Create coverage report + run: bun run cover + + contracts-static-analysis: + name: '[contracts] Run static analysis with `slither`' + runs-on: ubuntu-24.04 + permissions: + security-events: write + steps: + - uses: actions/checkout@v4 + with: + submodules: recursive + + - name: Make static analysis of contracts + uses: crytic/slither-action@v0.4.0 + id: slither + with: + target: contracts + sarif: results.sarif + fail-on: none + + - name: Upload SARIF file + uses: github/codeql-action/upload-sarif@v3 + with: + sarif_file: ${{ steps.slither.outputs.sarif }} - - uses: foundry-rs/foundry-toolchain@v1 - - name: Run forge tests - working-directory: contracts - run: forge test + build-and-test-stash: + name: '[stash] Build and run tests' + runs-on: ubuntu-24.04 + defaults: + run: + working-directory: stash + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-node@v4 + with: + node-version: '18' + cache: 'yarn' + cache-dependency-path: "./stash/yarn.lock" + + - uses: google-github-actions/auth@v2 + id: auth + with: + workload_identity_provider: ${{ secrets.GCP_WORKLOAD_IDENTITY_PROVIDER }} + service_account: ${{ secrets.GCP_SERVICE_ACCOUNT }} + - uses: google-github-actions/setup-gcloud@v2 + - uses: mdgreenwald/mozilla-sops-action@v1.6.0 + + - run: yarn install --immutable + - run: sops exec-env frontend.enc.env 'yarn run build' + - run: sops exec-env frontend.enc.env 'yarn run test:unit' build-foundry-deployer-image: - name: Build foundry deployer Docker image with smart contracts code - runs-on: ubuntu-latest + name: '[contracts] Build foundry deployer Docker image with smart contracts code' + runs-on: ubuntu-24.04 steps: - uses: actions/checkout@v4 with: @@ -50,8 +118,8 @@ jobs: docker buildx build --push --platform linux/amd64 -t gaspxyz/gasp-contracts:${{ inputs.version }} -f contracts/Dockerfile contracts build-avs-aggregator-image: - name: Build avs-aggregator Docker image - runs-on: ubuntu-latest + name: '[avs-aggregator] Build avs-aggregator Docker image' + runs-on: ubuntu-24.04 steps: - uses: actions/checkout@v4 with: @@ -62,7 +130,7 @@ jobs: docker buildx build --push --platform linux/amd64,linux/arm64 -t gaspxyz/avs-aggregator:${{ inputs.version }} -f avs-aggregator/Dockerfile . build-gasp-avs-image: - name: Build gasp-avs Docker image + name: '[gasp-avs] Build gasp-avs Docker image' uses: ./.github/workflows/reusable-rust-build.yml secrets: inherit with: @@ -71,7 +139,7 @@ jobs: docker_image_repository: gaspxyz/gasp-avs build-gasp-avs-image-with-fast-runtime: - name: Build gasp-avs Docker image with fast runtime + name: '[gasp-avs] Build gasp-avs Docker image with fast runtime' uses: ./.github/workflows/reusable-rust-build.yml secrets: inherit with: @@ -81,7 +149,7 @@ jobs: gasp_avs_fast_runtime: true build-updater-image: - name: Build updater Docker image + name: '[updater] Build updater Docker image' uses: ./.github/workflows/reusable-rust-build.yml secrets: inherit with: @@ -90,7 +158,7 @@ jobs: docker_image_repository: gaspxyz/updater build-sequencer-image: - name: Build sequencer Docker image + name: '[sequencer] Build sequencer Docker image' uses: ./.github/workflows/reusable-rust-build.yml secrets: inherit with: @@ -100,8 +168,8 @@ jobs: cargo_tests_filters: "l1::test::test_can_connect sequencer::test::test_find_malicious_update_ignores_updates_from_other_chains sequencer::test::test_find_malicious_update_ignores_valid_updates sequencer::test::test_find_malicious_update_with_invalid_range_works sequencer::test::test_find_malicious_update_works sequencer::test::test_find_pending_cancels_ignores_closed_cancels sequencer::test::test_find_pending_cancels_to_close sequencer::test::test_find_pending_cancels_to_close2 sequencer::test::test_find_pending_cancels_to_close_when_there_is_no_merkle_root_provided_to_l1 sequencer::test::test_get_pending_update_when_there_are_no_requests sequencer::test::test_get_pending_update_when_there_are_requests sequencer::test::test_get_pending_update_when_there_are_too_many_requests_for_single_update" build-ferry-withdrawal-image: - name: Build ferry-withdrawal Docker image - runs-on: ubuntu-latest + name: '[ferry-withdrawal] Build ferry-withdrawal Docker image' + runs-on: ubuntu-24.04 steps: - uses: actions/checkout@v4 - run: | @@ -110,8 +178,8 @@ jobs: docker buildx build --push --platform linux/amd64 -t gaspxyz/ferry-withdrawal:${{ inputs.version }} ./ferry-withdrawal build-ferry-deposit-image: - name: Build ferry-deposit Docker image - runs-on: ubuntu-latest + name: '[ferry-deposit] Build ferry-deposit Docker image' + runs-on: ubuntu-24.04 steps: - uses: actions/checkout@v4 - run: | @@ -120,8 +188,8 @@ jobs: docker buildx build --push --platform linux/amd64 -t gaspxyz/ferry-deposit:${{ inputs.version }} ./ferry-deposit helmfile-lint: - name: Lint and validate Helmfile configuration - runs-on: ubuntu-latest + name: '[ops] Lint and validate Helmfile configuration' + runs-on: ubuntu-24.04 defaults: run: working-directory: ops/helmfiles @@ -138,7 +206,7 @@ jobs: install-kubectl: no additional-helm-plugins: https://github.com/jkroepke/helm-secrets --version v4.1.1,https://github.com/aslafy-z/helm-git --version 1.3.0 - name: Install kubeconform - run: curl -L https://github.com/yannh/kubeconform/releases/download/v0.6.6/kubeconform-linux-amd64.tar.gz | tar xzv && sudo mv kubeconform /usr/local/bin/ + run: curl -L https://github.com/yannh/kubeconform/releases/download/v0.6.7/kubeconform-linux-amd64.tar.gz | tar xzv && sudo mv kubeconform /usr/local/bin/ - run: helmfile lint -e fungible - - run: helmfile template -e fungible | kubeconform -kubernetes-version 1.28.0 -skip ServiceMonitor -strict + - run: helmfile template -e fungible | kubeconform -kubernetes-version 1.30.0 -skip ServiceMonitor -strict diff --git a/.github/workflows/reusable-deploy.yml b/.github/workflows/reusable-deploy.yml index c7afd27f9..4800599c9 100644 --- a/.github/workflows/reusable-deploy.yml +++ b/.github/workflows/reusable-deploy.yml @@ -39,7 +39,7 @@ permissions: jobs: deploy: - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 env: ENV_REF: ${{ inputs.env == 'fungible' && format('pr-{0}', github.event.number) || inputs.env }} steps: @@ -75,11 +75,11 @@ jobs: working-directory: ops/helmfiles run: | # Install kubeconform - curl -L https://github.com/yannh/kubeconform/releases/download/v0.6.6/kubeconform-linux-amd64.tar.gz | tar xzv && sudo mv kubeconform /usr/local/bin/ + curl -L https://github.com/yannh/kubeconform/releases/download/v0.6.7/kubeconform-linux-amd64.tar.gz | tar xzv && sudo mv kubeconform /usr/local/bin/ export ENVIRONMENT=ci && export IMAGE_TAG=${{ inputs.version }} helmfile lint -e fungible - helmfile template -e fungible | kubeconform -kubernetes-version 1.28.0 -skip ServiceMonitor -strict + helmfile template -e fungible | kubeconform -kubernetes-version 1.30.0 -skip ServiceMonitor -strict - name: Deploy services working-directory: ops/helmfiles diff --git a/.github/workflows/reusable-e2e-tests.yml b/.github/workflows/reusable-e2e-tests.yml index 45148b7a6..d894dd5cc 100644 --- a/.github/workflows/reusable-e2e-tests.yml +++ b/.github/workflows/reusable-e2e-tests.yml @@ -44,7 +44,7 @@ env: jobs: run-e2e-tests: - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 steps: - uses: actions/checkout@v4 @@ -118,7 +118,7 @@ jobs: reporter: jest-junit run-avs-tests: - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 strategy: matrix: test: ["test-opt-out", "test-corrupted"] @@ -163,7 +163,7 @@ jobs: ferry-withdrawal-test: name: Ferry withdrawal tests - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 defaults: run: working-directory: ferry-withdrawal @@ -199,7 +199,7 @@ jobs: ferry-deposit-test: name: Ferry deposit tests - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 defaults: run: working-directory: ferry-deposit diff --git a/.github/workflows/reusable-gasp-node-build-and-test.yml b/.github/workflows/reusable-gasp-node-build-and-test.yml new file mode 100644 index 000000000..423f62aa5 --- /dev/null +++ b/.github/workflows/reusable-gasp-node-build-and-test.yml @@ -0,0 +1,739 @@ +name: '[gasp-node] Build and test' + +on: + workflow_call: + inputs: + version: + description: Version to be assigned to the built image + required: true + type: string + branch: + default: ci + description: Branch that given job relates to, that value will be used to tag docker image gaspxyz/rollup-node: + required: true + type: string + builder_image: + default: mangatasolutions/node-builder:multi-1.77-nightly-2024-01-20 + description: Docker image used for Rust builds + required: false + type: string + cache-version: + default: 2 + description: Cache version variable to be used to invalidate cache when needed + required: false + type: number + cache-enabled: + default: true + description: Enable cargo build cache + required: false + type: boolean + +permissions: + contents: read + checks: write + id-token: write + +env: + NODE_DOCKER_IMAGE_REPOSITORY: gaspxyz/rollup-node + +defaults: + run: + working-directory: gasp-node + +jobs: + # Reference implementation source: https://docs.docker.com/build/ci/github-actions/multi-platform/#distribute-build-across-multiple-runners + build-node-image: + name: '[gasp-node] Build Docker image' + strategy: + matrix: + platform: + - name: amd64 + runner: compile-gke + - name: arm64 + runner: compile-gke-arm + runs-on: ${{ matrix.platform.runner }} + env: + JOB_CACHE_PREFIX: gasp-node-buildx-cache-${{ matrix.platform.name }}-${{ inputs.cache-version }} + CACHE_ARCHIVE_NAME: cache_archive.tar.zst + steps: + - uses: actions/checkout@v4 + + - name: Docker metadata + id: meta + uses: docker/metadata-action@v5 + with: + images: ${{ env.NODE_DOCKER_IMAGE_REPOSITORY }} + labels: | + runtime-type=regular + + - uses: docker/setup-buildx-action@v3 + - run: curl -LJO https://github.com/reproducible-containers/buildkit-cache-dance/archive/refs/tags/v3.1.2.tar.gz && tar xvf buildkit-cache-dance-3.1.2.tar.gz + - run: docker login -u ${{ secrets.ORG_DOCKERHUB_USERNAME }} -p ${{ secrets.ORG_DOCKERHUB_TOKEN }} + + - uses: google-github-actions/auth@v2 + id: auth + with: + workload_identity_provider: ${{ secrets.GCP_WORKLOAD_IDENTITY_PROVIDER }} + service_account: ${{ secrets.GCP_SERVICE_ACCOUNT }} + - uses: google-github-actions/setup-gcloud@v2 + + - name: Download cargo build cache + if: inputs.cache-enabled + id: cache + run: | + set -x + CACHE_KEY="${{ env.JOB_CACHE_PREFIX }}-${{ hashFiles('**/Cargo.lock') }}" + ARCHIVE_NAME="${{ env.CACHE_ARCHIVE_NAME }}" + CACHE_FOUND=false + + if gcloud storage cp "gs://mangata-node-ci-cache/$CACHE_KEY/$ARCHIVE_NAME" - | zstd -d | tar -xf - ; then + echo "Injecting buildkit mount cache into Docker system" + node ./buildkit-cache-dance-3.1.2/dist/index.js --cache-map '{"usr-local-cargo-registry": "/usr/local/cargo/registry", "usr-local-cargo-git": "/usr/local/cargo/git", "app-target": "/app/target"}' + + CACHE_FOUND=true + fi + + echo "cache_found=$CACHE_FOUND" >> $GITHUB_OUTPUT + echo "cache_key=$CACHE_KEY" >> $GITHUB_OUTPUT + + - name: Build and export image by digest + id: build + uses: docker/build-push-action@v6 + with: + platforms: linux/${{ matrix.platform.name }} + labels: ${{ steps.meta.outputs.labels }} + context: ./gasp-node + outputs: type=image,name=${{ env.NODE_DOCKER_IMAGE_REPOSITORY }},push-by-digest=true,name-canonical=true,push=true + + - name: Export digest + run: | + mkdir -p /tmp/digests + digest="${{ steps.build.outputs.digest }}" + touch "/tmp/digests/${digest#sha256:}" + + - name: Upload digest + uses: actions/upload-artifact@v4 + with: + name: digests-linux-${{ matrix.platform.name }} + path: /tmp/digests/* + if-no-files-found: error + retention-days: 1 + + - name: Upload cargo build cache + if: inputs.cache-enabled && steps.cache.outputs.cache_found == 'false' + shell: bash + run: | + set -x + + echo "Extracting buildkit cache from Docker system" + node ./buildkit-cache-dance-3.1.2/dist/index.js --extract --cache-map '{"usr-local-cargo-registry": "/usr/local/cargo/registry", "usr-local-cargo-git": "/usr/local/cargo/git", "app-target": "/app/target"}' + + CACHE_KEY="${{ steps.cache.outputs.cache_key }}" + ARCHIVE_NAME="${{ env.CACHE_ARCHIVE_NAME }}" + CACHE_PATHS=( + "usr-local-cargo-registry" + "usr-local-cargo-git" + "app-target" + ) + + SECONDS=0; tar -cf - "${CACHE_PATHS[@]}" | zstd -T0 -5 > "$ARCHIVE_NAME" + echo "Compression completed in $SECONDS seconds" && echo "Archive size: $(du -h "$ARCHIVE_NAME" | cut -f1)" + + SECONDS=0; gcloud storage cp "$ARCHIVE_NAME" "gs://mangata-node-ci-cache/$CACHE_KEY/$ARCHIVE_NAME" + echo "Upload completed in $SECONDS seconds" + + create-docker-image-manifest-and-export-wasms: + name: '[gasp-node] Generate multiplatform Docker image manifest' + needs: [build-node-image] + runs-on: ubuntu-24.04 + steps: + - uses: actions/checkout@v4 + - name: Download digests + uses: actions/download-artifact@v4 + with: + path: /tmp/digests + pattern: digests-* + merge-multiple: true + + - uses: docker/setup-buildx-action@v3 + - run: docker login -u ${{ secrets.ORG_DOCKERHUB_USERNAME }} -p ${{ secrets.ORG_DOCKERHUB_TOKEN }} + + - name: Docker meta + id: meta + uses: docker/metadata-action@v5 + with: + images: ${{ env.NODE_DOCKER_IMAGE_REPOSITORY }} + tags: | + type=raw,value=${{ inputs.version }} + type=raw,value=${{ inputs.branch }} + + - name: Create manifest list and push + working-directory: /tmp/digests + run: | + docker buildx imagetools create $(jq -cr '.tags | map("-t " + .) | join(" ")' <<< "$DOCKER_METADATA_OUTPUT_JSON") \ + $(printf '${{ env.NODE_DOCKER_IMAGE_REPOSITORY }}@sha256:%s ' *) + + - name: Inspect image + run: docker buildx imagetools inspect ${{ env.NODE_DOCKER_IMAGE_REPOSITORY }}:${{ steps.meta.outputs.version }} + + - name: Export WASM artifacts from built images + run: | + # Export WASM artifact from image with regular runtime + container_id=$(docker create ${{ env.NODE_DOCKER_IMAGE_REPOSITORY }}:${{ inputs.version }}) && \ + docker cp $container_id:/app/rollup_runtime.compact.compressed.wasm ./rollup_runtime-${{ inputs.version }}.compact.compressed.wasm && \ + docker rm $container_id + + - uses: actions/upload-artifact@v4 + with: + name: wasm-${{ inputs.version }} + path: ./gasp-node/rollup_runtime-${{ inputs.version }}.compact.compressed.wasm + + build-node-image-with-fast-runtime: + name: '[gasp-node] Build fast runtime Docker image' + strategy: + matrix: + platform: + - name: amd64 + runner: compile-gke + - name: arm64 + runner: compile-gke-arm + runs-on: ${{ matrix.platform.runner }} + env: + JOB_CACHE_PREFIX: gasp-node-fast-cache-${{ matrix.platform.name }}-${{ inputs.cache-version }} + CACHE_ARCHIVE_NAME: cache_archive.tar.zst + steps: + - uses: actions/checkout@v4 + + - name: Docker metadata + id: meta + uses: docker/metadata-action@v5 + with: + images: ${{ env.NODE_DOCKER_IMAGE_REPOSITORY }} + labels: | + runtime-type=fast + + - uses: docker/setup-buildx-action@v3 + - run: curl -LJO https://github.com/reproducible-containers/buildkit-cache-dance/archive/refs/tags/v3.1.2.tar.gz && tar xvf buildkit-cache-dance-3.1.2.tar.gz + - run: docker login -u ${{ secrets.ORG_DOCKERHUB_USERNAME }} -p ${{ secrets.ORG_DOCKERHUB_TOKEN }} + + - uses: google-github-actions/auth@v2 + id: auth + with: + workload_identity_provider: ${{ secrets.GCP_WORKLOAD_IDENTITY_PROVIDER }} + service_account: ${{ secrets.GCP_SERVICE_ACCOUNT }} + - uses: google-github-actions/setup-gcloud@v2 + + - name: Download cargo build cache + if: inputs.cache-enabled + id: cache + run: | + set -x + CACHE_KEY="${{ env.JOB_CACHE_PREFIX }}-${{ hashFiles('**/Cargo.lock') }}" + ARCHIVE_NAME="${{ env.CACHE_ARCHIVE_NAME }}" + CACHE_FOUND=false + + if gcloud storage cp "gs://mangata-node-ci-cache/$CACHE_KEY/$ARCHIVE_NAME" - | zstd -d | tar -xf - ; then + echo "Injecting buildkit mount cache into Docker system" + node ./buildkit-cache-dance-3.1.2/dist/index.js --cache-map '{"usr-local-cargo-registry": "/usr/local/cargo/registry", "usr-local-cargo-git": "/usr/local/cargo/git", "app-target": "/app/target"}' + + CACHE_FOUND=true + fi + + echo "cache_found=$CACHE_FOUND" >> $GITHUB_OUTPUT + echo "cache_key=$CACHE_KEY" >> $GITHUB_OUTPUT + + - name: Build and export fast runtime image by digest + id: build + uses: docker/build-push-action@v6 + with: + build-args: "ENABLE_FAST_RUNTIME=true" + platforms: linux/${{ matrix.platform.name }} + labels: ${{ steps.meta.outputs.labels }} + context: ./gasp-node + outputs: type=image,name=${{ env.NODE_DOCKER_IMAGE_REPOSITORY }},push-by-digest=true,name-canonical=true,push=true + + - name: Export digest + run: | + mkdir -p /tmp/digests + digest="${{ steps.build.outputs.digest }}" + touch "/tmp/digests/${digest#sha256:}" + + - name: Upload digest + uses: actions/upload-artifact@v4 + with: + name: fast-digests-linux-${{ matrix.platform.name }} + path: /tmp/digests/* + if-no-files-found: error + retention-days: 1 + + - name: Upload cargo build cache + if: inputs.cache-enabled && steps.cache.outputs.cache_found == 'false' + shell: bash + run: | + set -x + + echo "Extracting buildkit cache from Docker system" + node ./buildkit-cache-dance-3.1.2/dist/index.js --extract --cache-map '{"usr-local-cargo-registry": "/usr/local/cargo/registry", "usr-local-cargo-git": "/usr/local/cargo/git", "app-target": "/app/target"}' + + CACHE_KEY="${{ steps.cache.outputs.cache_key }}" + ARCHIVE_NAME="${{ env.CACHE_ARCHIVE_NAME }}" + CACHE_PATHS=( + "usr-local-cargo-registry" + "usr-local-cargo-git" + "app-target" + ) + + SECONDS=0; tar -cf - "${CACHE_PATHS[@]}" | zstd -T0 -5 > "$ARCHIVE_NAME" + echo "Compression completed in $SECONDS seconds" && echo "Archive size: $(du -h "$ARCHIVE_NAME" | cut -f1)" + + SECONDS=0; gcloud storage cp "$ARCHIVE_NAME" "gs://mangata-node-ci-cache/$CACHE_KEY/$ARCHIVE_NAME" + echo "Upload completed in $SECONDS seconds" + + create-fast-docker-image-manifest-and-export-wasms: + name: '[gasp-node] Generate multiplatform Docker image manifest for fast runtime image' + needs: [build-node-image-with-fast-runtime] + runs-on: ubuntu-24.04 + steps: + - uses: actions/checkout@v4 + - name: Download digests + uses: actions/download-artifact@v4 + with: + path: /tmp/digests + pattern: fast-digests-* + merge-multiple: true + + - uses: docker/setup-buildx-action@v3 + - run: docker login -u ${{ secrets.ORG_DOCKERHUB_USERNAME }} -p ${{ secrets.ORG_DOCKERHUB_TOKEN }} + + - name: Docker meta + id: meta + uses: docker/metadata-action@v5 + with: + images: ${{ env.NODE_DOCKER_IMAGE_REPOSITORY }} + tags: | + type=raw,value=${{ inputs.version }}-fast + type=raw,value=${{ inputs.branch }}-fast + + - name: Create manifest list and push + working-directory: /tmp/digests + run: | + docker buildx imagetools create $(jq -cr '.tags | map("-t " + .) | join(" ")' <<< "$DOCKER_METADATA_OUTPUT_JSON") \ + $(printf '${{ env.NODE_DOCKER_IMAGE_REPOSITORY }}@sha256:%s ' *) + + - name: Inspect image + run: docker buildx imagetools inspect ${{ env.NODE_DOCKER_IMAGE_REPOSITORY }}:${{ steps.meta.outputs.version }} + + - name: Export WASM artifacts from built images + run: | + # Export WASM artifact from image with fast runtime + container_id_fast=$(docker create ${{ env.NODE_DOCKER_IMAGE_REPOSITORY }}:${{ inputs.version }}-fast) && \ + docker cp $container_id_fast:/app/rollup_runtime.compact.compressed.wasm ./rollup_runtime-${{ inputs.version }}-fast.compact.compressed.wasm && \ + docker rm $container_id_fast + - uses: actions/upload-artifact@v4 + with: + name: wasm-fast-${{ inputs.version }} + path: ./gasp-node/rollup_runtime-${{ inputs.version }}-fast.compact.compressed.wasm + + build-node-image-with-unlocked-runtime: + name: '[gasp-node] Build unlocked runtime Docker image' + strategy: + matrix: + platform: + - name: amd64 + runner: compile-gke + - name: arm64 + runner: compile-gke-arm + runs-on: ${{ matrix.platform.runner }} + env: + JOB_CACHE_PREFIX: gasp-node-unlocked-cache-${{ matrix.platform.name }}-${{ inputs.cache-version }} + CACHE_ARCHIVE_NAME: cache_archive.tar.zst + steps: + - uses: actions/checkout@v4 + + - name: Docker metadata + id: meta + uses: docker/metadata-action@v5 + with: + images: ${{ env.NODE_DOCKER_IMAGE_REPOSITORY }} + labels: | + runtime-type=unlocked + + - uses: docker/setup-buildx-action@v3 + - run: curl -LJO https://github.com/reproducible-containers/buildkit-cache-dance/archive/refs/tags/v3.1.2.tar.gz && tar xvf buildkit-cache-dance-3.1.2.tar.gz + - run: docker login -u ${{ secrets.ORG_DOCKERHUB_USERNAME }} -p ${{ secrets.ORG_DOCKERHUB_TOKEN }} + + - uses: google-github-actions/auth@v2 + id: auth + with: + workload_identity_provider: ${{ secrets.GCP_WORKLOAD_IDENTITY_PROVIDER }} + service_account: ${{ secrets.GCP_SERVICE_ACCOUNT }} + - uses: google-github-actions/setup-gcloud@v2 + + - name: Download cargo build cache + if: inputs.cache-enabled + id: cache + run: | + set -x + CACHE_KEY="${{ env.JOB_CACHE_PREFIX }}-${{ hashFiles('**/Cargo.lock') }}" + ARCHIVE_NAME="${{ env.CACHE_ARCHIVE_NAME }}" + CACHE_FOUND=false + + if gcloud storage cp "gs://mangata-node-ci-cache/$CACHE_KEY/$ARCHIVE_NAME" - | zstd -d | tar -xf - ; then + echo "Injecting buildkit mount cache into Docker system" + node ./buildkit-cache-dance-3.1.2/dist/index.js --cache-map '{"usr-local-cargo-registry": "/usr/local/cargo/registry", "usr-local-cargo-git": "/usr/local/cargo/git", "app-target": "/app/target"}' + + CACHE_FOUND=true + fi + + echo "cache_found=$CACHE_FOUND" >> $GITHUB_OUTPUT + echo "cache_key=$CACHE_KEY" >> $GITHUB_OUTPUT + + - name: Build and export unlocked runtime image by digest + id: build + uses: docker/build-push-action@v6 + with: + build-args: "ENABLE_UNLOCKED_RUNTIME=true" + platforms: linux/${{ matrix.platform.name }} + labels: ${{ steps.meta.outputs.labels }} + context: ./gasp-node + outputs: type=image,name=${{ env.NODE_DOCKER_IMAGE_REPOSITORY }},push-by-digest=true,name-canonical=true,push=true + + - name: Export digest + run: | + mkdir -p /tmp/digests + digest="${{ steps.build.outputs.digest }}" + touch "/tmp/digests/${digest#sha256:}" + + - name: Upload digest + uses: actions/upload-artifact@v4 + with: + name: unlocked-digests-linux-${{ matrix.platform.name }} + path: /tmp/digests/* + if-no-files-found: error + retention-days: 1 + + - name: Upload cargo build cache + if: inputs.cache-enabled && steps.cache.outputs.cache_found == 'false' + shell: bash + run: | + set -x + + echo "Extracting buildkit cache from Docker system" + node ./buildkit-cache-dance-3.1.2/dist/index.js --extract --cache-map '{"usr-local-cargo-registry": "/usr/local/cargo/registry", "usr-local-cargo-git": "/usr/local/cargo/git", "app-target": "/app/target"}' + + CACHE_KEY="${{ steps.cache.outputs.cache_key }}" + ARCHIVE_NAME="${{ env.CACHE_ARCHIVE_NAME }}" + CACHE_PATHS=( + "usr-local-cargo-registry" + "usr-local-cargo-git" + "app-target" + ) + + SECONDS=0; tar -cf - "${CACHE_PATHS[@]}" | zstd -T0 -5 > "$ARCHIVE_NAME" + echo "Compression completed in $SECONDS seconds" && echo "Archive size: $(du -h "$ARCHIVE_NAME" | cut -f1)" + + SECONDS=0; gcloud storage cp "$ARCHIVE_NAME" "gs://mangata-node-ci-cache/$CACHE_KEY/$ARCHIVE_NAME" + echo "Upload completed in $SECONDS seconds" + + create-unlocked-runtime-docker-image-manifest-and-export-wasms: + name: '[gasp-node] Generate multiplatform Docker image manifest for unlocked runtime image' + needs: [build-node-image-with-unlocked-runtime] + runs-on: ubuntu-24.04 + steps: + - uses: actions/checkout@v4 + - name: Download digests + uses: actions/download-artifact@v4 + with: + path: /tmp/digests + pattern: unlocked-digests-* + merge-multiple: true + + - uses: docker/setup-buildx-action@v3 + - run: docker login -u ${{ secrets.ORG_DOCKERHUB_USERNAME }} -p ${{ secrets.ORG_DOCKERHUB_TOKEN }} + + - name: Docker meta + id: meta + uses: docker/metadata-action@v5 + with: + images: ${{ env.NODE_DOCKER_IMAGE_REPOSITORY }} + tags: | + type=raw,value=${{ inputs.version }}-unlocked + type=raw,value=${{ inputs.branch }}-unlocked + + - name: Create manifest list and push + working-directory: /tmp/digests + run: | + docker buildx imagetools create $(jq -cr '.tags | map("-t " + .) | join(" ")' <<< "$DOCKER_METADATA_OUTPUT_JSON") \ + $(printf '${{ env.NODE_DOCKER_IMAGE_REPOSITORY }}@sha256:%s ' *) + + - name: Inspect image + run: docker buildx imagetools inspect ${{ env.NODE_DOCKER_IMAGE_REPOSITORY }}:${{ steps.meta.outputs.version }} + + - name: Export WASM artifacts from built images + run: | + # Export WASM artifact from image with unlocked runtime + container_id_unlocked=$(docker create ${{ env.NODE_DOCKER_IMAGE_REPOSITORY }}:${{ inputs.version }}-unlocked) && \ + docker cp $container_id_unlocked:/app/rollup_runtime.compact.compressed.wasm ./rollup_runtime-${{ inputs.version }}-unlocked.compact.compressed.wasm && \ + docker rm $container_id_unlocked + - uses: actions/upload-artifact@v4 + with: + name: wasm-unlocked-${{ inputs.version }} + path: ./gasp-node/rollup_runtime-${{ inputs.version }}-unlocked.compact.compressed.wasm + + rustfmt-check: + name: '[gasp-node] Formatting check' + runs-on: ubuntu-24.04 + container: + image: ${{ inputs.builder_image }} + steps: + - uses: actions/checkout@v4 + - name: Check formatting + run: cargo fmt --all -- --check + + clippy-check: + name: '[gasp-node] Clippy check' + runs-on: ubuntu-24.04 + container: + image: ${{ inputs.builder_image }} + env: + JOB_CACHE_PREFIX: gasp-node-clippy-job-cache-${{ inputs.cache-version }} + CACHE_ARCHIVE_NAME: cache_archive.tar.zst + steps: + - uses: actions/checkout@v4 + - uses: google-github-actions/auth@v2 + id: auth + with: + workload_identity_provider: ${{ secrets.GCP_WORKLOAD_IDENTITY_PROVIDER }} + service_account: ${{ secrets.GCP_SERVICE_ACCOUNT }} + - uses: google-github-actions/setup-gcloud@v2 + + - name: Download cargo build cache + if: inputs.cache-enabled + id: cache + run: | + set -x + CACHE_KEY="${{ env.JOB_CACHE_PREFIX }}-${{ hashFiles('**/Cargo.lock') }}" + ARCHIVE_NAME="${{ env.CACHE_ARCHIVE_NAME }}" + CACHE_FOUND=false + + if gcloud storage cp "gs://mangata-node-ci-cache/$CACHE_KEY/$ARCHIVE_NAME" - | zstd -d | tar -xf - -C / ; then + CACHE_FOUND=true + fi + + echo "cache_found=$CACHE_FOUND" >> $GITHUB_OUTPUT + echo "cache_key=$CACHE_KEY" >> $GITHUB_OUTPUT + + - name: Install sccache-cache only on non-release runs + if: github.event_name != 'release' && github.event_name != 'workflow_dispatch' + uses: mozilla-actions/sccache-action@v0.0.5 + - name: Set Rust caching env vars only on non-release run + if: github.event_name != 'release' && github.event_name != 'workflow_dispatch' + run: | + echo "SCCACHE_GCS_BUCKET=mangata-node-ci-cache" >> $GITHUB_ENV + echo "SCCACHE_GCS_RW_MODE=READ_WRITE" >> $GITHUB_ENV + echo "SCCACHE_GCS_KEY_PREFIX=${{ env.JOB_CACHE_PREFIX }}" >> $GITHUB_ENV + echo "RUSTC_WRAPPER=sccache" >> $GITHUB_ENV + echo "CARGO_INCREMENTAL=0" >> $GITHUB_ENV + - name: Run clippy + run: cargo clippy -p pallet-xyk + + - name: Upload cargo build cache + if: inputs.cache-enabled && steps.cache.outputs.cache_found == 'false' + shell: bash + run: | + set -x + CACHE_KEY="${{ steps.cache.outputs.cache_key }}" + ARCHIVE_NAME="${{ env.CACHE_ARCHIVE_NAME }}" + CACHE_PATHS=( + "./target" + "/usr/local/cargo/bin/" + "/usr/local/cargo/registry/index/" + "/usr/local/cargo/registry/cache/" + "/usr/local/cargo/git/db/" + ) + + SECONDS=0; tar -cf - "${CACHE_PATHS[@]}" | zstd -T0 -5 > "$ARCHIVE_NAME" + echo "Compression completed in $SECONDS seconds" && echo "Archive size: $(du -h "$ARCHIVE_NAME" | cut -f1)" + + SECONDS=0; gcloud storage cp "$ARCHIVE_NAME" "gs://mangata-node-ci-cache/$CACHE_KEY/$ARCHIVE_NAME" + echo "Upload completed in $SECONDS seconds" + + run-benchmarks-tests: + name: '[gasp-node] Run benchmark tests' + runs-on: ubuntu-24.04 + container: + image: ${{ inputs.builder_image }} + env: + JOB_CACHE_PREFIX: gasp-node-becnhmark-tests-job-cache-${{ inputs.cache-version }} + CACHE_ARCHIVE_NAME: cache_archive.tar.zst + steps: + - uses: actions/checkout@v4 + - uses: google-github-actions/auth@v2 + id: auth + with: + workload_identity_provider: ${{ secrets.GCP_WORKLOAD_IDENTITY_PROVIDER }} + service_account: ${{ secrets.GCP_SERVICE_ACCOUNT }} + - uses: google-github-actions/setup-gcloud@v2 + + - name: Download cargo build cache + if: inputs.cache-enabled + id: cache + run: | + set -x + CACHE_KEY="${{ env.JOB_CACHE_PREFIX }}-${{ hashFiles('**/Cargo.lock') }}" + ARCHIVE_NAME="${{ env.CACHE_ARCHIVE_NAME }}" + CACHE_FOUND=false + + if gcloud storage cp "gs://mangata-node-ci-cache/$CACHE_KEY/$ARCHIVE_NAME" - | zstd -d | tar -xf - -C / ; then + CACHE_FOUND=true + fi + + echo "cache_found=$CACHE_FOUND" >> $GITHUB_OUTPUT + echo "cache_key=$CACHE_KEY" >> $GITHUB_OUTPUT + + - name: Install sccache-cache only on non-release runs + if: github.event_name != 'release' && github.event_name != 'workflow_dispatch' + uses: mozilla-actions/sccache-action@v0.0.5 + - name: Set Rust caching env vars only on non-release run + if: github.event_name != 'release' && github.event_name != 'workflow_dispatch' + run: | + echo "SCCACHE_GCS_BUCKET=mangata-node-ci-cache" >> $GITHUB_ENV + echo "SCCACHE_GCS_RW_MODE=READ_WRITE" >> $GITHUB_ENV + echo "SCCACHE_GCS_KEY_PREFIX=${{ env.JOB_CACHE_PREFIX }}" >> $GITHUB_ENV + echo "RUSTC_WRAPPER=sccache" >> $GITHUB_ENV + echo "CARGO_INCREMENTAL=0" >> $GITHUB_ENV + - name: Run benchmarks tests + run: cargo test --release -j8 --features=runtime-benchmarks -p pallet-xyk -p pallet-issuance -p pallet-multipurpose-liquidity -p pallet-fee-lock + - name: Run benchmarks tests + run: cargo test --release -j8 --features=runtime-benchmarks -p pallet-bootstrap -p pallet-market + # NOTE: MGX-742 + - name: Run benchmarks tests + run: cargo test --release -j8 --features=runtime-benchmarks -p pallet-proof-of-stake + + - name: Upload cargo build cache + if: inputs.cache-enabled && steps.cache.outputs.cache_found == 'false' + shell: bash + run: | + set -x + CACHE_KEY="${{ steps.cache.outputs.cache_key }}" + ARCHIVE_NAME="${{ env.CACHE_ARCHIVE_NAME }}" + CACHE_PATHS=( + "./target" + "/usr/local/cargo/bin/" + "/usr/local/cargo/registry/index/" + "/usr/local/cargo/registry/cache/" + "/usr/local/cargo/git/db/" + ) + + SECONDS=0; tar -cf - "${CACHE_PATHS[@]}" | zstd -T0 -5 > "$ARCHIVE_NAME" + echo "Compression completed in $SECONDS seconds" && echo "Archive size: $(du -h "$ARCHIVE_NAME" | cut -f1)" + + SECONDS=0; gcloud storage cp "$ARCHIVE_NAME" "gs://mangata-node-ci-cache/$CACHE_KEY/$ARCHIVE_NAME" + echo "Upload completed in $SECONDS seconds" + + run-benchmarks: + name: '[gasp-node] Run runtime benchmarks' + # `performance` self-hosted runners have 8 cores and 16GB of RAM + runs-on: [performance-gke] + container: + image: ${{ inputs.builder_image }} + env: + STEPS: 2 + REPEATS: 1 + JOB_CACHE_PREFIX: gasp-node-run-benchmarks-job-cache-${{ inputs.cache-version }} + CACHE_ARCHIVE_NAME: cache_archive.tar.zst + steps: + - uses: actions/checkout@v4 + - uses: google-github-actions/auth@v2 + id: auth + with: + workload_identity_provider: ${{ secrets.GCP_WORKLOAD_IDENTITY_PROVIDER }} + service_account: ${{ secrets.GCP_SERVICE_ACCOUNT }} + - uses: google-github-actions/setup-gcloud@v2 + + - name: Download cargo build cache + if: inputs.cache-enabled + id: cache + run: | + set -x + CACHE_KEY="${{ env.JOB_CACHE_PREFIX }}-${{ hashFiles('**/Cargo.lock') }}" + ARCHIVE_NAME="${{ env.CACHE_ARCHIVE_NAME }}" + CACHE_FOUND=false + + if gcloud storage cp "gs://mangata-node-ci-cache/$CACHE_KEY/$ARCHIVE_NAME" - | zstd -d | tar -xf - -C / ; then + CACHE_FOUND=true + fi + + echo "cache_found=$CACHE_FOUND" >> $GITHUB_OUTPUT + echo "cache_key=$CACHE_KEY" >> $GITHUB_OUTPUT + + - name: Install sccache-cache only on non-release runs + if: github.event_name != 'release' && github.event_name != 'workflow_dispatch' + uses: mozilla-actions/sccache-action@v0.0.5 + - name: Set Rust caching env vars only on non-release run + if: github.event_name != 'release' && github.event_name != 'workflow_dispatch' + run: | + echo "SCCACHE_GCS_BUCKET=mangata-node-ci-cache" >> $GITHUB_ENV + echo "SCCACHE_GCS_RW_MODE=READ_WRITE" >> $GITHUB_ENV + echo "SCCACHE_GCS_KEY_PREFIX=${{ env.JOB_CACHE_PREFIX }}" >> $GITHUB_ENV + echo "RUSTC_WRAPPER=sccache" >> $GITHUB_ENV + echo "CARGO_INCREMENTAL=0" >> $GITHUB_ENV + + - name: Compile code + run: cargo build --release --no-default-features --features=runtime-benchmarks + + - name: Set full benchmark params + if: ${{ contains(github.event.pull_request.labels.*.name, 'full-benchmarks') }} + run: | + echo "STEPS=50" >> $GITHUB_ENV + echo "REPEATS=20" >> $GITHUB_ENV + + - name: Run pallet benchmarks + run: | + mkdir ./benchmarks && target/release/rollup-node benchmark pallet \ + -l=info,runtime::collective=warn,xyk=warn \ + --chain rollup-local \ + --wasm-execution compiled \ + --pallet '*' \ + --extrinsic '*' \ + --steps ${{ env.STEPS }} \ + --repeat ${{ env.REPEATS }} \ + --template ./templates/module-weight-template.hbs \ + --output ./benchmarks/ + + - name: Run block & extrinsic overhead benchmarks + run: | + target/release/rollup-node benchmark overhead --chain rollup-local -lblock_builder=debug --max-ext-per-block 50000 --base-path . + cp block_weights.rs extrinsic_weights.rs ./benchmarks + + - name: Upload logs and docker images to GitHub + if: ${{ contains(github.event.pull_request.labels.*.name, 'full-benchmarks') }} + uses: actions/upload-artifact@v4 + with: + name: benchmarks + path: ./gasp-node/benchmarks + + - name: Upload cargo build cache + if: inputs.cache-enabled && steps.cache.outputs.cache_found == 'false' + shell: bash + run: | + set -x + CACHE_KEY="${{ steps.cache.outputs.cache_key }}" + ARCHIVE_NAME="${{ env.CACHE_ARCHIVE_NAME }}" + CACHE_PATHS=( + "./target" + "/usr/local/cargo/bin/" + "/usr/local/cargo/registry/index/" + "/usr/local/cargo/registry/cache/" + "/usr/local/cargo/git/db/" + ) + + SECONDS=0; tar -cf - "${CACHE_PATHS[@]}" | zstd -T0 -5 > "$ARCHIVE_NAME" + echo "Compression completed in $SECONDS seconds" && echo "Archive size: $(du -h "$ARCHIVE_NAME" | cut -f1)" + + SECONDS=0; gcloud storage cp "$ARCHIVE_NAME" "gs://mangata-node-ci-cache/$CACHE_KEY/$ARCHIVE_NAME" + echo "Upload completed in $SECONDS seconds" + + - name: Fix permissions on self-hosted runner + if: always() + run: chown -R 1100:1100 $GITHUB_WORKSPACE \ No newline at end of file diff --git a/.github/workflows/reusable-gasp-node-e2e-tests.yml b/.github/workflows/reusable-gasp-node-e2e-tests.yml new file mode 100644 index 000000000..dc87d97ce --- /dev/null +++ b/.github/workflows/reusable-gasp-node-e2e-tests.yml @@ -0,0 +1,304 @@ +name: '[gasp-node] Reusable e2e tests workflow' + +on: + workflow_dispatch: + inputs: + e2eBranch: + description: "Name of the e2e target branch" + type: string + required: false + default: "eth-rollup-develop" + nodeDockerImage: + description: "gasp-node Docker image reference" + type: string + required: false + # default: "gaspxyz/rollup-node:main" + default: '' + skipBuild: + description: "Skip build phase" + type: string + required: false + default: 'false' + globalVersion: + description: "Set gasp-node version." + type: string + required: true + mangataTypesVersion: + description: "Set @mangata-finance/types version" + type: string + default: "" + required: false + workflow_call: + inputs: + e2eBranch: + description: "Name of the e2e target branch" + type: string + required: false + default: "eth-rollup-develop" + nodeDockerImage: + description: "gasp-node Docker image reference" + type: string + required: false + # default: "gaspxyz/rollup-node:main" + default: '' + skipBuild: + description: "Skip build phase" + type: string + required: false + default: 'false' + globalVersion: + description: "Set gasp-node version." + type: string + required: true + +permissions: + contents: read + actions: read + checks: write + +jobs: + setup-report: + runs-on: [ubuntu-24.04] + outputs: + testmo-run-id: ${{ steps.setTestRun.outputs.testmo-run-id }} + steps: + - name: Install testmo + run: npm install --no-save @testmo/testmo-cli + - name: Add url + run: | + npx testmo automation:resources:add-field --name git --type string \ + --value ${GITHUB_SHA:0:7} --resources resources.json + RUN_URL="$GITHUB_SERVER_URL/$GITHUB_REPOSITORY/actions/runs/$GITHUB_RUN_ID" + npx testmo automation:resources:add-link --name build \ + --url $RUN_URL --resources resources.json + + - name: Create test run + run: | + npx testmo automation:run:create \ + --instance https://mangata-finance.testmo.net \ + --project-id 2 \ + --name "BE tests from node-repo" \ + --resources resources.json \ + --source "BE-e2e-regression" > testmo-run-id.txt + ID=$(cat testmo-run-id.txt) + echo "testmo-run-id=$ID" >> $GITHUB_OUTPUT + echo "ID=$ID" >> $GITHUB_ENV + + env: + TESTMO_URL: ${{ secrets.TESTMO_URL }} + TESTMO_TOKEN: ${{ secrets.TESTMO_TOKEN }} + id: setTestRun + + e2e-test-matrix: + needs: [setup-report] + strategy: + fail-fast: false + matrix: + include: + - command: "yarn test-parallel --max-workers=10" + fast: false + unlocked: true + - command: "yarn test-sequential-no-bootstrap" + fast: false + unlocked: true + - command: "yarn test-seqgasless" + fast: false + unlocked: true + - command: "yarn test-maintenance" + fast: true + unlocked: false + - command: "yarn test-rollupUpdate" + fast: true + unlocked: false + - command: "yarn test-bootstrap" + fast: false + unlocked: true + - command: "yarn test-rewards-bootstrap" + fast: false + unlocked: true + - command: "yarn test-parallel-autocompound" + fast: true + unlocked: false + - command: "yarn test-sequential-autocompound" + fast: true + unlocked: false + - command: "yarn test-poolliquidity" + fast: true + unlocked: false + - command: "yarn test-governance" + fast: true + unlocked: false + - command: "yarn test-multiswap" + fast: false + unlocked: true + - command: "yarn test-experimentalStaking" + fast: true + unlocked: false + - command: "yarn test-crowdloan" + fast: false + unlocked: true + - command: "yarn test-sdk" + fast: true + unlocked: false + - command: "yarn test-parallel-3rdPartyRewards" + fast: true + unlocked: false + - command: "yarn test-sequencerStaking" + fast: true + unlocked: false + - command: "yarn test-sequencerCancellation" + fast: true + unlocked: false + - command: "yarn test-rolldown" + fast: true + unlocked: false + - command: "yarn test-rolldownWithdrawal" + fast: true + unlocked: false + - command: "yarn test-rolldownPreOperationWithdrawal" + fast: true + unlocked: false + - command: "yarn test-sequencerRewards" + fast: true + unlocked: false + + runs-on: [e2e-gke] + timeout-minutes: 180 + defaults: + run: + working-directory: gasp-node + env: + API_URL: "ws://127.0.0.1:9946" + E2EBRANCHNAME: "eth-rollup-develop" + NODE_DOCKER_IMAGE: ${{ inputs.nodeDockerImage != '' && inputs.nodeDockerImage || format('gaspxyz/rollup-node:{0}', inputs.globalVersion) }} + NODE_DOCKER_COMPOSE_NETWORK: gasp-node_default + steps: + - uses: actions/checkout@v4 + - name: Adapt if fast runtime + if: ${{ !contains(env.NODE_DOCKER_IMAGE, 'fast') && matrix.fast == true }} + run: echo "NODE_DOCKER_IMAGE=${{ env.NODE_DOCKER_IMAGE }}-fast" >> $GITHUB_ENV + - name: Adapt if unlocked + if: ${{ !contains(env.NODE_DOCKER_IMAGE, 'fast') && matrix.unlocked == true }} + run: echo "NODE_DOCKER_IMAGE=${{ env.NODE_DOCKER_IMAGE }}-unlocked" >> $GITHUB_ENV + + + - name: Download gasp-node Docker image + if: ${{ !contains(github.event.pull_request.labels.*.name, 'skip-build') && inputs.skipBuild != 'true' }} + run: docker pull ${{ env.NODE_DOCKER_IMAGE }} + + - name: E2E- Get branch name + id: branch-name + uses: tj-actions/branch-names@v8 + + - name: Current branch name for generating types + run: | + echo "GASP_TYPES_VERSION=${{ steps.branch-name.outputs.current_branch }}" | sed -E 's@[/\.]@-@g; s@_@-@g' >> $GITHUB_ENV + + - name: E2E- Calculate if run e2e feature branch or main. + run: | + echo DEFAULT: E2E test will run with: $E2EBRANCHNAME + echo "Running on: ${{ steps.branch-name.outputs.current_branch }}" + if [ -n "$(git ls-remote --heads https://github.com/gasp-xyz/gasp-e2e.git ${{ steps.branch-name.outputs.current_branch }} --force --quiet)" ]; then echo "E2EBRANCHNAME=${{ steps.branch-name.outputs.current_branch }}" >> $GITHUB_ENV; echo "MATCH - OK" ; elif [ -n "$(git ls-remote --heads https://github.com/gasp-xyz/gasp-e2e.git ${{ github.base_ref }} --force --quiet)" ]; then echo "E2EBRANCHNAME=${{ github.base_ref }}" >> $GITHUB_ENV; echo "MATCH - OK" ; fi + + - name: Decide if main - branch or parameter + # if we have something in e2eBranch - override E2EBranchName, else -> E2EBRANCHNAME , that + # by default will be main. + run: echo "E2EBRANCHNAME=${{ inputs.e2eBranch || env.E2EBRANCHNAME }}" >> $GITHUB_ENV + + - name: Checkout tests + uses: actions/checkout@v4 + with: + repository: gasp-xyz/gasp-e2e + ref: "${{ env.E2EBRANCHNAME }}" + path: e2eTests + + - uses: actions/setup-node@v4 + with: + node-version: '18.19.0' + + - name: Install e2e tests dependencies + working-directory: e2eTests + run: yarn install + + - name: Install @mangata-finance/types deps + run: | + if [[ -n "${{ github.event.inputs.mangataTypesVersion }}" ]]; then + yarn add @mangata-finance/types@${{ github.event.inputs.mangataTypesVersion }} + else + yarn add @mangata-finance/types@${{ env.GASP_TYPES_VERSION }} + fi + + - name: Run the Node + working-directory: gasp-node + env: + NODE_IMAGE: ${{ env.NODE_DOCKER_IMAGE }} + run: | + echo "Starting nodes with image: ${NODE_IMAGE}" + docker-compose up -d + docker ps + + - name: Sleep for 2 minutes + run: sleep 120s + + - name: Get docker status + run: docker ps + + - name: Run tests + working-directory: e2eTests + run: ${{ matrix.command }} + env: + NODE_OPTIONS: --max_old_space_size=12288 + + - name: Test Report + uses: dorny/test-reporter@v1.9.1 + continue-on-error: true + if: success() || failure() # run this step even if previous step failed + with: + name: E2E report ${{ matrix.command }} # Name of the check run which will be created + path: e2eTests/reports/*.xml # Path to test results + reporter: jest-junit # Format of test results + + - name: Submit results to the testmo-run + continue-on-error: true + if: always() + env: + TESTMO_URL: ${{ secrets.TESTMO_URL }} + TESTMO_TOKEN: ${{ secrets.TESTMO_TOKEN }} + run: | + npm install --no-save @testmo/testmo-cli + npx testmo automation:run:submit-thread \ + --instance https://mangata-finance.testmo.net \ + --run-id ${{needs.setup-report.outputs.testmo-run-id}} \ + --results e2eTests/reports/*.xml + + test-complete: + needs: [setup-report, e2e-test-matrix] + if: always() + runs-on: ubuntu-24.04 + steps: + - name: Install testmo + run: npm install --no-save @testmo/testmo-cli + + - name: Complete test run + run: | + npx testmo automation:run:complete \ + --instance https://mangata-finance.testmo.net \ + --run-id ${{needs.setup-report.outputs.testmo-run-id}} \ + env: + TESTMO_URL: ${{ secrets.TESTMO_URL }} + TESTMO_TOKEN: ${{ secrets.TESTMO_TOKEN }} + continue-on-error: true + + slack-notify-nook: + needs: [e2e-test-matrix] + if: failure() + runs-on: ubuntu-24.04 + steps: + - name: Slack Notification - Error + uses: bryannice/gitactions-slack-notification@2.0.0 + env: + SLACK_INCOMING_WEBHOOK: ${{ secrets.BNB_E2E_NOTIFICATION_WEBHOOK }} + SLACK_TITLE: 'bnb e2e test execution - NOOK' + SLACK_COLOR: "#ff0011" + SLACK_MESSAGE: 'Test failures [ ${{ env.E2EBRANCHNAME }} - ${{ env.NODE_DOCKER_IMAGE }} ] testmo report: https://mangata-finance.testmo.net/automation/runs/view/${{needs.setup-report.outputs.testmo-run-id}}' + GITHUB_REF: 'https://gasp-xyz.github.io/gasp-monorepo/${{ github.run_number }}' diff --git a/.github/workflows/reusable-gasp-node-generate-types.yml b/.github/workflows/reusable-gasp-node-generate-types.yml new file mode 100644 index 000000000..0d7376e21 --- /dev/null +++ b/.github/workflows/reusable-gasp-node-generate-types.yml @@ -0,0 +1,69 @@ +name: '[gasp-node] Reusable generate types workflow' + +on: + workflow_dispatch: + inputs: + branch: + description: "Branch to create in gasp-dev-kit" + type: string + required: true + nodeDockerImage: + description: "Name of the gasp-node docker reference" + type: string + required: false + # default: "gaspxyz/rollup-node:main" + default: '' + globalVersion: + description: "Set gasp-node version." + type: string + required: true + workflow_call: + inputs: + branch: + description: "Branch to create in gasp-dev-kit" + type: string + required: true + nodeDockerImage: + description: "Name of the gasp-node docker reference" + type: string + required: false + # default: "gaspxyz/rollup-node:main" + default: '' + globalVersion: + description: "Set gasp-node version." + type: string + required: true + +permissions: + contents: write + id-token: write + deployments: write + checks: write + +jobs: + generate-gasp-node-types: + runs-on: ubuntu-24.04 + env: + NODE_DOCKER_IMAGE: ${{ inputs.nodeDockerImage != '' && inputs.nodeDockerImage || format('gaspxyz/rollup-node:{0}', inputs.globalVersion) }} + BRANCH: ${{ inputs.branch }} + steps: + - uses: actions/checkout@v4 + + - name: Create branch ${{ env.BRANCH }} in gasp-dev-kit + uses: GuillaumeFalourd/create-other-repo-branch-action@v1.5 + with: + repository_owner: gasp-xyz + repository_name: gasp-dev-kit + new_branch_name: ${{ env.BRANCH }} + new_branch_ref: eth-rollup-develop + ignore_branch_exists: true + access_token: ${{ secrets.BOT_GITHUB_TOKEN }} + + - name: Invoke workflow in gasp-dev-kit repo with inputs + uses: the-actions-org/workflow-dispatch@v4 + with: + ref: ${{ env.BRANCH }} + workflow: pr-automation-types-rollup-solochain.yml + repo: gasp-xyz/gasp-dev-kit + token: ${{ secrets.BOT_GITHUB_TOKEN }} + inputs: '{"parachainDocker": "${{ env.NODE_DOCKER_IMAGE }}", "branch": "${{ env.BRANCH }}", "nodeRepository": "gasp-xyz/gasp-monorepo", "nodeCodebaseWorkingDirectory": "mangata-repo/gasp-node"}' \ No newline at end of file diff --git a/.github/workflows/reusable-gasp-node-perfomance-tests.yml b/.github/workflows/reusable-gasp-node-perfomance-tests.yml new file mode 100644 index 000000000..88f8289dc --- /dev/null +++ b/.github/workflows/reusable-gasp-node-perfomance-tests.yml @@ -0,0 +1,96 @@ +name: "[gasp-node] Performance tests" +on: + workflow_call: + inputs: + e2eBranch: + description: "Name of the e2e target branch" + type: string + required: false + default: "main" + targetEnv: + description: "env name" + type: string + required: false + +permissions: + contents: write + id-token: write + deployments: write + checks: write + +jobs: + performance-tests: + # Allows to keep e2e tests jobs running even if performance-tests fail + continue-on-error: true + runs-on: [compile-gke] + timeout-minutes: 180 + env: + ENV_REF: ${{ inputs.targetEnv || format('pr-{0}', github.event.number) }} + E2EBRANCHNAME: "main" + steps: + ####IDK, but this is neccesary for reports + - uses: actions/checkout@v4 + + - name: E2E- Get branch name + id: branch-name + uses: tj-actions/branch-names@v8 + + - name: Set Configuration - Develop + if: "${{ inputs.targetEnv == 'dev' }}" + run: | + echo 'TEST_SUDO_NAME=${{ secrets.DEV_SUDO_NAME }}' >> $GITHUB_ENV + + - name: E2E- Calculate if run e2e feature branch or main. + run: | + echo DEFAULT: E2E test will run with: $E2EBRANCHNAME + echo "Running on: ${{ steps.branch-name.outputs.current_branch }}" + if [ -n "$(git ls-remote --heads https://github.com/gasp-xyz/gasp-e2e.git ${{ steps.branch-name.outputs.current_branch }} --force --quiet)" ]; then echo "E2EBRANCHNAME=${{ steps.branch-name.outputs.current_branch }}" >> $GITHUB_ENV; echo "MATCH - OK" ; elif [ -n "$(git ls-remote --heads https://github.com/mangata-finance/mangata-e2e.git ${{ github.base_ref }} --force --quiet)" ]; then echo "E2EBRANCHNAME=${{ github.base_ref }}" >> $GITHUB_ENV; echo "MATCH - OK" ; fi + + - name: Decide if main - branch or parameter + # if we have something in e2eBranch - override E2EBranchName, else -> E2EBRANCHNAME , that + # by default will be main. + run: echo "E2EBRANCHNAME=${{ inputs.e2eBranch || env.E2EBRANCHNAME }}" >> $GITHUB_ENV + + - name: Checkout performance-tests + uses: actions/checkout@v4 + with: + repository: gasp-xyz/gasp-e2e + ref: "${{ env.E2EBRANCHNAME }}" + path: e2e + + - uses: actions/setup-node@v3 + with: + node-version: "18" + cache: "yarn" + cache-dependency-path: "**/yarn.lock" + + - name: Install e2e tests dependencies + working-directory: e2e + run: yarn + + - name: Run performance tests + working-directory: e2e/performance + run: | + node --experimental-specifier-resolution=node --loader ts-node/esm --experimental-vm-modules index.ts \ + transfer \ + nodes="wss://node-01-ws-${{ env.ENV_REF }}.gasp.xyz" \ + testCaseName=ConcurrentTest \ + pending=2000 \ + threadNumber=12 \ + duration=50 \ + throughput=200 + + - name: Upload reports + if: always() + uses: actions/upload-artifact@v4 + with: + name: performance-benchmarks-report + path: | + ./e2e/performance/report.html + ./e2e/performance/enqueued.txt + ./e2e/performance/executed.txt + ./e2e/performance/pending.txt + + - name: Fix permissions on self-hosted runner + if: always() + run: chown -R 1100:1100 $GITHUB_WORKSPACE \ No newline at end of file diff --git a/.github/workflows/reusable-gasp-node-unit-tests-and-coverage.yml b/.github/workflows/reusable-gasp-node-unit-tests-and-coverage.yml new file mode 100644 index 000000000..6d4e505ea --- /dev/null +++ b/.github/workflows/reusable-gasp-node-unit-tests-and-coverage.yml @@ -0,0 +1,196 @@ +name: '[gasp-node] Unit tests and coverage' + +on: + workflow_call: + inputs: + version: + description: Version to be assigned to the built image + required: true + type: string + branch: + default: ci + description: Branch that given job relates to, that value will be used to tag docker image gaspxyz/rollup-node: + required: true + type: string + builder_image: + default: mangatasolutions/node-builder:multi-1.77-nightly-2024-01-20 + description: Docker image used for Rust builds + required: false + type: string + cache-version: + default: 2 + description: Cache version variable to be used to invalidate cache when needed + required: false + type: number + cache-enabled: + default: true + description: Enable cargo build cache + required: false + type: boolean + +permissions: + contents: read + checks: write + id-token: write + +env: + NODE_DOCKER_IMAGE_REPOSITORY: gaspxyz/rollup-node + +defaults: + run: + working-directory: gasp-node + +jobs: + unit-test: + name: '[gasp-node] Unit tests' + runs-on: [compile-gke] + container: + image: ${{ inputs.builder_image }} + env: + JOB_CACHE_PREFIX: gasp-node-unit-tests-cache-${{ inputs.cache-version }} + CACHE_ARCHIVE_NAME: cache_archive.tar.zst + steps: + - uses: actions/checkout@v4 + - uses: google-github-actions/auth@v2 + id: auth + with: + workload_identity_provider: ${{ secrets.GCP_WORKLOAD_IDENTITY_PROVIDER }} + service_account: ${{ secrets.GCP_SERVICE_ACCOUNT }} + - uses: google-github-actions/setup-gcloud@v2 + + - name: Download cargo build cache + if: inputs.cache-enabled + id: cache + run: | + set -x + CACHE_KEY="${{ env.JOB_CACHE_PREFIX }}-${{ hashFiles('**/Cargo.lock') }}" + ARCHIVE_NAME="${{ env.CACHE_ARCHIVE_NAME }}" + CACHE_FOUND=false + + if gcloud storage cp "gs://mangata-node-ci-cache/$CACHE_KEY/$ARCHIVE_NAME" - | zstd -d | tar -xf - -C / ; then + CACHE_FOUND=true + fi + + echo "cache_found=$CACHE_FOUND" >> $GITHUB_OUTPUT + echo "cache_key=$CACHE_KEY" >> $GITHUB_OUTPUT + + - name: Install sccache-cache only on non-release runs + if: github.event_name != 'release' && github.event_name != 'workflow_dispatch' + uses: mozilla-actions/sccache-action@v0.0.5 + - name: Set Rust caching env vars only on non-release run + if: github.event_name != 'release' && github.event_name != 'workflow_dispatch' + run: | + echo "SCCACHE_GCS_BUCKET=mangata-node-ci-cache" >> $GITHUB_ENV + echo "SCCACHE_GCS_RW_MODE=READ_WRITE" >> $GITHUB_ENV + echo "SCCACHE_GCS_KEY_PREFIX=${{ env.JOB_CACHE_PREFIX }}" >> $GITHUB_ENV + echo "RUSTC_WRAPPER=sccache" >> $GITHUB_ENV + echo "CARGO_INCREMENTAL=0" >> $GITHUB_ENV + + - name: Run unit tests + run: cargo test -j2 + + - name: Upload cargo build cache + if: inputs.cache-enabled && steps.cache.outputs.cache_found == 'false' + shell: bash + run: | + set -x + CACHE_KEY="${{ steps.cache.outputs.cache_key }}" + ARCHIVE_NAME="${{ env.CACHE_ARCHIVE_NAME }}" + CACHE_PATHS=( + "./target" + "/usr/local/cargo/bin/" + "/usr/local/cargo/registry/index/" + "/usr/local/cargo/registry/cache/" + "/usr/local/cargo/git/db/" + ) + + SECONDS=0; tar -cf - "${CACHE_PATHS[@]}" | zstd -T0 -5 > "$ARCHIVE_NAME" + echo "Compression completed in $SECONDS seconds" && echo "Archive size: $(du -h "$ARCHIVE_NAME" | cut -f1)" + + SECONDS=0; gcloud storage cp "$ARCHIVE_NAME" "gs://mangata-node-ci-cache/$CACHE_KEY/$ARCHIVE_NAME" + echo "Upload completed in $SECONDS seconds" + + - name: Fix permissions on self-hosted runner + if: always() + run: chown -R 1100:1100 $GITHUB_WORKSPACE + + coverage-report: + name: '[gasp-node] Coverage report' + runs-on: [compile-gke] + container: + image: ${{ inputs.builder_image }} + options: --security-opt seccomp=unconfined + env: + JOB_CACHE_PREFIX: gasp-node-coverage-job-cache-${{ inputs.cache-version }} + CACHE_ARCHIVE_NAME: cache_archive.tar.zst + steps: + - uses: actions/checkout@v4 + - uses: google-github-actions/auth@v2 + id: auth + with: + workload_identity_provider: ${{ secrets.GCP_WORKLOAD_IDENTITY_PROVIDER }} + service_account: ${{ secrets.GCP_SERVICE_ACCOUNT }} + - uses: google-github-actions/setup-gcloud@v2 + + - name: Download cargo build cache + if: inputs.cache-enabled + id: cache + run: | + set -x + CACHE_KEY="${{ env.JOB_CACHE_PREFIX }}-${{ hashFiles('**/Cargo.lock') }}" + ARCHIVE_NAME="${{ env.CACHE_ARCHIVE_NAME }}" + CACHE_FOUND=false + + if gcloud storage cp "gs://mangata-node-ci-cache/$CACHE_KEY/$ARCHIVE_NAME" - | zstd -d | tar -xf - -C / ; then + CACHE_FOUND=true + fi + + echo "cache_found=$CACHE_FOUND" >> $GITHUB_OUTPUT + echo "cache_key=$CACHE_KEY" >> $GITHUB_OUTPUT + + - name: Install sccache-cache only on non-release runs + if: github.event_name != 'release' && github.event_name != 'workflow_dispatch' + uses: mozilla-actions/sccache-action@v0.0.5 + - name: Set Rust caching env vars only on non-release run + if: github.event_name != 'release' && github.event_name != 'workflow_dispatch' + run: | + echo "SCCACHE_GCS_BUCKET=mangata-node-ci-cache" >> $GITHUB_ENV + echo "SCCACHE_GCS_RW_MODE=READ_WRITE" >> $GITHUB_ENV + echo "SCCACHE_GCS_KEY_PREFIX=${{ env.JOB_CACHE_PREFIX }}" >> $GITHUB_ENV + echo "RUSTC_WRAPPER=sccache" >> $GITHUB_ENV + echo "CARGO_INCREMENTAL=0" >> $GITHUB_ENV + + - name: Install cargo-tarpaulin + run: cargo install cargo-tarpaulin@0.26.1 --locked --force + - name: Generate coverage report with cargo-tarpaulin + run: cargo tarpaulin --timeout 120 --workspace -e rollup-runtime-integration-test rollup-node rollup-runtime --exclude-files **/mock.rs **/weights.rs **/weights/* --out Xml + - name: Upload to codecov.io + uses: codecov/codecov-action@v5 + with: + token: ${{ secrets.ORG_CODECOV_TOKEN }} + fail_ci_if_error: false + + - name: Upload cargo build cache + if: inputs.cache-enabled && steps.cache.outputs.cache_found == 'false' + shell: bash + run: | + set -x + CACHE_KEY="${{ steps.cache.outputs.cache_key }}" + ARCHIVE_NAME="${{ env.CACHE_ARCHIVE_NAME }}" + CACHE_PATHS=( + "./target" + "/usr/local/cargo/bin/" + "/usr/local/cargo/registry/index/" + "/usr/local/cargo/registry/cache/" + "/usr/local/cargo/git/db/" + ) + + SECONDS=0; tar -cf - "${CACHE_PATHS[@]}" | zstd -T0 -5 > "$ARCHIVE_NAME" + echo "Compression completed in $SECONDS seconds" && echo "Archive size: $(du -h "$ARCHIVE_NAME" | cut -f1)" + + SECONDS=0; gcloud storage cp "$ARCHIVE_NAME" "gs://mangata-node-ci-cache/$CACHE_KEY/$ARCHIVE_NAME" + echo "Upload completed in $SECONDS seconds" + + - name: Fix permissions on self-hosted runner + if: always() + run: chown -R 1100:1100 $GITHUB_WORKSPACE diff --git a/.github/workflows/reusable-rust-build.yml b/.github/workflows/reusable-rust-build.yml index 381fa1904..4a5ec5273 100644 --- a/.github/workflows/reusable-rust-build.yml +++ b/.github/workflows/reusable-rust-build.yml @@ -32,6 +32,16 @@ on: required: false type: boolean default: false + gasp_node_fast_runtime: + description: Use `rollup-node` with fast runtime when building `gasp-node` image + required: false + type: boolean + default: false + gasp_node_unlocked_runtime: + description: Use `rollup-node` with unlocked runtime when building `gasp-node` image + required: false + type: boolean + default: false cargo_tests_enabled: description: Run cargo test required: false @@ -49,11 +59,12 @@ on: env: SKIP_WASM_BUILD: 1 + IMAGE_PREFIX: ${{ inputs.gasp_node_fast_runtime && '-fast' || inputs.gasp_avs_fast_runtime && '-fast' || inputs.gasp_node_unlocked_runtime && '-unlocked' || '' }} jobs: rustfmt-check: name: "[${{ inputs.service_folder }}] Formatting check" - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 steps: - uses: actions/checkout@v4 - uses: actions-rust-lang/setup-rust-toolchain@v1.10.1 @@ -67,7 +78,7 @@ jobs: clippy-check: name: "[${{ inputs.service_folder }}] Clippy check" if: inputs.cargo_clippy_check_enabled - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 steps: - uses: actions/checkout@v4 - uses: actions-rust-lang/setup-rust-toolchain@v1.10.1 @@ -82,7 +93,7 @@ jobs: cargo-test: name: "[${{ inputs.service_folder }}] Run cargo tests" if: inputs.cargo_tests_enabled - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 steps: - uses: actions/checkout@v4 - uses: actions-rust-lang/setup-rust-toolchain@v1.10.1 @@ -100,7 +111,7 @@ jobs: fi docker-image-build: - name: "[${{ inputs.service_folder }}] [${{ matrix.platform.name }}] image${{ inputs.gasp_avs_fast_runtime && ' with fast runtime' || '' }}" + name: "[${{ inputs.service_folder }}] [${{ matrix.platform.name }}] image${{ inputs.gasp_node_fast_runtime && ' with fast runtime' || inputs.gasp_avs_fast_runtime && ' with fast runtime' || inputs.gasp_node_unlocked_runtime && ' with unlocked runtime' || '' }}" strategy: matrix: platform: @@ -110,11 +121,11 @@ jobs: runner: compile-gke-arm runs-on: ${{ matrix.platform.runner }} env: - JOB_CACHE_PREFIX: ${{ inputs.service_folder }}-${{ matrix.platform.name }}${{ inputs.gasp_avs_fast_runtime && 'fast' || 'regular' }}-${{ inputs.cache-version }} + JOB_CACHE_PREFIX: ${{ inputs.service_folder }}-${{ matrix.platform.name }}${{ inputs.gasp_node_fast_runtime && '-fast' || inputs.gasp_avs_fast_runtime && '-fast' || inputs.gasp_node_unlocked_runtime && '-unlocked' || '' }}-${{ inputs.cache-version }} SERVICE_FOLDER: ${{ inputs.service_folder }} DOCKER_IMAGE_REPOSITORY: ${{ inputs.docker_image_repository }} CACHE_ARCHIVE_NAME: cache_archive.tar.zst - IMAGE_TAG: ${{ inputs.version }}${{ inputs.gasp_avs_fast_runtime && '-fast' || '' }} + IMAGE_TAG: ${{ inputs.version }}${{ inputs.gasp_node_fast_runtime && '-fast' || inputs.gasp_avs_fast_runtime && '-fast' || inputs.gasp_node_unlocked_runtime && '-unlocked' || '' }} steps: - uses: actions/checkout@v4 @@ -165,7 +176,10 @@ jobs: platforms: linux/${{ matrix.platform.name }} labels: ${{ steps.meta.outputs.labels }} outputs: type=image,name=${{ env.DOCKER_IMAGE_REPOSITORY }},push-by-digest=true,name-canonical=true,push=true - build-args: ROLLUP_NODE_VERSION=eth-rollup-develop${{ inputs.gasp_avs_fast_runtime && '-fast' || '' }} + build-args: | + ROLLUP_NODE_VERSION=eth-rollup-develop${{ inputs.gasp_avs_fast_runtime && '-fast' || '' }} + ${{ inputs.gasp_node_fast_runtime && 'ENABLE_FAST_RUNTIME=true' || '' }} + ${{ inputs.gasp_node_unlocked_runtime && 'ENABLE_UNLOCKED_RUNTIME=true' || '' }} - name: Export digest run: | @@ -176,7 +190,7 @@ jobs: - name: Upload digest uses: actions/upload-artifact@v4 with: - name: ${{ inputs.service_folder }}-${{ inputs.gasp_avs_fast_runtime && 'fast' || 'regular' }}-linux-${{ matrix.platform.name }} + name: ${{ inputs.service_folder }}-${{ inputs.gasp_node_fast_runtime && '-fast' || inputs.gasp_avs_fast_runtime && '-fast' || inputs.gasp_node_unlocked_runtime && '-unlocked' || '' }}-linux-${{ matrix.platform.name }} path: /tmp/digests/* if-no-files-found: error retention-days: 1 @@ -207,16 +221,16 @@ jobs: multiplatform-docker-image-compiation-and-push: name: "[${{ inputs.service_folder }}] Generate multiplatform Docker image manifest" needs: [docker-image-build] - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 env: DOCKER_IMAGE_REPOSITORY: ${{ inputs.docker_image_repository }} - IMAGE_TAG: ${{ inputs.version }}${{ inputs.gasp_avs_fast_runtime && '-fast' || '' }} + IMAGE_TAG: ${{ inputs.version }}${{ inputs.gasp_node_fast_runtime && '-fast' || inputs.gasp_avs_fast_runtime && '-fast' || inputs.gasp_node_unlocked_runtime && '-unlocked' || '' }} steps: - name: Download digests uses: actions/download-artifact@v4 with: path: /tmp/digests - pattern: ${{ inputs.service_folder }}-${{ inputs.gasp_avs_fast_runtime && 'fast' || 'regular' }}-* + pattern: ${{ inputs.service_folder }}-${{ inputs.gasp_node_fast_runtime && '-fast' || inputs.gasp_avs_fast_runtime && '-fast' || inputs.gasp_node_unlocked_runtime && '-unlocked' || '' }}-* merge-multiple: true - uses: docker/setup-buildx-action@v3 diff --git a/contracts/.github/workflows/ci.yml b/contracts/.github/workflows/ci.yml deleted file mode 100644 index d328838c2..000000000 --- a/contracts/.github/workflows/ci.yml +++ /dev/null @@ -1,73 +0,0 @@ -name: CI - -on: - push: - -env: - FOUNDRY_PROFILE: ci - -jobs: - check: - name: Foundry project - runs-on: ubuntu-latest - steps: - - name: Checkout repository - uses: actions/checkout@v4 - with: - submodules: recursive - - - name: Install Python - uses: actions/setup-python@v5 - with: - python-version: "3.12.x" - check-latest: true - cache: pip - - - name: Install Foundry - uses: foundry-rs/foundry-toolchain@v1 - with: - version: "1.9.5" - cache: true - - - name: Install Bun - uses: oven-sh/setup-bun@v2 - with: - bun-version: latest - - - name: Install Python dependencies - run: | - pip install -U setuptools - pip install -r requirements.txt - - - name: Compile contracts - run: bun run compile - id: compile - - - name: Get sizes of contracts - run: bun run size - id: size - - - name: Check format of contracts and files - run: bun run format - id: format - - - name: Lint contracts - run: bun run lint - id: lint - - - name: Run unit tests - run: bun run test - id: test - - - name: Make static analysis of contracts - if: false - run: bun run analyze:ci - id: analyze - - - name: Create gas report - run: bun run gas - id: gas - - - name: Create coverage report - run: bun run cover - id: cover diff --git a/contracts/.python-version b/contracts/.python-version new file mode 100644 index 000000000..5020c063e --- /dev/null +++ b/contracts/.python-version @@ -0,0 +1 @@ +3.12.8 \ No newline at end of file diff --git a/gasp-node/.dockerignore b/gasp-node/.dockerignore new file mode 100644 index 000000000..1c4321029 --- /dev/null +++ b/gasp-node/.dockerignore @@ -0,0 +1,37 @@ +.git +.github +target +devops +launch +scripts +workflows +Makefile +Tiltfile +**/*.txt +**/*.md +./docker-cargo +Dockerfile +compose.yml +.gitignore +.dockerignore + +tmp +benchmarks +chains +**/chains/ +./target/* +./docker-cargo/* +node_modules + +!target/release/rollup-node +!docker-cargo/release/rollup-node + +# dotfiles in the repo root +/.* + +# buildkit-cache-dance ignores +buildkit-cache-dance-* +scratch +usr-local-cargo-registry +usr-local-cargo-git +app-target \ No newline at end of file diff --git a/gasp-node/.editorconfig b/gasp-node/.editorconfig new file mode 100644 index 000000000..e8ff2027c --- /dev/null +++ b/gasp-node/.editorconfig @@ -0,0 +1,29 @@ +root = true +[*] +indent_style=tab +indent_size=tab +tab_width=4 +end_of_line=lf +charset=utf-8 +trim_trailing_whitespace=true +max_line_length=100 +insert_final_newline=true + +[*.yml] +indent_style=space +indent_size=2 +tab_width=8 +end_of_line=lf + +[*.sh] +indent_style=space +indent_size=4 +tab_width=8 +end_of_line=lf + +[*.json] +indent_style=space +indent_size=2 +tab_width=8 +end_of_line=lf + diff --git a/gasp-node/.gitattributes b/gasp-node/.gitattributes new file mode 100644 index 000000000..383338e04 --- /dev/null +++ b/gasp-node/.gitattributes @@ -0,0 +1 @@ +Cargo.lock -diff diff --git a/gasp-node/.gitignore b/gasp-node/.gitignore new file mode 100644 index 000000000..2ea319458 --- /dev/null +++ b/gasp-node/.gitignore @@ -0,0 +1,42 @@ +# Cargo compiled files and executables +**/target/ + +# These are backup files generated by rustfmt +**/*.rs.bk + +# Local chain databases (defalut location) +**/chains/ + +# The cache for chain data in container +.local + +# The cache for docker container dependency +/.cargo +docker-cargo + +.DS_Store +.idea +.vscode +node_modules +output/ +devops/parachain/config/state +tarpaulin-report.html +cobertura.xml +build_rs_cov.profraw + +# nektos/act related files +/event.json +.actrc + +# Ignore generated credentials from google-github-actions/auth +gha-creds-*.json + +tmp +benchmarks + +# buildkit-cache-dance ignores +buildkit-cache-dance-* +scratch +usr-local-cargo-registry +usr-local-cargo-git +app-target \ No newline at end of file diff --git a/gasp-node/.rustfmt.toml b/gasp-node/.rustfmt.toml new file mode 100644 index 000000000..3018614bb --- /dev/null +++ b/gasp-node/.rustfmt.toml @@ -0,0 +1,23 @@ +# Basic +hard_tabs = true +max_width = 100 +use_small_heuristics = "Max" + +# Imports +imports_granularity = "Crate" +reorder_imports = true + +# Consistency +newline_style = "Unix" + +# Misc +binop_separator = "Back" +chain_width = 80 +match_arm_blocks = false +match_arm_leading_pipes = "Preserve" +match_block_trailing_comma = true +reorder_impl_items = false +spaces_around_ranges = false +trailing_comma = "Vertical" +trailing_semicolon = false +use_field_init_shorthand = true diff --git a/gasp-node/Cargo.lock b/gasp-node/Cargo.lock new file mode 100644 index 000000000..22a93d9b0 --- /dev/null +++ b/gasp-node/Cargo.lock @@ -0,0 +1,12459 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "Inflector" +version = "0.11.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fe438c63458706e03479442743baae6c88256498e6431708f6dfc520a26515d3" +dependencies = [ + "lazy_static", + "regex", +] + +[[package]] +name = "addr2line" +version = "0.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a76fd60b23679b7d19bd066031410fb7e458ccc5e958eb5c325888ce4baedc97" +dependencies = [ + "gimli 0.27.3", +] + +[[package]] +name = "addr2line" +version = "0.24.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f5fb1d8e4442bd405fdfd1dacb42792696b0cf9cb15882e5d097b742a676d375" +dependencies = [ + "gimli 0.31.0", +] + +[[package]] +name = "adler2" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "512761e0bb2578dd7380c6baaa0f4ce03e84f95e960231d1dec8bf4d7d6e2627" + +[[package]] +name = "aead" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d122413f284cf2d62fb1b7db97e02edb8cda96d769b16e443a4f6195e35662b0" +dependencies = [ + "crypto-common", + "generic-array 0.14.7", +] + +[[package]] +name = "aes" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b169f7a6d4742236a0a00c541b845991d0ac43e546831af1249753ab4c3aa3a0" +dependencies = [ + "cfg-if", + "cipher 0.4.4", + "cpufeatures", +] + +[[package]] +name = "aes-gcm" +version = "0.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "831010a0f742e1209b3bcea8fab6a8e149051ba6099432c8cb2cc117dec3ead1" +dependencies = [ + "aead", + "aes", + "cipher 0.4.4", + "ctr", + "ghash", + "subtle 2.6.1", +] + +[[package]] +name = "ahash" +version = "0.7.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "891477e0c6a8957309ee5c45a6368af3ae14bb510732d2684ffa19af310920f9" +dependencies = [ + "getrandom 0.2.15", + "once_cell", + "version_check", +] + +[[package]] +name = "ahash" +version = "0.8.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e89da841a80418a9b391ebaea17f5c112ffaaa96f621d2c285b5174da76b9011" +dependencies = [ + "cfg-if", + "getrandom 0.2.15", + "once_cell", + "version_check", + "zerocopy", +] + +[[package]] +name = "aho-corasick" +version = "1.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" +dependencies = [ + "memchr", +] + +[[package]] +name = "allocator-api2" +version = "0.2.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c6cb57a04249c6480766f7f7cef5467412af1490f8d1e243141daddada3264f" + +[[package]] +name = "alloy-primitives" +version = "0.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c234f92024707f224510ff82419b2be0e1d8e1fd911defcac5a085cd7f83898" +dependencies = [ + "alloy-rlp", + "bytes", + "cfg-if", + "const-hex", + "derive_more", + "hex-literal 0.4.1", + "itoa", + "keccak-asm", + "proptest", + "rand 0.8.5", + "ruint", + "serde", + "tiny-keccak", +] + +[[package]] +name = "alloy-rlp" +version = "0.3.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26154390b1d205a4a7ac7352aa2eb4f81f391399d4e2f546fb81a2f8bb383f62" +dependencies = [ + "arrayvec 0.7.6", + "bytes", +] + +[[package]] +name = "alloy-sol-macro" +version = "0.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "970e5cf1ca089e964d4f7f7afc7c9ad642bfb1bdc695a20b0cba3b3c28954774" +dependencies = [ + "const-hex", + "dunce", + "heck 0.4.1", + "indexmap 2.5.0", + "proc-macro-error", + "proc-macro2", + "quote", + "syn 2.0.77", + "syn-solidity", + "tiny-keccak", +] + +[[package]] +name = "alloy-sol-types" +version = "0.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a059d4d2c78f8f21e470772c75f9abd9ac6d48c2aaf6b278d1ead06ed9ac664" +dependencies = [ + "alloy-primitives", + "alloy-sol-macro", + "const-hex", + "serde", +] + +[[package]] +name = "android-tzdata" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e999941b234f3131b00bc13c22d06e8c5ff726d1b6318ac7eb276997bbb4fef0" + +[[package]] +name = "android_system_properties" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311" +dependencies = [ + "libc", +] + +[[package]] +name = "ansi_term" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d52a9bb7ec0cf484c551830a7ce27bd20d67eac647e1befb56b0be4ee39a55d2" +dependencies = [ + "winapi", +] + +[[package]] +name = "anstream" +version = "0.6.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "64e15c1ab1f89faffbf04a634d5e1962e9074f2741eef6d97f3c4e322426d526" +dependencies = [ + "anstyle", + "anstyle-parse", + "anstyle-query", + "anstyle-wincon", + "colorchoice", + "is_terminal_polyfill", + "utf8parse", +] + +[[package]] +name = "anstyle" +version = "1.0.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bec1de6f59aedf83baf9ff929c98f2ad654b97c9510f4e70cf6f661d49fd5b1" + +[[package]] +name = "anstyle-parse" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eb47de1e80c2b463c735db5b217a0ddc39d612e7ac9e2e96a5aed1f57616c1cb" +dependencies = [ + "utf8parse", +] + +[[package]] +name = "anstyle-query" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d36fc52c7f6c869915e99412912f22093507da8d9e942ceaf66fe4b7c14422a" +dependencies = [ + "windows-sys 0.52.0", +] + +[[package]] +name = "anstyle-wincon" +version = "3.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5bf74e1b6e971609db8ca7a9ce79fd5768ab6ae46441c572e46cf596f59e57f8" +dependencies = [ + "anstyle", + "windows-sys 0.52.0", +] + +[[package]] +name = "anyhow" +version = "1.0.89" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "86fdf8605db99b54d3cd748a44c6d04df638eb5dafb219b135d0149bd0db01f6" + +[[package]] +name = "approx" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cab112f0a86d568ea0e627cc1d6be74a1e9cd55214684db5561995f6dad897c6" +dependencies = [ + "num-traits", +] + +[[package]] +name = "aquamarine" +version = "0.1.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a941c39708478e8eea39243b5983f1c42d2717b3620ee91f4a52115fd02ac43f" +dependencies = [ + "itertools 0.9.0", + "proc-macro-error", + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "aquamarine" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d1da02abba9f9063d786eab1509833ebb2fac0f966862ca59439c76b9c566760" +dependencies = [ + "include_dir", + "itertools 0.10.5", + "proc-macro-error", + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "aquamarine" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "21cc1548309245035eb18aa7f0967da6bc65587005170c56e6ef2788a4cf3f4e" +dependencies = [ + "include_dir", + "itertools 0.10.5", + "proc-macro-error", + "proc-macro2", + "quote", + "syn 2.0.77", +] + +[[package]] +name = "ark-bls12-377" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fb00293ba84f51ce3bd026bd0de55899c4e68f0a39a5728cebae3a73ffdc0a4f" +dependencies = [ + "ark-ec 0.4.2", + "ark-ff 0.4.2", + "ark-std 0.4.0", +] + +[[package]] +name = "ark-bls12-377-ext" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "20c7021f180a0cbea0380eba97c2af3c57074cdaffe0eef7e840e1c9f2841e55" +dependencies = [ + "ark-bls12-377", + "ark-ec 0.4.2", + "ark-models-ext", + "ark-std 0.4.0", +] + +[[package]] +name = "ark-bls12-381" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c775f0d12169cba7aae4caeb547bb6a50781c7449a8aa53793827c9ec4abf488" +dependencies = [ + "ark-ec 0.4.2", + "ark-ff 0.4.2", + "ark-serialize 0.4.2", + "ark-std 0.4.0", +] + +[[package]] +name = "ark-bls12-381-ext" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1dc4b3d08f19e8ec06e949712f95b8361e43f1391d94f65e4234df03480631c" +dependencies = [ + "ark-bls12-381", + "ark-ec 0.4.2", + "ark-ff 0.4.2", + "ark-models-ext", + "ark-serialize 0.4.2", + "ark-std 0.4.0", +] + +[[package]] +name = "ark-bw6-761" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2e0605daf0cc5aa2034b78d008aaf159f56901d92a52ee4f6ecdfdac4f426700" +dependencies = [ + "ark-bls12-377", + "ark-ec 0.4.2", + "ark-ff 0.4.2", + "ark-std 0.4.0", +] + +[[package]] +name = "ark-bw6-761-ext" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ccee5fba47266f460067588ee1bf070a9c760bf2050c1c509982c5719aadb4f2" +dependencies = [ + "ark-bw6-761", + "ark-ec 0.4.2", + "ark-ff 0.4.2", + "ark-models-ext", + "ark-std 0.4.0", +] + +[[package]] +name = "ark-ec" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "defd9a439d56ac24968cca0571f598a61bc8c55f71d50a89cda591cb750670ba" +dependencies = [ + "ark-ff 0.4.2", + "ark-poly 0.4.2", + "ark-serialize 0.4.2", + "ark-std 0.4.0", + "derivative", + "hashbrown 0.13.2", + "itertools 0.10.5", + "num-traits", + "rayon", + "zeroize", +] + +[[package]] +name = "ark-ec" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43d68f2d516162846c1238e755a7c4d131b892b70cc70c471a8e3ca3ed818fce" +dependencies = [ + "ahash 0.8.11", + "ark-ff 0.5.0", + "ark-poly 0.5.0", + "ark-serialize 0.5.0", + "ark-std 0.5.0", + "educe", + "fnv", + "hashbrown 0.15.2", + "itertools 0.13.0", + "num-bigint", + "num-integer", + "num-traits", + "zeroize", +] + +[[package]] +name = "ark-ed-on-bls12-377" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b10d901b9ac4b38f9c32beacedfadcdd64e46f8d7f8e88c1ae1060022cf6f6c6" +dependencies = [ + "ark-bls12-377", + "ark-ec 0.4.2", + "ark-ff 0.4.2", + "ark-std 0.4.0", +] + +[[package]] +name = "ark-ed-on-bls12-377-ext" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "524a4fb7540df2e1a8c2e67a83ba1d1e6c3947f4f9342cc2359fc2e789ad731d" +dependencies = [ + "ark-ec 0.4.2", + "ark-ed-on-bls12-377", + "ark-ff 0.4.2", + "ark-models-ext", + "ark-std 0.4.0", +] + +[[package]] +name = "ark-ed-on-bls12-381-bandersnatch" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f9cde0f2aa063a2a5c28d39b47761aa102bda7c13c84fc118a61b87c7b2f785c" +dependencies = [ + "ark-bls12-381", + "ark-ec 0.4.2", + "ark-ff 0.4.2", + "ark-std 0.4.0", +] + +[[package]] +name = "ark-ed-on-bls12-381-bandersnatch-ext" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d15185f1acb49a07ff8cbe5f11a1adc5a93b19e211e325d826ae98e98e124346" +dependencies = [ + "ark-ec 0.4.2", + "ark-ed-on-bls12-381-bandersnatch", + "ark-ff 0.4.2", + "ark-models-ext", + "ark-std 0.4.0", +] + +[[package]] +name = "ark-ff" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6b3235cc41ee7a12aaaf2c575a2ad7b46713a8a50bda2fc3b003a04845c05dd6" +dependencies = [ + "ark-ff-asm 0.3.0", + "ark-ff-macros 0.3.0", + "ark-serialize 0.3.0", + "ark-std 0.3.0", + "derivative", + "num-bigint", + "num-traits", + "paste", + "rustc_version 0.3.3", + "zeroize", +] + +[[package]] +name = "ark-ff" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec847af850f44ad29048935519032c33da8aa03340876d351dfab5660d2966ba" +dependencies = [ + "ark-ff-asm 0.4.2", + "ark-ff-macros 0.4.2", + "ark-serialize 0.4.2", + "ark-std 0.4.0", + "derivative", + "digest 0.10.7", + "itertools 0.10.5", + "num-bigint", + "num-traits", + "paste", + "rustc_version 0.4.1", + "zeroize", +] + +[[package]] +name = "ark-ff" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a177aba0ed1e0fbb62aa9f6d0502e9b46dad8c2eab04c14258a1212d2557ea70" +dependencies = [ + "ark-ff-asm 0.5.0", + "ark-ff-macros 0.5.0", + "ark-serialize 0.5.0", + "ark-std 0.5.0", + "arrayvec 0.7.6", + "digest 0.10.7", + "educe", + "itertools 0.13.0", + "num-bigint", + "num-traits", + "paste", + "zeroize", +] + +[[package]] +name = "ark-ff-asm" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db02d390bf6643fb404d3d22d31aee1c4bc4459600aef9113833d17e786c6e44" +dependencies = [ + "quote", + "syn 1.0.109", +] + +[[package]] +name = "ark-ff-asm" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3ed4aa4fe255d0bc6d79373f7e31d2ea147bcf486cba1be5ba7ea85abdb92348" +dependencies = [ + "quote", + "syn 1.0.109", +] + +[[package]] +name = "ark-ff-asm" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62945a2f7e6de02a31fe400aa489f0e0f5b2502e69f95f853adb82a96c7a6b60" +dependencies = [ + "quote", + "syn 2.0.77", +] + +[[package]] +name = "ark-ff-macros" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db2fd794a08ccb318058009eefdf15bcaaaaf6f8161eb3345f907222bac38b20" +dependencies = [ + "num-bigint", + "num-traits", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "ark-ff-macros" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7abe79b0e4288889c4574159ab790824d0033b9fdcb2a112a3182fac2e514565" +dependencies = [ + "num-bigint", + "num-traits", + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "ark-ff-macros" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09be120733ee33f7693ceaa202ca41accd5653b779563608f1234f78ae07c4b3" +dependencies = [ + "num-bigint", + "num-traits", + "proc-macro2", + "quote", + "syn 2.0.77", +] + +[[package]] +name = "ark-models-ext" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3e9eab5d4b5ff2f228b763d38442adc9b084b0a465409b059fac5c2308835ec2" +dependencies = [ + "ark-ec 0.4.2", + "ark-ff 0.4.2", + "ark-serialize 0.4.2", + "ark-std 0.4.0", + "derivative", +] + +[[package]] +name = "ark-poly" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d320bfc44ee185d899ccbadfa8bc31aab923ce1558716e1997a1e74057fe86bf" +dependencies = [ + "ark-ff 0.4.2", + "ark-serialize 0.4.2", + "ark-std 0.4.0", + "derivative", + "hashbrown 0.13.2", +] + +[[package]] +name = "ark-poly" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "579305839da207f02b89cd1679e50e67b4331e2f9294a57693e5051b7703fe27" +dependencies = [ + "ahash 0.8.11", + "ark-ff 0.5.0", + "ark-serialize 0.5.0", + "ark-std 0.5.0", + "educe", + "fnv", + "hashbrown 0.15.2", +] + +[[package]] +name = "ark-scale" +version = "0.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f69c00b3b529be29528a6f2fd5fa7b1790f8bed81b9cdca17e326538545a179" +dependencies = [ + "ark-ec 0.4.2", + "ark-ff 0.4.2", + "ark-serialize 0.4.2", + "ark-std 0.4.0", + "parity-scale-codec", + "scale-info", +] + +[[package]] +name = "ark-secret-scalar" +version = "0.0.2" +source = "git+https://github.com/w3f/ring-vrf?rev=e9782f9#e9782f938629c90f3adb3fff2358bc8d1386af3e" +dependencies = [ + "ark-ec 0.4.2", + "ark-ff 0.4.2", + "ark-serialize 0.4.2", + "ark-std 0.4.0", + "ark-transcript 0.0.2", + "digest 0.10.7", + "getrandom_or_panic", + "zeroize", +] + +[[package]] +name = "ark-serialize" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d6c2b318ee6e10f8c2853e73a83adc0ccb88995aa978d8a3408d492ab2ee671" +dependencies = [ + "ark-std 0.3.0", + "digest 0.9.0", +] + +[[package]] +name = "ark-serialize" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "adb7b85a02b83d2f22f89bd5cac66c9c89474240cb6207cb1efc16d098e822a5" +dependencies = [ + "ark-serialize-derive 0.4.2", + "ark-std 0.4.0", + "digest 0.10.7", + "num-bigint", +] + +[[package]] +name = "ark-serialize" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f4d068aaf107ebcd7dfb52bc748f8030e0fc930ac8e360146ca54c1203088f7" +dependencies = [ + "ark-serialize-derive 0.5.0", + "ark-std 0.5.0", + "arrayvec 0.7.6", + "digest 0.10.7", + "num-bigint", +] + +[[package]] +name = "ark-serialize-derive" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae3281bc6d0fd7e549af32b52511e1302185bd688fd3359fa36423346ff682ea" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "ark-serialize-derive" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "213888f660fddcca0d257e88e54ac05bca01885f258ccdf695bafd77031bb69d" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.77", +] + +[[package]] +name = "ark-std" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1df2c09229cbc5a028b1d70e00fdb2acee28b1055dfb5ca73eea49c5a25c4e7c" +dependencies = [ + "num-traits", + "rand 0.8.5", +] + +[[package]] +name = "ark-std" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94893f1e0c6eeab764ade8dc4c0db24caf4fe7cbbaafc0eba0a9030f447b5185" +dependencies = [ + "num-traits", + "rand 0.8.5", + "rayon", +] + +[[package]] +name = "ark-std" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "246a225cc6131e9ee4f24619af0f19d67761fff15d7ccc22e42b80846e69449a" +dependencies = [ + "num-traits", + "rand 0.8.5", +] + +[[package]] +name = "ark-transcript" +version = "0.0.2" +source = "git+https://github.com/w3f/ring-vrf?rev=e9782f9#e9782f938629c90f3adb3fff2358bc8d1386af3e" +dependencies = [ + "ark-ff 0.4.2", + "ark-serialize 0.4.2", + "ark-std 0.4.0", + "digest 0.10.7", + "rand_core 0.6.4", + "sha3", +] + +[[package]] +name = "ark-transcript" +version = "0.0.3" +source = "git+https://github.com/w3f/ark-transcript#37a169f587f45d67e5afad143bc2a7c9c864884b" +dependencies = [ + "ark-ff 0.5.0", + "ark-serialize 0.5.0", + "ark-std 0.5.0", + "digest 0.10.7", + "rand_core 0.6.4", + "sha3", +] + +[[package]] +name = "array-bytes" +version = "4.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f52f63c5c1316a16a4b35eaac8b76a98248961a533f061684cb2a7cb0eafb6c6" + +[[package]] +name = "array-bytes" +version = "6.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d5dde061bd34119e902bbb2d9b90c5692635cf59fb91d582c2b68043f1b8293" + +[[package]] +name = "arrayref" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76a2e8124351fda1ef8aaaa3bbd7ebbcb486bbcd4225aca0aa0d84bb2db8fecb" + +[[package]] +name = "arrayvec" +version = "0.4.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd9fd44efafa8690358b7408d253adf110036b88f55672a933f01d616ad9b1b9" +dependencies = [ + "nodrop", +] + +[[package]] +name = "arrayvec" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23b62fc65de8e4e7f52534fb52b0f3ed04746ae267519eef2a83941e8085068b" + +[[package]] +name = "arrayvec" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50" + +[[package]] +name = "asn1-rs" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f6fd5ddaf0351dff5b8da21b2fb4ff8e08ddd02857f0bf69c47639106c0fff0" +dependencies = [ + "asn1-rs-derive", + "asn1-rs-impl", + "displaydoc", + "nom", + "num-traits", + "rusticata-macros", + "thiserror", + "time", +] + +[[package]] +name = "asn1-rs-derive" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "726535892e8eae7e70657b4c8ea93d26b8553afb1ce617caee529ef96d7dee6c" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", + "synstructure", +] + +[[package]] +name = "asn1-rs-impl" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2777730b2039ac0f95f093556e61b6d26cebed5393ca6f152717777cec3a42ed" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "assert_matches" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b34d609dfbaf33d6889b2b7106d3ca345eacad44200913df5ba02bfd31d2ba9" + +[[package]] +name = "async-channel" +version = "1.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "81953c529336010edd6d8e358f886d9581267795c61b19475b71314bffa46d35" +dependencies = [ + "concurrent-queue", + "event-listener 2.5.3", + "futures-core", +] + +[[package]] +name = "async-io" +version = "2.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "444b0228950ee6501b3568d3c93bf1176a1fdbc3b758dcd9475046d30f4dc7e8" +dependencies = [ + "async-lock", + "cfg-if", + "concurrent-queue", + "futures-io", + "futures-lite", + "parking", + "polling", + "rustix 0.38.37", + "slab", + "tracing", + "windows-sys 0.59.0", +] + +[[package]] +name = "async-lock" +version = "3.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff6e472cdea888a4bd64f342f09b3f50e1886d32afe8df3d663c01140b811b18" +dependencies = [ + "event-listener 5.3.1", + "event-listener-strategy", + "pin-project-lite 0.2.14", +] + +[[package]] +name = "async-trait" +version = "0.1.82" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a27b8a3a6e1a44fa4c8baf1f653e4172e81486d4941f2237e20dc2d0cf4ddff1" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.77", +] + +[[package]] +name = "asynchronous-codec" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4057f2c32adbb2fc158e22fb38433c8e9bbf76b75a4732c7c0cbaf695fb65568" +dependencies = [ + "bytes", + "futures-sink", + "futures-util", + "memchr", + "pin-project-lite 0.2.14", +] + +[[package]] +name = "atty" +version = "0.2.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" +dependencies = [ + "hermit-abi 0.1.19", + "libc", + "winapi", +] + +[[package]] +name = "auto_impl" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c87f3f15e7794432337fc718554eaa4dc8f04c9677a950ffe366f20a162ae42" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.77", +] + +[[package]] +name = "autocfg" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c4b4d0bd25bd0b74681c0ad21497610ce1b7c91b1022cd21c80c6fbdd9476b0" + +[[package]] +name = "backtrace" +version = "0.3.74" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8d82cb332cdfaed17ae235a638438ac4d4839913cc2af585c3c6746e8f8bee1a" +dependencies = [ + "addr2line 0.24.1", + "cfg-if", + "libc", + "miniz_oxide", + "object 0.36.4", + "rustc-demangle", + "windows-targets 0.52.6", +] + +[[package]] +name = "bandersnatch_vrfs" +version = "0.0.4" +source = "git+https://github.com/w3f/ring-vrf?rev=e9782f9#e9782f938629c90f3adb3fff2358bc8d1386af3e" +dependencies = [ + "ark-bls12-381", + "ark-ec 0.4.2", + "ark-ed-on-bls12-381-bandersnatch", + "ark-ff 0.4.2", + "ark-serialize 0.4.2", + "ark-std 0.4.0", + "dleq_vrf", + "fflonk", + "merlin 3.0.0", + "rand_chacha 0.3.1", + "rand_core 0.6.4", + "ring 0.1.0", + "sha2 0.10.8", + "sp-ark-bls12-381", + "sp-ark-ed-on-bls12-381-bandersnatch", + "zeroize", +] + +[[package]] +name = "base-x" +version = "0.2.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4cbbc9d0964165b47557570cce6c952866c2678457aca742aafc9fb771d30270" + +[[package]] +name = "base16ct" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c7f02d4ea65f2c1853089ffd8d2787bdbc63de2f0d29dedbcf8ccdfa0ccd4cf" + +[[package]] +name = "base64" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8" + +[[package]] +name = "base64" +version = "0.21.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d297deb1925b89f2ccc13d7635fa0714f12c87adce1c75356b39ca9b7178567" + +[[package]] +name = "base64" +version = "0.22.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" + +[[package]] +name = "base64ct" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8c3c1a368f70d6cf7302d78f8f7093da241fb8e8807c05cc9e51a125895a6d5b" + +[[package]] +name = "beef" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3a8241f3ebb85c056b509d4327ad0358fbbba6ffb340bf388f26350aeda225b1" +dependencies = [ + "serde", +] + +[[package]] +name = "bincode" +version = "1.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1f45e9417d87227c7a56d22e471c6206462cba514c7590c09aff4cf6d1ddcad" +dependencies = [ + "serde", +] + +[[package]] +name = "bindgen" +version = "0.65.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cfdf7b466f9a4903edc73f95d6d2bcd5baf8ae620638762244d3f60143643cc5" +dependencies = [ + "bitflags 1.3.2", + "cexpr", + "clang-sys", + "lazy_static", + "lazycell", + "peeking_take_while", + "prettyplease 0.2.22", + "proc-macro2", + "quote", + "regex", + "rustc-hash", + "shlex", + "syn 2.0.77", +] + +[[package]] +name = "bit-set" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0700ddab506f33b20a03b13996eccd309a48e5ff77d0d95926aa0210fb4e95f1" +dependencies = [ + "bit-vec", +] + +[[package]] +name = "bit-vec" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "349f9b6a179ed607305526ca489b34ad0a41aed5f7980fa90eb03160b69598fb" + +[[package]] +name = "bitcoin-internals" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9425c3bf7089c983facbae04de54513cce73b41c7f9ff8c845b54e7bc64ebbfb" + +[[package]] +name = "bitcoin_hashes" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1930a4dabfebb8d7d9992db18ebe3ae2876f0a305fab206fd168df931ede293b" +dependencies = [ + "bitcoin-internals", + "hex-conservative", +] + +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + +[[package]] +name = "bitflags" +version = "2.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b048fb63fd8b5923fc5aa7b340d8e156aec7ec02f0c78fa8a6ddc2613f6f71de" + +[[package]] +name = "bitvec" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bc2832c24239b0141d5674bb9174f9d68a8b5b3f2753311927c172ca46f7e9c" +dependencies = [ + "funty", + "radium", + "tap", + "wyz", +] + +[[package]] +name = "blake2" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94cb07b0da6a73955f8fb85d24c466778e70cda767a568229b104f0264089330" +dependencies = [ + "byte-tools", + "crypto-mac 0.7.0", + "digest 0.8.1", + "opaque-debug 0.2.3", +] + +[[package]] +name = "blake2" +version = "0.10.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "46502ad458c9a52b69d4d4d32775c788b7a1b85e8bc9d482d92250fc0e3f8efe" +dependencies = [ + "digest 0.10.7", +] + +[[package]] +name = "blake2-rfc" +version = "0.2.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d6d530bdd2d52966a6d03b7a964add7ae1a288d25214066fd4b600f0f796400" +dependencies = [ + "arrayvec 0.4.12", + "constant_time_eq 0.1.5", +] + +[[package]] +name = "blake2b_simd" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23285ad32269793932e830392f2fe2f83e26488fd3ec778883a93c8323735780" +dependencies = [ + "arrayref", + "arrayvec 0.7.6", + "constant_time_eq 0.3.1", +] + +[[package]] +name = "blake2s_simd" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94230421e395b9920d23df13ea5d77a20e1725331f90fbbf6df6040b33f756ae" +dependencies = [ + "arrayref", + "arrayvec 0.7.6", + "constant_time_eq 0.3.1", +] + +[[package]] +name = "blake3" +version = "1.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d82033247fd8e890df8f740e407ad4d038debb9eb1f40533fffb32e7d17dc6f7" +dependencies = [ + "arrayref", + "arrayvec 0.7.6", + "cc", + "cfg-if", + "constant_time_eq 0.3.1", +] + +[[package]] +name = "block-buffer" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0940dc441f31689269e10ac70eb1002a3a1d3ad1390e030043662eb7fe4688b" +dependencies = [ + "block-padding", + "byte-tools", + "byteorder", + "generic-array 0.12.4", +] + +[[package]] +name = "block-buffer" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4152116fd6e9dadb291ae18fc1ec3575ed6d84c29642d97890f4b4a3417297e4" +dependencies = [ + "generic-array 0.14.7", +] + +[[package]] +name = "block-buffer" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" +dependencies = [ + "generic-array 0.14.7", +] + +[[package]] +name = "block-padding" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa79dedbb091f449f1f39e53edf88d5dbe95f895dae6135a8d7b881fb5af73f5" +dependencies = [ + "byte-tools", +] + +[[package]] +name = "bounded-collections" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d32385ecb91a31bddaf908e8dcf4a15aef1bcd3913cc03ebfad02ff6d568abc1" +dependencies = [ + "log", + "parity-scale-codec", + "scale-info", + "serde", +] + +[[package]] +name = "bs58" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "771fe0050b883fcc3ea2359b1a96bcfbc090b7116eae7c3c512c7a083fdf23d3" + +[[package]] +name = "bs58" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf88ba1141d185c399bee5288d850d63b8369520c1eafc32a0430b5b6c287bf4" +dependencies = [ + "tinyvec", +] + +[[package]] +name = "bstr" +version = "1.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "40723b8fb387abc38f4f4a37c09073622e41dd12327033091ef8950659e6dc0c" +dependencies = [ + "memchr", + "regex-automata 0.4.7", + "serde", +] + +[[package]] +name = "build-helper" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bdce191bf3fa4995ce948c8c83b4640a1745457a149e73c6db75b4ffe36aad5f" +dependencies = [ + "semver 0.6.0", +] + +[[package]] +name = "bumpalo" +version = "3.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "79296716171880943b8470b5f8d03aa55eb2e645a4874bdbb28adb49162e012c" + +[[package]] +name = "byte-slice-cast" +version = "1.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3ac9f8b63eca6fd385229b3675f6cc0dc5c8a5c8a54a59d4f52ffd670d87b0c" + +[[package]] +name = "byte-tools" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3b5ca7a04898ad4bcd41c90c5285445ff5b791899bb1b0abdd2a2aa791211d7" + +[[package]] +name = "bytemuck" +version = "1.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94bbb0ad554ad961ddc5da507a12a29b14e4ae5bda06b19f575a3e6079d2e2ae" + +[[package]] +name = "byteorder" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" + +[[package]] +name = "bytes" +version = "1.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "428d9aa8fbc0670b7b8d6030a7fadd0f86151cae55e4dbbece15f3780a3dfaf3" + +[[package]] +name = "bzip2-sys" +version = "0.1.11+1.0.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "736a955f3fa7875102d57c82b8cac37ec45224a07fd32d58f9f7a186b6cd4cdc" +dependencies = [ + "cc", + "libc", + "pkg-config", +] + +[[package]] +name = "c2-chacha" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d27dae93fe7b1e0424dc57179ac396908c26b035a87234809f5c4dfd1b47dc80" +dependencies = [ + "cipher 0.2.5", + "ppv-lite86", +] + +[[package]] +name = "camino" +version = "1.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b96ec4966b5813e2c0507c1f86115c8c5abaadc3980879c3424042a02fd1ad3" +dependencies = [ + "serde", +] + +[[package]] +name = "cargo-platform" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24b1f0365a6c6bb4020cd05806fd0d33c44d38046b8bd7f0e40814b9763cabfc" +dependencies = [ + "serde", +] + +[[package]] +name = "cargo_metadata" +version = "0.15.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eee4243f1f26fc7a42710e7439c149e2b10b05472f88090acce52632f231a73a" +dependencies = [ + "camino", + "cargo-platform", + "semver 1.0.23", + "serde", + "serde_json", + "thiserror", +] + +[[package]] +name = "cc" +version = "1.1.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07b1695e2c7e8fc85310cde85aeaab7e3097f593c91d209d3f9df76c928100f0" +dependencies = [ + "jobserver", + "libc", + "shlex", +] + +[[package]] +name = "cexpr" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6fac387a98bb7c37292057cffc56d62ecb629900026402633ae9160df93a8766" +dependencies = [ + "nom", +] + +[[package]] +name = "cfg-expr" +version = "0.15.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d067ad48b8650848b989a59a86c6c36a995d02d2bf778d45c3c5d57bc2718f02" +dependencies = [ + "smallvec", +] + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "cfg_aliases" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fd16c4719339c4530435d38e511904438d07cce7950afa3718a84ac36c10e89e" + +[[package]] +name = "chacha" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ddf3c081b5fba1e5615640aae998e0fbd10c24cbd897ee39ed754a77601a4862" +dependencies = [ + "byteorder", + "keystream", +] + +[[package]] +name = "chacha20" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3613f74bd2eac03dad61bd53dbe620703d4371614fe0bc3b9f04dd36fe4e818" +dependencies = [ + "cfg-if", + "cipher 0.4.4", + "cpufeatures", +] + +[[package]] +name = "chacha20poly1305" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "10cd79432192d1c0f4e1a0fef9527696cc039165d729fb41b3f4f4f354c2dc35" +dependencies = [ + "aead", + "chacha20", + "cipher 0.4.4", + "poly1305", + "zeroize", +] + +[[package]] +name = "chrono" +version = "0.4.38" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a21f936df1771bf62b77f047b726c4625ff2e8aa607c01ec06e5a05bd8463401" +dependencies = [ + "android-tzdata", + "iana-time-zone", + "js-sys", + "num-traits", + "wasm-bindgen", + "windows-targets 0.52.6", +] + +[[package]] +name = "cid" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9b68e3193982cd54187d71afdb2a271ad4cf8af157858e9cb911b91321de143" +dependencies = [ + "core2", + "multibase", + "multihash", + "serde", + "unsigned-varint", +] + +[[package]] +name = "cipher" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "12f8e7987cbd042a63249497f41aed09f8e65add917ea6566effbc56578d6801" +dependencies = [ + "generic-array 0.14.7", +] + +[[package]] +name = "cipher" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "773f3b9af64447d2ce9850330c473515014aa235e6a783b02db81ff39e4a3dad" +dependencies = [ + "crypto-common", + "inout", + "zeroize", +] + +[[package]] +name = "clang-sys" +version = "1.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b023947811758c97c59bf9d1c188fd619ad4718dcaa767947df1cadb14f39f4" +dependencies = [ + "glob", + "libc", + "libloading", +] + +[[package]] +name = "clap" +version = "4.5.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3e5a21b8495e732f1b3c364c9949b201ca7bae518c502c80256c96ad79eaf6ac" +dependencies = [ + "clap_builder", + "clap_derive", +] + +[[package]] +name = "clap_builder" +version = "4.5.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8cf2dd12af7a047ad9d6da2b6b249759a22a7abc0f474c1dae1777afa4b21a73" +dependencies = [ + "anstream", + "anstyle", + "clap_lex", + "strsim", + "terminal_size", +] + +[[package]] +name = "clap_derive" +version = "4.5.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "501d359d5f3dcaf6ecdeee48833ae73ec6e42723a1e52419c79abf9507eec0a0" +dependencies = [ + "heck 0.5.0", + "proc-macro2", + "quote", + "syn 2.0.77", +] + +[[package]] +name = "clap_lex" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1462739cb27611015575c0c11df5df7601141071f07518d56fcc1be504cbec97" + +[[package]] +name = "codespan-reporting" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3538270d33cc669650c4b093848450d380def10c331d38c768e34cac80576e6e" +dependencies = [ + "termcolor", + "unicode-width", +] + +[[package]] +name = "color-print" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ee543c60ff3888934877a5671f45494dd27ed4ba25c6670b9a7576b7ed7a8c0" +dependencies = [ + "color-print-proc-macro", +] + +[[package]] +name = "color-print-proc-macro" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77ff1a80c5f3cb1ca7c06ffdd71b6a6dd6d8f896c42141fbd43f50ed28dcdb93" +dependencies = [ + "nom", + "proc-macro2", + "quote", + "syn 2.0.77", +] + +[[package]] +name = "colorchoice" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3fd119d74b830634cea2a0f58bbd0d54540518a14397557951e79340abc28c0" + +[[package]] +name = "comfy-table" +version = "7.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b34115915337defe99b2aff5c2ce6771e5fbc4079f4b506301f5cf394c8452f7" +dependencies = [ + "strum 0.26.3", + "strum_macros 0.26.4", + "unicode-width", +] + +[[package]] +name = "common" +version = "0.1.0" +source = "git+https://github.com/w3f/ring-proof#1eedf08d97effe1921f4aa2e926575088b068e2b" +dependencies = [ + "ark-ec 0.5.0", + "ark-ff 0.5.0", + "ark-poly 0.5.0", + "ark-serialize 0.5.0", + "ark-std 0.5.0", + "fflonk", + "getrandom_or_panic", + "rand_core 0.6.4", +] + +[[package]] +name = "common-path" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2382f75942f4b3be3690fe4f86365e9c853c1587d6ee58212cebf6e2a9ccd101" + +[[package]] +name = "concurrent-queue" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ca0197aee26d1ae37445ee532fefce43251d24cc7c166799f4d46817f1d3973" +dependencies = [ + "crossbeam-utils", +] + +[[package]] +name = "console" +version = "0.15.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0e1f83fc076bd6dd27517eacdf25fef6c4dfe5f1d7448bafaaf3a26f13b5e4eb" +dependencies = [ + "encode_unicode", + "lazy_static", + "libc", + "unicode-width", + "windows-sys 0.52.0", +] + +[[package]] +name = "const-hex" +version = "1.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94fb8a24a26d37e1ffd45343323dc9fe6654ceea44c12f2fcb3d7ac29e610bc6" +dependencies = [ + "cfg-if", + "cpufeatures", + "hex", + "proptest", + "serde", +] + +[[package]] +name = "const-oid" +version = "0.9.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8" + +[[package]] +name = "const-random" +version = "0.1.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87e00182fe74b066627d63b85fd550ac2998d4b0bd86bfed477a0ae4c7c71359" +dependencies = [ + "const-random-macro", +] + +[[package]] +name = "const-random-macro" +version = "0.1.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f9d839f2a20b0aee515dc581a6172f2321f96cab76c1a38a4c584a194955390e" +dependencies = [ + "getrandom 0.2.15", + "once_cell", + "tiny-keccak", +] + +[[package]] +name = "constant_time_eq" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "245097e9a4535ee1e3e3931fcfcd55a796a44c643e8596ff6566d68f09b87bbc" + +[[package]] +name = "constant_time_eq" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c74b8349d32d297c9134b8c88677813a227df8f779daa29bfc29c183fe3dca6" + +[[package]] +name = "constcat" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd7e35aee659887cbfb97aaf227ac12cad1a9d7c71e55ff3376839ed4e282d08" + +[[package]] +name = "convert_case" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6245d59a3e82a7fc217c5828a6692dbc6dfb63a0c8c90495621f7b9d79704a0e" + +[[package]] +name = "core-foundation" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91e195e091a93c46f7102ec7818a2aa394e1e1771c3ab4825963fa03e45afb8f" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "core-foundation-sys" +version = "0.8.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" + +[[package]] +name = "core2" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b49ba7ef1ad6107f8824dbe97de947cbaac53c44e7f9756a1fba0d37c1eec505" +dependencies = [ + "memchr", +] + +[[package]] +name = "cpp_demangle" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eeaa953eaad386a53111e47172c2fedba671e5684c8dd601a5f474f4f118710f" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "cpufeatures" +version = "0.2.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "608697df725056feaccfa42cffdaeeec3fccc4ffc38358ecd19b243e716a78e0" +dependencies = [ + "libc", +] + +[[package]] +name = "cranelift-bforest" +version = "0.95.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1277fbfa94bc82c8ec4af2ded3e639d49ca5f7f3c7eeab2c66accd135ece4e70" +dependencies = [ + "cranelift-entity", +] + +[[package]] +name = "cranelift-codegen" +version = "0.95.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c6e8c31ad3b2270e9aeec38723888fe1b0ace3bea2b06b3f749ccf46661d3220" +dependencies = [ + "bumpalo", + "cranelift-bforest", + "cranelift-codegen-meta", + "cranelift-codegen-shared", + "cranelift-entity", + "cranelift-isle", + "gimli 0.27.3", + "hashbrown 0.13.2", + "log", + "regalloc2 0.6.1", + "smallvec", + "target-lexicon", +] + +[[package]] +name = "cranelift-codegen-meta" +version = "0.95.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8ac5ac30d62b2d66f12651f6b606dbdfd9c2cfd0908de6b387560a277c5c9da" +dependencies = [ + "cranelift-codegen-shared", +] + +[[package]] +name = "cranelift-codegen-shared" +version = "0.95.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd82b8b376247834b59ed9bdc0ddeb50f517452827d4a11bccf5937b213748b8" + +[[package]] +name = "cranelift-entity" +version = "0.95.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "40099d38061b37e505e63f89bab52199037a72b931ad4868d9089ff7268660b0" +dependencies = [ + "serde", +] + +[[package]] +name = "cranelift-frontend" +version = "0.95.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "64a25d9d0a0ae3079c463c34115ec59507b4707175454f0eee0891e83e30e82d" +dependencies = [ + "cranelift-codegen", + "log", + "smallvec", + "target-lexicon", +] + +[[package]] +name = "cranelift-isle" +version = "0.95.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "80de6a7d0486e4acbd5f9f87ec49912bf4c8fb6aea00087b989685460d4469ba" + +[[package]] +name = "cranelift-native" +version = "0.95.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb6b03e0e03801c4b3fd8ce0758a94750c07a44e7944cc0ffbf0d3f2e7c79b00" +dependencies = [ + "cranelift-codegen", + "libc", + "target-lexicon", +] + +[[package]] +name = "cranelift-wasm" +version = "0.95.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff3220489a3d928ad91e59dd7aeaa8b3de18afb554a6211213673a71c90737ac" +dependencies = [ + "cranelift-codegen", + "cranelift-entity", + "cranelift-frontend", + "itertools 0.10.5", + "log", + "smallvec", + "wasmparser", + "wasmtime-types", +] + +[[package]] +name = "crc32fast" +version = "1.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a97769d94ddab943e4510d138150169a2758b5ef3eb191a9ee688de3e23ef7b3" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "crossbeam-deque" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "613f8cc01fe9cf1a3eb3d7f488fd2fa8388403e97039e2f73692932e291a770d" +dependencies = [ + "crossbeam-epoch", + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-epoch" +version = "0.9.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e" +dependencies = [ + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-utils" +version = "0.8.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22ec99545bb0ed0ea7bb9b8e1e9122ea386ff8a48c0922e43f36d45ab09e0e80" + +[[package]] +name = "crunchy" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a81dae078cea95a014a339291cec439d2f232ebe854a9d672b796c6afafa9b7" + +[[package]] +name = "crypto-bigint" +version = "0.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0dc92fb57ca44df6db8059111ab3af99a63d5d0f8375d9972e319a379c6bab76" +dependencies = [ + "generic-array 0.14.7", + "rand_core 0.6.4", + "subtle 2.6.1", + "zeroize", +] + +[[package]] +name = "crypto-common" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" +dependencies = [ + "generic-array 0.14.7", + "rand_core 0.6.4", + "typenum", +] + +[[package]] +name = "crypto-mac" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4434400df11d95d556bac068ddfedd482915eb18fe8bea89bc80b6e4b1c179e5" +dependencies = [ + "generic-array 0.12.4", + "subtle 1.0.0", +] + +[[package]] +name = "crypto-mac" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b584a330336237c1eecd3e94266efb216c56ed91225d634cb2991c5f3fd1aeab" +dependencies = [ + "generic-array 0.14.7", + "subtle 2.6.1", +] + +[[package]] +name = "ctr" +version = "0.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0369ee1ad671834580515889b80f2ea915f23b8be8d0daa4bbaf2ac5c7590835" +dependencies = [ + "cipher 0.4.4", +] + +[[package]] +name = "curve25519-dalek" +version = "2.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4a9b85542f99a2dfa2a1b8e192662741c9859a846b296bef1c92ef9b58b5a216" +dependencies = [ + "byteorder", + "digest 0.8.1", + "rand_core 0.5.1", + "subtle 2.6.1", + "zeroize", +] + +[[package]] +name = "curve25519-dalek" +version = "3.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b9fdf9972b2bd6af2d913799d9ebc165ea4d2e65878e329d9c6b372c4491b61" +dependencies = [ + "byteorder", + "digest 0.9.0", + "rand_core 0.5.1", + "subtle 2.6.1", + "zeroize", +] + +[[package]] +name = "curve25519-dalek" +version = "4.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e89b8c6a2e4b1f45971ad09761aafb85514a84744b67a95e32c3cc1352d1f65c" +dependencies = [ + "cfg-if", + "cpufeatures", + "curve25519-dalek-derive", + "digest 0.10.7", + "fiat-crypto", + "platforms", + "rustc_version 0.4.1", + "subtle 2.6.1", + "zeroize", +] + +[[package]] +name = "curve25519-dalek-derive" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f46882e17999c6cc590af592290432be3bce0428cb0d5f8b6715e4dc7b383eb3" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.77", +] + +[[package]] +name = "cxx" +version = "1.0.128" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "54ccead7d199d584d139148b04b4a368d1ec7556a1d9ea2548febb1b9d49f9a4" +dependencies = [ + "cc", + "cxxbridge-flags", + "cxxbridge-macro", + "link-cplusplus", +] + +[[package]] +name = "cxx-build" +version = "1.0.128" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c77953e99f01508f89f55c494bfa867171ef3a6c8cea03d26975368f2121a5c1" +dependencies = [ + "cc", + "codespan-reporting", + "once_cell", + "proc-macro2", + "quote", + "scratch", + "syn 2.0.77", +] + +[[package]] +name = "cxxbridge-flags" +version = "1.0.128" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "65777e06cc48f0cb0152024c77d6cf9e4bdb4408e7b48bea993d42fa0f5b02b6" + +[[package]] +name = "cxxbridge-macro" +version = "1.0.128" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "98532a60dedaebc4848cb2cba5023337cc9ea3af16a5b062633fabfd9f18fb60" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.77", +] + +[[package]] +name = "dashmap" +version = "5.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "978747c1d849a7d2ee5e8adc0159961c48fb7e5db2f06af6723b80123bb53856" +dependencies = [ + "cfg-if", + "hashbrown 0.14.5", + "lock_api", + "once_cell", + "parking_lot_core 0.9.10", +] + +[[package]] +name = "data-encoding" +version = "2.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e8566979429cf69b49a5c740c60791108e86440e8be149bbea4fe54d2c32d6e2" + +[[package]] +name = "data-encoding-macro" +version = "0.1.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1559b6cba622276d6d63706db152618eeb15b89b3e4041446b05876e352e639" +dependencies = [ + "data-encoding", + "data-encoding-macro-internal", +] + +[[package]] +name = "data-encoding-macro-internal" +version = "0.1.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "332d754c0af53bc87c108fed664d121ecf59207ec4196041f04d6ab9002ad33f" +dependencies = [ + "data-encoding", + "syn 1.0.109", +] + +[[package]] +name = "der" +version = "0.7.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f55bf8e7b65898637379c1b74eb1551107c8294ed26d855ceb9fd1a09cfc9bc0" +dependencies = [ + "const-oid", + "zeroize", +] + +[[package]] +name = "der-parser" +version = "8.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dbd676fbbab537128ef0278adb5576cf363cff6aa22a7b24effe97347cfab61e" +dependencies = [ + "asn1-rs", + "displaydoc", + "nom", + "num-bigint", + "num-traits", + "rusticata-macros", +] + +[[package]] +name = "deranged" +version = "0.3.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b42b6fa04a440b495c8b04d0e71b707c585f83cb9cb28cf8cd0d976c315e31b4" +dependencies = [ + "powerfmt", +] + +[[package]] +name = "derivative" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fcc3dd5e9e9c0b295d6e1e4d811fb6f157d5ffd784b8d202fc62eac8035a770b" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "derive-syn-parse" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e79116f119dd1dba1abf1f3405f03b9b0e79a27a3883864bfebded8a3dc768cd" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "derive-syn-parse" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d65d7ce8132b7c0e54497a4d9a55a1c2a0912a0d786cf894472ba818fba45762" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.77", +] + +[[package]] +name = "derive_more" +version = "0.99.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f33878137e4dafd7fa914ad4e259e18a4e8e532b9617a2d0150262bf53abfce" +dependencies = [ + "convert_case", + "proc-macro2", + "quote", + "rustc_version 0.4.1", + "syn 2.0.77", +] + +[[package]] +name = "difflib" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6184e33543162437515c2e2b48714794e37845ec9851711914eec9d308f6ebe8" + +[[package]] +name = "digest" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f3d0c8c8752312f9713efd397ff63acb9f85585afbf179282e720e7704954dd5" +dependencies = [ + "generic-array 0.12.4", +] + +[[package]] +name = "digest" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3dd60d1080a57a05ab032377049e0591415d2b31afd7028356dbf3cc6dcb066" +dependencies = [ + "generic-array 0.14.7", +] + +[[package]] +name = "digest" +version = "0.10.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" +dependencies = [ + "block-buffer 0.10.4", + "const-oid", + "crypto-common", + "subtle 2.6.1", +] + +[[package]] +name = "directories" +version = "5.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a49173b84e034382284f27f1af4dcbbd231ffa358c0fe316541a7337f376a35" +dependencies = [ + "dirs-sys", +] + +[[package]] +name = "directories-next" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "339ee130d97a610ea5a5872d2bbb130fdf68884ff09d3028b81bec8a1ac23bbc" +dependencies = [ + "cfg-if", + "dirs-sys-next", +] + +[[package]] +name = "dirs-sys" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "520f05a5cbd335fae5a99ff7a6ab8627577660ee5cfd6a94a6a929b52ff0321c" +dependencies = [ + "libc", + "option-ext", + "redox_users", + "windows-sys 0.48.0", +] + +[[package]] +name = "dirs-sys-next" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ebda144c4fe02d1f7ea1a7d9641b6fc6b580adcfa024ae48797ecdeb6825b4d" +dependencies = [ + "libc", + "redox_users", + "winapi", +] + +[[package]] +name = "displaydoc" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.77", +] + +[[package]] +name = "dleq_vrf" +version = "0.0.2" +source = "git+https://github.com/w3f/ring-vrf?rev=e9782f9#e9782f938629c90f3adb3fff2358bc8d1386af3e" +dependencies = [ + "ark-ec 0.4.2", + "ark-ff 0.4.2", + "ark-scale", + "ark-secret-scalar", + "ark-serialize 0.4.2", + "ark-std 0.4.0", + "ark-transcript 0.0.2", + "arrayvec 0.7.6", + "zeroize", +] + +[[package]] +name = "docify" +version = "0.2.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43a2f138ad521dc4a2ced1a4576148a6a610b4c5923933b062a263130a6802ce" +dependencies = [ + "docify_macros", +] + +[[package]] +name = "docify_macros" +version = "0.2.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a081e51fb188742f5a7a1164ad752121abcb22874b21e2c3b0dd040c515fdad" +dependencies = [ + "common-path", + "derive-syn-parse 0.2.0", + "once_cell", + "proc-macro2", + "quote", + "regex", + "syn 2.0.77", + "termcolor", + "toml 0.8.19", + "walkdir", +] + +[[package]] +name = "downcast" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1435fa1053d8b2fbbe9be7e97eca7f33d37b28409959813daefc1446a14247f1" + +[[package]] +name = "dtoa" +version = "1.0.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dcbb2bf8e87535c23f7a8a321e364ce21462d0ff10cb6407820e8e96dfff6653" + +[[package]] +name = "dunce" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92773504d58c093f6de2459af4af33faa518c13451eb8f2b5698ed3d36e7c813" + +[[package]] +name = "dyn-clonable" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4e9232f0e607a262ceb9bd5141a3dfb3e4db6994b31989bbfd845878cba59fd4" +dependencies = [ + "dyn-clonable-impl", + "dyn-clone", +] + +[[package]] +name = "dyn-clonable-impl" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "558e40ea573c374cf53507fd240b7ee2f5477df7cfebdb97323ec61c719399c5" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "dyn-clone" +version = "1.0.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d6ef0072f8a535281e4876be788938b528e9a1d43900b82c2569af7da799125" + +[[package]] +name = "ecdsa" +version = "0.16.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee27f32b5c5292967d2d4a9d7f1e0b0aed2c15daded5a60300e4abb9d8020bca" +dependencies = [ + "der", + "digest 0.10.7", + "elliptic-curve", + "rfc6979", + "serdect", + "signature", + "spki", +] + +[[package]] +name = "ed25519" +version = "2.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "115531babc129696a58c64a4fef0a8bf9e9698629fb97e9e40767d235cfbcd53" +dependencies = [ + "pkcs8", + "signature", +] + +[[package]] +name = "ed25519-dalek" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4a3daa8e81a3963a60642bcc1f90a670680bd4a77535faa384e9d1c79d620871" +dependencies = [ + "curve25519-dalek 4.1.1", + "ed25519", + "rand_core 0.6.4", + "serde", + "sha2 0.10.8", + "subtle 2.6.1", + "zeroize", +] + +[[package]] +name = "ed25519-zebra" +version = "3.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c24f403d068ad0b359e577a77f92392118be3f3c927538f2bb544a5ecd828c6" +dependencies = [ + "curve25519-dalek 3.2.0", + "hashbrown 0.12.3", + "hex", + "rand_core 0.6.4", + "sha2 0.9.9", + "zeroize", +] + +[[package]] +name = "educe" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d7bc049e1bd8cdeb31b68bbd586a9464ecf9f3944af3958a7a9d0f8b9799417" +dependencies = [ + "enum-ordinalize", + "proc-macro2", + "quote", + "syn 2.0.77", +] + +[[package]] +name = "either" +version = "1.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "60b1af1c220855b6ceac025d3f6ecdd2b7c4894bfe9cd9bda4fbb4bc7c0d4cf0" + +[[package]] +name = "elliptic-curve" +version = "0.13.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5e6043086bf7973472e0c7dff2142ea0b680d30e18d9cc40f267efbf222bd47" +dependencies = [ + "base16ct", + "crypto-bigint", + "digest 0.10.7", + "ff", + "generic-array 0.14.7", + "group", + "pkcs8", + "rand_core 0.6.4", + "sec1", + "serdect", + "subtle 2.6.1", + "zeroize", +] + +[[package]] +name = "encode_unicode" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a357d28ed41a50f9c765dbfe56cbc04a64e53e5fc58ba79fbc34c10ef3df831f" + +[[package]] +name = "enum-as-inner" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c9720bba047d567ffc8a3cba48bf19126600e249ab7f128e9233e6376976a116" +dependencies = [ + "heck 0.4.1", + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "enum-ordinalize" +version = "4.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fea0dcfa4e54eeb516fe454635a95753ddd39acda650ce703031c6973e315dd5" +dependencies = [ + "enum-ordinalize-derive", +] + +[[package]] +name = "enum-ordinalize-derive" +version = "4.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d28318a75d4aead5c4db25382e8ef717932d0346600cacae6357eb5941bc5ff" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.77", +] + +[[package]] +name = "enumflags2" +version = "0.7.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d232db7f5956f3f14313dc2f87985c58bd2c695ce124c8cdd984e08e15ac133d" +dependencies = [ + "enumflags2_derive", +] + +[[package]] +name = "enumflags2_derive" +version = "0.7.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "de0d48a183585823424a4ce1aa132d174a6a81bd540895822eb4c8373a8e49e8" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.77", +] + +[[package]] +name = "env_logger" +version = "0.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a12e6657c4c97ebab115a42dcee77225f7f482cdd841cf7088c657a42e9e00e7" +dependencies = [ + "atty", + "humantime", + "log", + "regex", + "termcolor", +] + +[[package]] +name = "env_logger" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4cd405aab171cb85d6735e5c8d9db038c17d3ca007a4d2c25f337935c3d90580" +dependencies = [ + "humantime", + "is-terminal", + "log", + "regex", + "termcolor", +] + +[[package]] +name = "environmental" +version = "1.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e48c92028aaa870e83d51c64e5d4e0b6981b360c522198c23959f219a4e1b15b" + +[[package]] +name = "equivalent" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" + +[[package]] +name = "errno" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "534c5cf6194dfab3db3242765c03bbe257cf92f22b38f6bc0c58d59108a820ba" +dependencies = [ + "libc", + "windows-sys 0.52.0", +] + +[[package]] +name = "event-listener" +version = "2.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0206175f82b8d6bf6652ff7d71a1e27fd2e4efde587fd368662814d6ec1d9ce0" + +[[package]] +name = "event-listener" +version = "5.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6032be9bd27023a771701cc49f9f053c751055f71efb2e0ae5c15809093675ba" +dependencies = [ + "concurrent-queue", + "parking", + "pin-project-lite 0.2.14", +] + +[[package]] +name = "event-listener-strategy" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0f214dc438f977e6d4e3500aaa277f5ad94ca83fbbd9b1a15713ce2344ccc5a1" +dependencies = [ + "event-listener 5.3.1", + "pin-project-lite 0.2.14", +] + +[[package]] +name = "exit-future" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e43f2f1833d64e33f15592464d6fdd70f349dda7b1a53088eb83cd94014008c5" +dependencies = [ + "futures", +] + +[[package]] +name = "expander" +version = "2.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2c470c71d91ecbd179935b24170459e926382eaaa86b590b78814e180d8a8e2" +dependencies = [ + "blake2 0.10.6", + "file-guard", + "fs-err", + "prettyplease 0.2.22", + "proc-macro2", + "quote", + "syn 2.0.77", +] + +[[package]] +name = "extrinsic-shuffler" +version = "4.0.0-dev" +source = "git+https://github.com/gasp-xyz/polkadot-sdk?branch=eth-rollup-develop#e5bbc9b3eaa4f69cd39000bb69f6963f36dd93b8" +dependencies = [ + "derive_more", + "log", + "parity-scale-codec", + "sp-api", + "sp-block-builder", + "sp-core", + "sp-runtime", + "sp-std", + "sp-ver", + "ver-api", +] + +[[package]] +name = "fake-simd" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e88a8acf291dafb59c2d96e8f59828f3838bb1a70398823ade51a84de6a6deed" + +[[package]] +name = "fallible-iterator" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4443176a9f2c162692bd3d352d745ef9413eec5782a80d8fd6f8a1ac692a07f7" + +[[package]] +name = "fallible-iterator" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2acce4a10f12dc2fb14a218589d4f1f62ef011b2d0cc4b3cb1bba8e94da14649" + +[[package]] +name = "fastrand" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e8c02a5121d4ea3eb16a80748c74f5549a5665e4c21333c6098f283870fbdea6" + +[[package]] +name = "fastrlp" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "139834ddba373bbdd213dffe02c8d110508dcf1726c2be27e8d1f7d7e1856418" +dependencies = [ + "arrayvec 0.7.6", + "auto_impl", + "bytes", +] + +[[package]] +name = "fdlimit" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e182f7dbc2ef73d9ef67351c5fbbea084729c48362d3ce9dd44c28e32e277fe5" +dependencies = [ + "libc", + "thiserror", +] + +[[package]] +name = "ff" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ded41244b729663b1e574f1b4fb731469f69f79c17667b5d776b16cda0479449" +dependencies = [ + "rand_core 0.6.4", + "subtle 2.6.1", +] + +[[package]] +name = "fflonk" +version = "0.1.1" +source = "git+https://github.com/w3f/fflonk#eda051ea3b80042e844a3ebd17c2f60536e6ee3f" +dependencies = [ + "ark-ec 0.5.0", + "ark-ff 0.5.0", + "ark-poly 0.5.0", + "ark-serialize 0.5.0", + "ark-std 0.5.0", + "merlin 3.0.0", +] + +[[package]] +name = "fiat-crypto" +version = "0.2.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "28dea519a9695b9977216879a3ebfddf92f1c08c05d984f8996aecd6ecdc811d" + +[[package]] +name = "file-guard" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "21ef72acf95ec3d7dbf61275be556299490a245f017cf084bd23b4f68cf9407c" +dependencies = [ + "libc", + "winapi", +] + +[[package]] +name = "file-per-thread-logger" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "84f2e425d9790201ba4af4630191feac6dcc98765b118d4d18e91d23c2353866" +dependencies = [ + "env_logger 0.10.2", + "log", +] + +[[package]] +name = "filetime" +version = "0.2.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "35c0522e981e68cbfa8c3f978441a5f34b30b96e146b33cd3359176b50fe8586" +dependencies = [ + "cfg-if", + "libc", + "libredox", + "windows-sys 0.59.0", +] + +[[package]] +name = "finality-grandpa" +version = "0.16.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "36530797b9bf31cd4ff126dcfee8170f86b00cfdcea3269d73133cc0415945c3" +dependencies = [ + "either", + "futures", + "futures-timer", + "log", + "num-traits", + "parity-scale-codec", + "parking_lot 0.12.3", + "scale-info", +] + +[[package]] +name = "fixed-hash" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "835c052cb0c08c1acf6ffd71c022172e18723949c8282f2b9f27efbc51e64534" +dependencies = [ + "byteorder", + "rand 0.8.5", + "rustc-hex", + "static_assertions", +] + +[[package]] +name = "fixedbitset" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ce7134b9999ecaf8bcd65542e436736ef32ddca1b3e06094cb6ec5755203b80" + +[[package]] +name = "flate2" +version = "1.0.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "324a1be68054ef05ad64b861cc9eaf1d623d2d8cb25b4bf2cb9cdd902b4bf253" +dependencies = [ + "crc32fast", + "libz-sys", + "miniz_oxide", +] + +[[package]] +name = "float-cmp" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "98de4bbd547a563b716d8dfa9aad1cb19bfab00f4fa09a6a4ed21dbcf44ce9c4" +dependencies = [ + "num-traits", +] + +[[package]] +name = "fnv" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" + +[[package]] +name = "fork-tree" +version = "12.0.0" +source = "git+https://github.com/gasp-xyz/polkadot-sdk?branch=eth-rollup-develop#e5bbc9b3eaa4f69cd39000bb69f6963f36dd93b8" +dependencies = [ + "parity-scale-codec", +] + +[[package]] +name = "form_urlencoded" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e13624c2627564efccf4934284bdd98cbaa14e79b0b5a141218e507b3a823456" +dependencies = [ + "percent-encoding", +] + +[[package]] +name = "fragile" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c2141d6d6c8512188a7891b4b01590a45f6dac67afb4f255c4124dbb86d4eaa" + +[[package]] +name = "frame-benchmarking" +version = "28.0.0" +source = "git+https://github.com/gasp-xyz/polkadot-sdk?branch=eth-rollup-develop#e5bbc9b3eaa4f69cd39000bb69f6963f36dd93b8" +dependencies = [ + "frame-support", + "frame-support-procedural", + "frame-system", + "linregress", + "log", + "parity-scale-codec", + "paste", + "scale-info", + "serde", + "sp-api", + "sp-application-crypto", + "sp-core", + "sp-io", + "sp-runtime", + "sp-runtime-interface", + "sp-std", + "sp-storage", + "static_assertions", +] + +[[package]] +name = "frame-benchmarking-cli" +version = "32.0.0" +source = "git+https://github.com/gasp-xyz/polkadot-sdk?branch=eth-rollup-develop#e5bbc9b3eaa4f69cd39000bb69f6963f36dd93b8" +dependencies = [ + "Inflector", + "array-bytes 6.2.3", + "chrono", + "clap", + "comfy-table", + "frame-benchmarking", + "frame-support", + "frame-system", + "futures", + "gethostname", + "handlebars", + "itertools 0.10.5", + "lazy_static", + "linked-hash-map", + "log", + "parity-scale-codec", + "rand 0.8.5", + "rand_pcg", + "sc-block-builder", + "sc-block-builder-ver", + "sc-cli", + "sc-client-api", + "sc-client-db", + "sc-consensus", + "sc-executor", + "sc-service", + "sc-sysinfo", + "serde", + "serde_json", + "sp-api", + "sp-blockchain", + "sp-consensus", + "sp-consensus-aura", + "sp-core", + "sp-database", + "sp-externalities", + "sp-inherents", + "sp-io", + "sp-keystore", + "sp-runtime", + "sp-state-machine", + "sp-storage", + "sp-trie", + "sp-wasm-interface", + "thiserror", + "thousands", + "ver-api", +] + +[[package]] +name = "frame-executive" +version = "28.0.0" +source = "git+https://github.com/gasp-xyz/polkadot-sdk?branch=eth-rollup-develop#e5bbc9b3eaa4f69cd39000bb69f6963f36dd93b8" +dependencies = [ + "aquamarine 0.3.3", + "extrinsic-shuffler", + "frame-support", + "frame-system", + "frame-try-runtime", + "log", + "merlin 2.0.1", + "parity-scale-codec", + "scale-info", + "schnorrkel 0.9.1", + "sp-core", + "sp-io", + "sp-runtime", + "sp-std", + "sp-tracing", +] + +[[package]] +name = "frame-metadata" +version = "16.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87cf1549fba25a6fcac22785b61698317d958e96cac72a59102ea45b9ae64692" +dependencies = [ + "cfg-if", + "parity-scale-codec", + "scale-info", + "serde", +] + +[[package]] +name = "frame-remote-externalities" +version = "0.35.0" +source = "git+https://github.com/gasp-xyz/polkadot-sdk?branch=eth-rollup-develop#6b71a3c1276a1e0d8a75eaf5932d9afb82bb477a" +dependencies = [ + "futures", + "indicatif", + "jsonrpsee", + "log", + "parity-scale-codec", + "serde", + "sp-core", + "sp-crypto-hashing", + "sp-io", + "sp-runtime", + "sp-state-machine", + "spinners", + "substrate-rpc-client", + "tokio", + "tokio-retry", +] + +[[package]] +name = "frame-support" +version = "28.0.0" +source = "git+https://github.com/gasp-xyz/polkadot-sdk?branch=eth-rollup-develop#e5bbc9b3eaa4f69cd39000bb69f6963f36dd93b8" +dependencies = [ + "aquamarine 0.5.0", + "array-bytes 6.2.3", + "bitflags 1.3.2", + "docify", + "environmental", + "frame-metadata", + "frame-support-procedural", + "impl-trait-for-tuples", + "k256", + "log", + "macro_magic", + "mangata-types", + "parity-scale-codec", + "paste", + "scale-info", + "serde", + "serde_json", + "smallvec", + "sp-api", + "sp-arithmetic", + "sp-core", + "sp-crypto-hashing-proc-macro", + "sp-debug-derive", + "sp-genesis-builder", + "sp-inherents", + "sp-io", + "sp-metadata-ir", + "sp-runtime", + "sp-staking", + "sp-state-machine", + "sp-std", + "sp-tracing", + "sp-weights", + "static_assertions", + "tt-call", +] + +[[package]] +name = "frame-support-procedural" +version = "23.0.0" +source = "git+https://github.com/gasp-xyz/polkadot-sdk?branch=eth-rollup-develop#e5bbc9b3eaa4f69cd39000bb69f6963f36dd93b8" +dependencies = [ + "Inflector", + "cfg-expr", + "derive-syn-parse 0.1.5", + "expander", + "frame-support-procedural-tools", + "itertools 0.10.5", + "macro_magic", + "proc-macro-warning", + "proc-macro2", + "quote", + "sp-crypto-hashing", + "syn 2.0.77", +] + +[[package]] +name = "frame-support-procedural-tools" +version = "10.0.0" +source = "git+https://github.com/gasp-xyz/polkadot-sdk?branch=eth-rollup-develop#e5bbc9b3eaa4f69cd39000bb69f6963f36dd93b8" +dependencies = [ + "frame-support-procedural-tools-derive", + "proc-macro-crate 3.2.0", + "proc-macro2", + "quote", + "syn 2.0.77", +] + +[[package]] +name = "frame-support-procedural-tools-derive" +version = "11.0.0" +source = "git+https://github.com/gasp-xyz/polkadot-sdk?branch=eth-rollup-develop#e5bbc9b3eaa4f69cd39000bb69f6963f36dd93b8" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.77", +] + +[[package]] +name = "frame-system" +version = "28.0.0" +source = "git+https://github.com/gasp-xyz/polkadot-sdk?branch=eth-rollup-develop#e5bbc9b3eaa4f69cd39000bb69f6963f36dd93b8" +dependencies = [ + "cfg-if", + "docify", + "extrinsic-shuffler", + "frame-support", + "log", + "parity-scale-codec", + "scale-info", + "serde", + "sp-core", + "sp-io", + "sp-runtime", + "sp-std", + "sp-ver", + "sp-version", + "sp-weights", +] + +[[package]] +name = "frame-system-benchmarking" +version = "28.0.0" +source = "git+https://github.com/gasp-xyz/polkadot-sdk?branch=eth-rollup-develop#e5bbc9b3eaa4f69cd39000bb69f6963f36dd93b8" +dependencies = [ + "frame-benchmarking", + "frame-support", + "frame-system", + "parity-scale-codec", + "scale-info", + "sp-core", + "sp-runtime", + "sp-std", +] + +[[package]] +name = "frame-system-rpc-runtime-api" +version = "26.0.0" +source = "git+https://github.com/gasp-xyz/polkadot-sdk?branch=eth-rollup-develop#e5bbc9b3eaa4f69cd39000bb69f6963f36dd93b8" +dependencies = [ + "parity-scale-codec", + "sp-api", +] + +[[package]] +name = "frame-try-runtime" +version = "0.34.0" +source = "git+https://github.com/gasp-xyz/polkadot-sdk?branch=eth-rollup-develop#e5bbc9b3eaa4f69cd39000bb69f6963f36dd93b8" +dependencies = [ + "frame-support", + "parity-scale-codec", + "sp-api", + "sp-runtime", + "sp-std", +] + +[[package]] +name = "fs-err" +version = "2.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "88a41f105fe1d5b6b34b2055e3dc59bb79b46b48b2040b9e6c7b4b5de097aa41" +dependencies = [ + "autocfg", +] + +[[package]] +name = "fs2" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9564fc758e15025b46aa6643b1b77d047d1a56a1aea6e01002ac0c7026876213" +dependencies = [ + "libc", + "winapi", +] + +[[package]] +name = "funty" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6d5a32815ae3f33302d95fdcb2ce17862f8c65363dcfd29360480ba1001fc9c" + +[[package]] +name = "futures" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "645c6916888f6cb6350d2550b80fb63e734897a8498abe35cfb732b6487804b0" +dependencies = [ + "futures-channel", + "futures-core", + "futures-executor", + "futures-io", + "futures-sink", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-channel" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eac8f7d7865dcb88bd4373ab671c8cf4508703796caa2b1985a9ca867b3fcb78" +dependencies = [ + "futures-core", + "futures-sink", +] + +[[package]] +name = "futures-core" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dfc6580bb841c5a68e9ef15c77ccc837b40a7504914d52e47b8b0e9bbda25a1d" + +[[package]] +name = "futures-executor" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a576fc72ae164fca6b9db127eaa9a9dda0d61316034f33a0a0d4eda41f02b01d" +dependencies = [ + "futures-core", + "futures-task", + "futures-util", + "num_cpus", +] + +[[package]] +name = "futures-io" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a44623e20b9681a318efdd71c299b6b222ed6f231972bfe2f224ebad6311f0c1" + +[[package]] +name = "futures-lite" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "52527eb5074e35e9339c6b4e8d12600c7128b68fb25dcb9fa9dec18f7c25f3a5" +dependencies = [ + "futures-core", + "pin-project-lite 0.2.14", +] + +[[package]] +name = "futures-macro" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87750cf4b7a4c0625b1529e4c543c2182106e4dedc60a2a6455e00d212c489ac" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.77", +] + +[[package]] +name = "futures-rustls" +version = "0.22.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2411eed028cdf8c8034eaf21f9915f956b6c3abec4d4c7949ee67f0721127bd" +dependencies = [ + "futures-io", + "rustls 0.20.9", + "webpki", +] + +[[package]] +name = "futures-sink" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9fb8e00e87438d937621c1c6269e53f536c14d3fbd6a042bb24879e57d474fb5" + +[[package]] +name = "futures-task" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38d84fa142264698cdce1a9f9172cf383a0c82de1bddcf3092901442c4097004" + +[[package]] +name = "futures-timer" +version = "3.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f288b0a4f20f9a56b5d1da57e2227c661b7b16168e2f72365f57b63326e29b24" +dependencies = [ + "gloo-timers", + "send_wrapper", +] + +[[package]] +name = "futures-util" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d6401deb83407ab3da39eba7e33987a73c3df0c82b4bb5813ee871c19c41d48" +dependencies = [ + "futures-channel", + "futures-core", + "futures-io", + "futures-macro", + "futures-sink", + "futures-task", + "memchr", + "pin-project-lite 0.2.14", + "pin-utils", + "slab", +] + +[[package]] +name = "fxhash" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c31b6d751ae2c7f11320402d34e41349dd1016f8d5d45e48c4312bc8625af50c" +dependencies = [ + "byteorder", +] + +[[package]] +name = "generic-array" +version = "0.12.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ffdf9f34f1447443d37393cc6c2b8313aebddcd96906caf34e54c68d8e57d7bd" +dependencies = [ + "typenum", +] + +[[package]] +name = "generic-array" +version = "0.14.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" +dependencies = [ + "typenum", + "version_check", + "zeroize", +] + +[[package]] +name = "gethostname" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c1ebd34e35c46e00bb73e81363248d627782724609fe1b6396f553f68fe3862e" +dependencies = [ + "libc", + "winapi", +] + +[[package]] +name = "getrandom" +version = "0.1.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8fc3cb4d91f53b50155bdcfd23f6a4c39ae1969c2ae85982b135750cccaf5fce" +dependencies = [ + "cfg-if", + "libc", + "wasi 0.9.0+wasi-snapshot-preview1", +] + +[[package]] +name = "getrandom" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7" +dependencies = [ + "cfg-if", + "libc", + "wasi 0.11.0+wasi-snapshot-preview1", +] + +[[package]] +name = "getrandom_or_panic" +version = "0.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ea1015b5a70616b688dc230cfe50c8af89d972cb132d5a622814d29773b10b9" +dependencies = [ + "rand 0.8.5", + "rand_core 0.6.4", +] + +[[package]] +name = "ghash" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0d8a4362ccb29cb0b265253fb0a2728f592895ee6854fd9bc13f2ffda266ff1" +dependencies = [ + "opaque-debug 0.3.1", + "polyval", +] + +[[package]] +name = "gimli" +version = "0.27.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6c80984affa11d98d1b88b66ac8853f143217b399d3c74116778ff8fdb4ed2e" +dependencies = [ + "fallible-iterator 0.2.0", + "indexmap 1.9.3", + "stable_deref_trait", +] + +[[package]] +name = "gimli" +version = "0.28.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4271d37baee1b8c7e4b708028c57d816cf9d2434acb33a549475f78c181f6253" +dependencies = [ + "fallible-iterator 0.3.0", + "stable_deref_trait", +] + +[[package]] +name = "gimli" +version = "0.31.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32085ea23f3234fc7846555e85283ba4de91e21016dc0455a16286d87a292d64" + +[[package]] +name = "glob" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b" + +[[package]] +name = "gloo-net" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43aaa242d1239a8822c15c645f02166398da4f8b5c4bae795c1f5b44e9eee173" +dependencies = [ + "futures-channel", + "futures-core", + "futures-sink", + "gloo-utils", + "http", + "js-sys", + "pin-project", + "serde", + "serde_json", + "thiserror", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", +] + +[[package]] +name = "gloo-timers" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b995a66bb87bebce9a0f4a95aed01daca4872c050bfcb21653361c03bc35e5c" +dependencies = [ + "futures-channel", + "futures-core", + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "gloo-utils" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b5555354113b18c547c1d3a98fbf7fb32a9ff4f6fa112ce823a21641a0ba3aa" +dependencies = [ + "js-sys", + "serde", + "serde_json", + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "governor" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68a7f542ee6b35af73b06abc0dad1c1bae89964e4e253bc4b587b91c9637867b" +dependencies = [ + "cfg-if", + "dashmap", + "futures", + "futures-timer", + "no-std-compat", + "nonzero_ext", + "parking_lot 0.12.3", + "portable-atomic", + "quanta", + "rand 0.8.5", + "smallvec", + "spinning_top", +] + +[[package]] +name = "group" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0f9ef7462f7c099f518d754361858f86d8a07af53ba9af0fe635bbccb151a63" +dependencies = [ + "ff", + "rand_core 0.6.4", + "subtle 2.6.1", +] + +[[package]] +name = "h2" +version = "0.3.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "81fe527a889e1532da5c525686d96d4c2e74cdd345badf8dfef9f6b39dd5f5e8" +dependencies = [ + "bytes", + "fnv", + "futures-core", + "futures-sink", + "futures-util", + "http", + "indexmap 2.5.0", + "slab", + "tokio", + "tokio-util", + "tracing", +] + +[[package]] +name = "handlebars" +version = "5.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d08485b96a0e6393e9e4d1b8d48cf74ad6c063cd905eb33f42c1ce3f0377539b" +dependencies = [ + "log", + "pest", + "pest_derive", + "serde", + "serde_json", + "thiserror", +] + +[[package]] +name = "hash-db" +version = "0.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e7d7786361d7425ae2fe4f9e407eb0efaa0840f5212d109cc018c40c35c6ab4" + +[[package]] +name = "hash256-std-hasher" +version = "0.15.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92c171d55b98633f4ed3860808f004099b36c1cc29c42cfc53aa8591b21efcf2" +dependencies = [ + "crunchy", +] + +[[package]] +name = "hashbrown" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" +dependencies = [ + "ahash 0.7.8", +] + +[[package]] +name = "hashbrown" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43a3c133739dddd0d2990f9a4bdf8eb4b21ef50e4851ca85ab661199821d510e" +dependencies = [ + "ahash 0.8.11", +] + +[[package]] +name = "hashbrown" +version = "0.14.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" +dependencies = [ + "ahash 0.8.11", + "allocator-api2", +] + +[[package]] +name = "hashbrown" +version = "0.15.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf151400ff0baff5465007dd2f3e717f3fe502074ca563069ce3a6629d07b289" +dependencies = [ + "allocator-api2", +] + +[[package]] +name = "hashlink" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e8094feaf31ff591f651a2664fb9cfd92bba7a60ce3197265e9482ebe753c8f7" +dependencies = [ + "hashbrown 0.14.5", +] + +[[package]] +name = "heck" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" + +[[package]] +name = "heck" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" + +[[package]] +name = "hermit-abi" +version = "0.1.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33" +dependencies = [ + "libc", +] + +[[package]] +name = "hermit-abi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d231dfb89cfffdbc30e7fc41579ed6066ad03abda9e567ccafae602b97ec5024" + +[[package]] +name = "hermit-abi" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fbf6a919d6cf397374f7dfeeea91d974c7c0a7221d0d0f4f20d859d329e53fcc" + +[[package]] +name = "hex" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" + +[[package]] +name = "hex-conservative" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "212ab92002354b4819390025006c897e8140934349e8635c9b077f47b4dcbd20" + +[[package]] +name = "hex-literal" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ebdb29d2ea9ed0083cd8cece49bbd968021bd99b0849edb4a9a7ee0fdf6a4e0" + +[[package]] +name = "hex-literal" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6fe2267d4ed49bc07b63801559be28c718ea06c4738b7a03c94df7386d2cde46" + +[[package]] +name = "hkdf" +version = "0.12.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b5f8eb2ad728638ea2c7d47a21db23b7b58a72ed6a38256b8a1849f15fbbdf7" +dependencies = [ + "hmac 0.12.1", +] + +[[package]] +name = "hmac" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "126888268dcc288495a26bf004b38c5fdbb31682f992c84ceb046a1f0fe38840" +dependencies = [ + "crypto-mac 0.8.0", + "digest 0.9.0", +] + +[[package]] +name = "hmac" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e" +dependencies = [ + "digest 0.10.7", +] + +[[package]] +name = "hmac-drbg" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "17ea0a1394df5b6574da6e0c1ade9e78868c9fb0a4e5ef4428e32da4676b85b1" +dependencies = [ + "digest 0.9.0", + "generic-array 0.14.7", + "hmac 0.8.1", +] + +[[package]] +name = "home" +version = "0.5.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3d1354bf6b7235cb4a0576c2619fd4ed18183f689b12b006a0ee7329eeff9a5" +dependencies = [ + "windows-sys 0.52.0", +] + +[[package]] +name = "hostname" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c731c3e10504cc8ed35cfe2f1db4c9274c3d35fa486e3b31df46f068ef3e867" +dependencies = [ + "libc", + "match_cfg", + "winapi", +] + +[[package]] +name = "http" +version = "0.2.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "601cbb57e577e2f5ef5be8e7b83f0f63994f25aa94d673e54a92d5c516d101f1" +dependencies = [ + "bytes", + "fnv", + "itoa", +] + +[[package]] +name = "http-body" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ceab25649e9960c0311ea418d17bee82c0dcec1bd053b5f9a66e265a693bed2" +dependencies = [ + "bytes", + "http", + "pin-project-lite 0.2.14", +] + +[[package]] +name = "http-range-header" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "add0ab9360ddbd88cfeb3bd9574a1d85cfdfa14db10b3e21d3700dbc4328758f" + +[[package]] +name = "httparse" +version = "1.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fcc0b4a115bf80b728eb8ea024ad5bd707b615bfed49e0665b6e0f86fd082d9" + +[[package]] +name = "httpdate" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" + +[[package]] +name = "humantime" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4" + +[[package]] +name = "hyper" +version = "0.14.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a152ddd61dfaec7273fe8419ab357f33aee0d914c5f4efbf0d96fa749eea5ec9" +dependencies = [ + "bytes", + "futures-channel", + "futures-core", + "futures-util", + "h2", + "http", + "http-body", + "httparse", + "httpdate", + "itoa", + "pin-project-lite 0.2.14", + "socket2 0.5.7", + "tokio", + "tower-service", + "tracing", + "want", +] + +[[package]] +name = "hyper-rustls" +version = "0.24.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec3efd23720e2049821a693cbc7e65ea87c72f1c58ff2f9522ff332b1491e590" +dependencies = [ + "futures-util", + "http", + "hyper", + "log", + "rustls 0.21.12", + "rustls-native-certs 0.6.3", + "tokio", + "tokio-rustls 0.24.1", +] + +[[package]] +name = "iana-time-zone" +version = "0.1.61" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "235e081f3925a06703c2d0117ea8b91f042756fd6e7a6e5d901e8ca1a996b220" +dependencies = [ + "android_system_properties", + "core-foundation-sys", + "iana-time-zone-haiku", + "js-sys", + "wasm-bindgen", + "windows-core", +] + +[[package]] +name = "iana-time-zone-haiku" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f" +dependencies = [ + "cc", +] + +[[package]] +name = "idna" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "418a0a6fab821475f634efe3ccc45c013f742efe03d853e8d3355d5cb850ecf8" +dependencies = [ + "matches", + "unicode-bidi", + "unicode-normalization", +] + +[[package]] +name = "idna" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "634d9b1461af396cad843f47fdba5597a4f9e6ddd4bfb6ff5d85028c25cb12f6" +dependencies = [ + "unicode-bidi", + "unicode-normalization", +] + +[[package]] +name = "if-addrs" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cabb0019d51a643781ff15c9c8a3e5dedc365c47211270f4e8f82812fedd8f0a" +dependencies = [ + "libc", + "windows-sys 0.48.0", +] + +[[package]] +name = "if-watch" +version = "3.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d6b0422c86d7ce0e97169cc42e04ae643caf278874a7a3c87b8150a220dc7e1e" +dependencies = [ + "async-io", + "core-foundation", + "fnv", + "futures", + "if-addrs", + "ipnet", + "log", + "rtnetlink", + "system-configuration", + "tokio", + "windows", +] + +[[package]] +name = "impl-codec" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba6a270039626615617f3f36d15fc827041df3b78c439da2cadfa47455a77f2f" +dependencies = [ + "parity-scale-codec", +] + +[[package]] +name = "impl-num-traits" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "951641f13f873bff03d4bf19ae8bec531935ac0ac2cc775f84d7edfdcfed3f17" +dependencies = [ + "integer-sqrt", + "num-traits", + "uint", +] + +[[package]] +name = "impl-serde" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4551f042f3438e64dbd6226b20527fc84a6e1fe65688b58746a2f53623f25f5c" +dependencies = [ + "serde", +] + +[[package]] +name = "impl-serde" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebc88fc67028ae3db0c853baa36269d398d5f45b6982f95549ff5def78c935cd" +dependencies = [ + "serde", +] + +[[package]] +name = "impl-trait-for-tuples" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11d7a9f6330b71fea57921c9b61c47ee6e84f72d394754eff6163ae67e7395eb" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "include_dir" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "923d117408f1e49d914f1a379a309cffe4f18c05cf4e3d12e613a15fc81bd0dd" +dependencies = [ + "include_dir_macros", +] + +[[package]] +name = "include_dir_macros" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7cab85a7ed0bd5f0e76d93846e0147172bed2e2d3f859bcc33a8d9699cad1a75" +dependencies = [ + "proc-macro2", + "quote", +] + +[[package]] +name = "indexmap" +version = "1.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99" +dependencies = [ + "autocfg", + "hashbrown 0.12.3", + "serde", +] + +[[package]] +name = "indexmap" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68b900aa2f7301e21c36462b170ee99994de34dff39a4a6a528e80e7376d07e5" +dependencies = [ + "equivalent", + "hashbrown 0.14.5", +] + +[[package]] +name = "indicatif" +version = "0.17.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "763a5a8f45087d6bcea4222e7b72c291a054edf80e4ef6efd2a4979878c7bea3" +dependencies = [ + "console", + "instant", + "number_prefix", + "portable-atomic", + "unicode-width", +] + +[[package]] +name = "inout" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a0c10553d664a4d0bcff9f4215d0aac67a639cc68ef660840afe309b807bc9f5" +dependencies = [ + "generic-array 0.14.7", +] + +[[package]] +name = "instant" +version = "0.1.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e0242819d153cba4b4b05a5a8f2a7e9bbf97b6055b2a002b395c96b5ff3c0222" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "integer-sqrt" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "276ec31bcb4a9ee45f58bec6f9ec700ae4cf4f4f8f2fa7e06cb406bd5ffdd770" +dependencies = [ + "num-traits", +] + +[[package]] +name = "io-lifetimes" +version = "1.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eae7b9aee968036d54dce06cebaefd919e4472e753296daccd6d344e3e2df0c2" +dependencies = [ + "hermit-abi 0.3.9", + "libc", + "windows-sys 0.48.0", +] + +[[package]] +name = "ip_network" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa2f047c0a98b2f299aa5d6d7088443570faae494e9ae1305e48be000c9e0eb1" + +[[package]] +name = "ipconfig" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b58db92f96b720de98181bbbe63c831e87005ab460c1bf306eb2622b4707997f" +dependencies = [ + "socket2 0.5.7", + "widestring", + "windows-sys 0.48.0", + "winreg", +] + +[[package]] +name = "ipnet" +version = "2.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "187674a687eed5fe42285b40c6291f9a01517d415fad1c3cbc6a9f778af7fcd4" + +[[package]] +name = "is-terminal" +version = "0.4.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "261f68e344040fbd0edea105bef17c66edf46f984ddb1115b775ce31be948f4b" +dependencies = [ + "hermit-abi 0.4.0", + "libc", + "windows-sys 0.52.0", +] + +[[package]] +name = "is_terminal_polyfill" +version = "1.70.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf" + +[[package]] +name = "itertools" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "284f18f85651fe11e8a991b2adb42cb078325c996ed026d994719efcfca1d54b" +dependencies = [ + "either", +] + +[[package]] +name = "itertools" +version = "0.10.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b0fd2260e829bddf4cb6ea802289de2f86d6a7a690192fbe91b3f46e0f2c8473" +dependencies = [ + "either", +] + +[[package]] +name = "itertools" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba291022dbbd398a455acf126c1e341954079855bc60dfdda641363bd6922569" +dependencies = [ + "either", +] + +[[package]] +name = "itertools" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "413ee7dfc52ee1a4949ceeb7dbc8a33f2d6c088194d9f922fb8318faf1f01186" +dependencies = [ + "either", +] + +[[package]] +name = "itoa" +version = "1.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b" + +[[package]] +name = "jobserver" +version = "0.1.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "48d1dbcbbeb6a7fec7e059840aa538bd62aaccf972c7346c4d9d2059312853d0" +dependencies = [ + "libc", +] + +[[package]] +name = "js-sys" +version = "0.3.70" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1868808506b929d7b0cfa8f75951347aa71bb21144b7791bae35d9bccfcfe37a" +dependencies = [ + "wasm-bindgen", +] + +[[package]] +name = "jsonrpsee" +version = "0.22.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cfdb12a2381ea5b2e68c3469ec604a007b367778cdb14d09612c8069ebd616ad" +dependencies = [ + "jsonrpsee-client-transport", + "jsonrpsee-core", + "jsonrpsee-http-client", + "jsonrpsee-proc-macros", + "jsonrpsee-server", + "jsonrpsee-types", + "jsonrpsee-wasm-client", + "jsonrpsee-ws-client", + "tokio", + "tracing", +] + +[[package]] +name = "jsonrpsee-client-transport" +version = "0.22.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4978087a58c3ab02efc5b07c5e5e2803024536106fd5506f558db172c889b3aa" +dependencies = [ + "futures-channel", + "futures-util", + "gloo-net", + "http", + "jsonrpsee-core", + "pin-project", + "rustls-native-certs 0.7.3", + "rustls-pki-types", + "soketto", + "thiserror", + "tokio", + "tokio-rustls 0.25.0", + "tokio-util", + "tracing", + "url", + "webpki-roots 0.26.5", +] + +[[package]] +name = "jsonrpsee-core" +version = "0.22.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4b257e1ec385e07b0255dde0b933f948b5c8b8c28d42afda9587c3a967b896d" +dependencies = [ + "anyhow", + "async-trait", + "beef", + "futures-timer", + "futures-util", + "hyper", + "jsonrpsee-types", + "parking_lot 0.12.3", + "pin-project", + "rand 0.8.5", + "rustc-hash", + "serde", + "serde_json", + "thiserror", + "tokio", + "tokio-stream", + "tracing", + "wasm-bindgen-futures", +] + +[[package]] +name = "jsonrpsee-http-client" +version = "0.22.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ccf93fc4a0bfe05d851d37d7c32b7f370fe94336b52a2f0efc5f1981895c2e5" +dependencies = [ + "async-trait", + "hyper", + "hyper-rustls", + "jsonrpsee-core", + "jsonrpsee-types", + "serde", + "serde_json", + "thiserror", + "tokio", + "tower", + "tracing", + "url", +] + +[[package]] +name = "jsonrpsee-proc-macros" +version = "0.22.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7d0bb047e79a143b32ea03974a6bf59b62c2a4c5f5d42a381c907a8bbb3f75c0" +dependencies = [ + "heck 0.4.1", + "proc-macro-crate 3.2.0", + "proc-macro2", + "quote", + "syn 2.0.77", +] + +[[package]] +name = "jsonrpsee-server" +version = "0.22.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "12d8b6a9674422a8572e0b0abb12feeb3f2aeda86528c80d0350c2bd0923ab41" +dependencies = [ + "futures-util", + "http", + "hyper", + "jsonrpsee-core", + "jsonrpsee-types", + "pin-project", + "route-recognizer", + "serde", + "serde_json", + "soketto", + "thiserror", + "tokio", + "tokio-stream", + "tokio-util", + "tower", + "tracing", +] + +[[package]] +name = "jsonrpsee-types" +version = "0.22.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "150d6168405890a7a3231a3c74843f58b8959471f6df76078db2619ddee1d07d" +dependencies = [ + "anyhow", + "beef", + "serde", + "serde_json", + "thiserror", +] + +[[package]] +name = "jsonrpsee-wasm-client" +version = "0.22.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f448d8eacd945cc17b6c0b42c361531ca36a962ee186342a97cdb8fca679cd77" +dependencies = [ + "jsonrpsee-client-transport", + "jsonrpsee-core", + "jsonrpsee-types", +] + +[[package]] +name = "jsonrpsee-ws-client" +version = "0.22.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "58b9db2dfd5bb1194b0ce921504df9ceae210a345bc2f6c5a61432089bbab070" +dependencies = [ + "http", + "jsonrpsee-client-transport", + "jsonrpsee-core", + "jsonrpsee-types", + "url", +] + +[[package]] +name = "k256" +version = "0.13.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "956ff9b67e26e1a6a866cb758f12c6f8746208489e3e4a4b5580802f2f0a587b" +dependencies = [ + "cfg-if", + "ecdsa", + "elliptic-curve", + "once_cell", + "serdect", + "sha2 0.10.8", +] + +[[package]] +name = "keccak" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ecc2af9a1119c51f12a14607e783cb977bde58bc069ff0c3da1095e635d70654" +dependencies = [ + "cpufeatures", +] + +[[package]] +name = "keccak-asm" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "505d1856a39b200489082f90d897c3f07c455563880bc5952e38eabf731c83b6" +dependencies = [ + "digest 0.10.7", + "sha3-asm", +] + +[[package]] +name = "keystream" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c33070833c9ee02266356de0c43f723152bd38bd96ddf52c82b3af10c9138b28" + +[[package]] +name = "kvdb" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7d770dcb02bf6835887c3a979b5107a04ff4bbde97a5f0928d27404a155add9" +dependencies = [ + "smallvec", +] + +[[package]] +name = "kvdb-memorydb" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf7a85fe66f9ff9cd74e169fdd2c94c6e1e74c412c99a73b4df3200b5d3760b2" +dependencies = [ + "kvdb", + "parking_lot 0.12.3", +] + +[[package]] +name = "kvdb-rocksdb" +version = "0.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b644c70b92285f66bfc2032922a79000ea30af7bc2ab31902992a5dcb9b434f6" +dependencies = [ + "kvdb", + "num_cpus", + "parking_lot 0.12.3", + "regex", + "rocksdb", + "smallvec", +] + +[[package]] +name = "lazy_static" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" + +[[package]] +name = "lazycell" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55" + +[[package]] +name = "libc" +version = "0.2.158" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d8adc4bb1803a324070e64a98ae98f38934d91957a99cfb3a43dcbc01bc56439" + +[[package]] +name = "libloading" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4979f22fdb869068da03c9f7528f8297c6fd2606bc3a4affe42e6a823fdb8da4" +dependencies = [ + "cfg-if", + "windows-targets 0.52.6", +] + +[[package]] +name = "libm" +version = "0.2.1" +source = "git+https://github.com/rust-lang/libm?rev=2f3fc968f43d345f9b449938d050a9ea46a04c83#2f3fc968f43d345f9b449938d050a9ea46a04c83" + +[[package]] +name = "libm" +version = "0.2.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ec2a862134d2a7d32d7983ddcdd1c4923530833c9f2ea1a44fc5fa473989058" + +[[package]] +name = "libp2p" +version = "0.51.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f35eae38201a993ece6bdc823292d6abd1bffed1c4d0f4a3517d2bd8e1d917fe" +dependencies = [ + "bytes", + "futures", + "futures-timer", + "getrandom 0.2.15", + "instant", + "libp2p-allow-block-list", + "libp2p-connection-limits", + "libp2p-core", + "libp2p-dns", + "libp2p-identify", + "libp2p-identity", + "libp2p-kad", + "libp2p-mdns", + "libp2p-metrics", + "libp2p-noise", + "libp2p-ping", + "libp2p-quic", + "libp2p-request-response", + "libp2p-swarm", + "libp2p-tcp", + "libp2p-wasm-ext", + "libp2p-websocket", + "libp2p-yamux", + "multiaddr", + "pin-project", +] + +[[package]] +name = "libp2p-allow-block-list" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "510daa05efbc25184458db837f6f9a5143888f1caa742426d92e1833ddd38a50" +dependencies = [ + "libp2p-core", + "libp2p-identity", + "libp2p-swarm", + "void", +] + +[[package]] +name = "libp2p-connection-limits" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4caa33f1d26ed664c4fe2cca81a08c8e07d4c1c04f2f4ac7655c2dd85467fda0" +dependencies = [ + "libp2p-core", + "libp2p-identity", + "libp2p-swarm", + "void", +] + +[[package]] +name = "libp2p-core" +version = "0.39.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c1df63c0b582aa434fb09b2d86897fa2b419ffeccf934b36f87fcedc8e835c2" +dependencies = [ + "either", + "fnv", + "futures", + "futures-timer", + "instant", + "libp2p-identity", + "log", + "multiaddr", + "multihash", + "multistream-select", + "once_cell", + "parking_lot 0.12.3", + "pin-project", + "quick-protobuf", + "rand 0.8.5", + "rw-stream-sink", + "smallvec", + "thiserror", + "unsigned-varint", + "void", +] + +[[package]] +name = "libp2p-dns" +version = "0.39.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "146ff7034daae62077c415c2376b8057368042df6ab95f5432ad5e88568b1554" +dependencies = [ + "futures", + "libp2p-core", + "log", + "parking_lot 0.12.3", + "smallvec", + "trust-dns-resolver", +] + +[[package]] +name = "libp2p-identify" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5455f472243e63b9c497ff320ded0314254a9eb751799a39c283c6f20b793f3c" +dependencies = [ + "asynchronous-codec", + "either", + "futures", + "futures-timer", + "libp2p-core", + "libp2p-identity", + "libp2p-swarm", + "log", + "lru", + "quick-protobuf", + "quick-protobuf-codec", + "smallvec", + "thiserror", + "void", +] + +[[package]] +name = "libp2p-identity" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "276bb57e7af15d8f100d3c11cbdd32c6752b7eef4ba7a18ecf464972c07abcce" +dependencies = [ + "bs58 0.4.0", + "ed25519-dalek", + "log", + "multiaddr", + "multihash", + "quick-protobuf", + "rand 0.8.5", + "sha2 0.10.8", + "thiserror", + "zeroize", +] + +[[package]] +name = "libp2p-kad" +version = "0.43.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "39d5ef876a2b2323d63c258e63c2f8e36f205fe5a11f0b3095d59635650790ff" +dependencies = [ + "arrayvec 0.7.6", + "asynchronous-codec", + "bytes", + "either", + "fnv", + "futures", + "futures-timer", + "instant", + "libp2p-core", + "libp2p-identity", + "libp2p-swarm", + "log", + "quick-protobuf", + "rand 0.8.5", + "sha2 0.10.8", + "smallvec", + "thiserror", + "uint", + "unsigned-varint", + "void", +] + +[[package]] +name = "libp2p-mdns" +version = "0.43.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19983e1f949f979a928f2c603de1cf180cc0dc23e4ac93a62651ccb18341460b" +dependencies = [ + "data-encoding", + "futures", + "if-watch", + "libp2p-core", + "libp2p-identity", + "libp2p-swarm", + "log", + "rand 0.8.5", + "smallvec", + "socket2 0.4.10", + "tokio", + "trust-dns-proto", + "void", +] + +[[package]] +name = "libp2p-metrics" +version = "0.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a42ec91e227d7d0dafa4ce88b333cdf5f277253873ab087555c92798db2ddd46" +dependencies = [ + "libp2p-core", + "libp2p-identify", + "libp2p-kad", + "libp2p-ping", + "libp2p-swarm", + "prometheus-client", +] + +[[package]] +name = "libp2p-noise" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c3673da89d29936bc6435bafc638e2f184180d554ce844db65915113f86ec5e" +dependencies = [ + "bytes", + "curve25519-dalek 3.2.0", + "futures", + "libp2p-core", + "libp2p-identity", + "log", + "once_cell", + "quick-protobuf", + "rand 0.8.5", + "sha2 0.10.8", + "snow", + "static_assertions", + "thiserror", + "x25519-dalek 1.1.1", + "zeroize", +] + +[[package]] +name = "libp2p-ping" +version = "0.42.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3e57759c19c28a73ef1eb3585ca410cefb72c1a709fcf6de1612a378e4219202" +dependencies = [ + "either", + "futures", + "futures-timer", + "instant", + "libp2p-core", + "libp2p-swarm", + "log", + "rand 0.8.5", + "void", +] + +[[package]] +name = "libp2p-quic" +version = "0.7.0-alpha.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c6b26abd81cd2398382a1edfe739b539775be8a90fa6914f39b2ab49571ec735" +dependencies = [ + "bytes", + "futures", + "futures-timer", + "if-watch", + "libp2p-core", + "libp2p-identity", + "libp2p-tls", + "log", + "parking_lot 0.12.3", + "quinn-proto", + "rand 0.8.5", + "rustls 0.20.9", + "thiserror", + "tokio", +] + +[[package]] +name = "libp2p-request-response" +version = "0.24.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ffdb374267d42dc5ed5bc53f6e601d4a64ac5964779c6e40bb9e4f14c1e30d5" +dependencies = [ + "async-trait", + "futures", + "instant", + "libp2p-core", + "libp2p-identity", + "libp2p-swarm", + "rand 0.8.5", + "smallvec", +] + +[[package]] +name = "libp2p-swarm" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "903b3d592d7694e56204d211f29d31bc004be99386644ba8731fc3e3ef27b296" +dependencies = [ + "either", + "fnv", + "futures", + "futures-timer", + "instant", + "libp2p-core", + "libp2p-identity", + "libp2p-swarm-derive", + "log", + "rand 0.8.5", + "smallvec", + "tokio", + "void", +] + +[[package]] +name = "libp2p-swarm-derive" +version = "0.32.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fba456131824ab6acd4c7bf61e9c0f0a3014b5fc9868ccb8e10d344594cdc4f" +dependencies = [ + "heck 0.4.1", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "libp2p-tcp" +version = "0.39.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "33d33698596d7722d85d3ab0c86c2c322254fce1241e91208e3679b4eb3026cf" +dependencies = [ + "futures", + "futures-timer", + "if-watch", + "libc", + "libp2p-core", + "log", + "socket2 0.4.10", + "tokio", +] + +[[package]] +name = "libp2p-tls" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff08d13d0dc66e5e9ba6279c1de417b84fa0d0adc3b03e5732928c180ec02781" +dependencies = [ + "futures", + "futures-rustls", + "libp2p-core", + "libp2p-identity", + "rcgen", + "ring 0.16.20", + "rustls 0.20.9", + "thiserror", + "webpki", + "x509-parser", + "yasna", +] + +[[package]] +name = "libp2p-wasm-ext" +version = "0.39.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77dff9d32353a5887adb86c8afc1de1a94d9e8c3bc6df8b2201d7cdf5c848f43" +dependencies = [ + "futures", + "js-sys", + "libp2p-core", + "parity-send-wrapper", + "wasm-bindgen", + "wasm-bindgen-futures", +] + +[[package]] +name = "libp2p-websocket" +version = "0.41.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "111273f7b3d3510524c752e8b7a5314b7f7a1fee7e68161c01a7d72cbb06db9f" +dependencies = [ + "either", + "futures", + "futures-rustls", + "libp2p-core", + "log", + "parking_lot 0.12.3", + "quicksink", + "rw-stream-sink", + "soketto", + "url", + "webpki-roots 0.22.6", +] + +[[package]] +name = "libp2p-yamux" +version = "0.43.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4dcd21d950662700a385d4c6d68e2f5f54d778e97068cdd718522222ef513bda" +dependencies = [ + "futures", + "libp2p-core", + "log", + "thiserror", + "yamux", +] + +[[package]] +name = "libredox" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0ff37bd590ca25063e35af745c343cb7a0271906fb7b37e4813e8f79f00268d" +dependencies = [ + "bitflags 2.6.0", + "libc", + "redox_syscall 0.5.4", +] + +[[package]] +name = "librocksdb-sys" +version = "0.11.0+8.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3386f101bcb4bd252d8e9d2fb41ec3b0862a15a62b478c355b2982efa469e3e" +dependencies = [ + "bindgen", + "bzip2-sys", + "cc", + "glob", + "libc", + "libz-sys", + "tikv-jemalloc-sys", +] + +[[package]] +name = "libsecp256k1" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95b09eff1b35ed3b33b877ced3a691fc7a481919c7e29c53c906226fcf55e2a1" +dependencies = [ + "arrayref", + "base64 0.13.1", + "digest 0.9.0", + "hmac-drbg", + "libsecp256k1-core", + "libsecp256k1-gen-ecmult", + "libsecp256k1-gen-genmult", + "rand 0.8.5", + "serde", + "sha2 0.9.9", + "typenum", +] + +[[package]] +name = "libsecp256k1-core" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5be9b9bb642d8522a44d533eab56c16c738301965504753b03ad1de3425d5451" +dependencies = [ + "crunchy", + "digest 0.9.0", + "subtle 2.6.1", +] + +[[package]] +name = "libsecp256k1-gen-ecmult" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3038c808c55c87e8a172643a7d87187fc6c4174468159cb3090659d55bcb4809" +dependencies = [ + "libsecp256k1-core", +] + +[[package]] +name = "libsecp256k1-gen-genmult" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3db8d6ba2cec9eacc40e6e8ccc98931840301f1006e95647ceb2dd5c3aa06f7c" +dependencies = [ + "libsecp256k1-core", +] + +[[package]] +name = "libz-sys" +version = "1.1.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2d16453e800a8cf6dd2fc3eb4bc99b786a9b90c663b8559a5b1a041bf89e472" +dependencies = [ + "cc", + "pkg-config", + "vcpkg", +] + +[[package]] +name = "link-cplusplus" +version = "1.0.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d240c6f7e1ba3a28b0249f774e6a9dd0175054b52dfbb61b16eb8505c3785c9" +dependencies = [ + "cc", +] + +[[package]] +name = "linked-hash-map" +version = "0.5.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0717cef1bc8b636c6e1c1bbdefc09e6322da8a9321966e8928ef80d20f7f770f" + +[[package]] +name = "linked_hash_set" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "47186c6da4d81ca383c7c47c1bfc80f4b95f4720514d860a5407aaf4233f9588" +dependencies = [ + "linked-hash-map", +] + +[[package]] +name = "linregress" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4de04dcecc58d366391f9920245b85ffa684558a5ef6e7736e754347c3aea9c2" +dependencies = [ + "nalgebra", +] + +[[package]] +name = "linux-raw-sys" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f051f77a7c8e6957c0696eac88f26b0117e54f52d3fc682ab19397a8812846a4" + +[[package]] +name = "linux-raw-sys" +version = "0.4.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78b3ae25bc7c8c38cec158d1f2757ee79e9b3740fbc7ccf0e59e4b08d793fa89" + +[[package]] +name = "lioness" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ae926706ba42c425c9457121178330d75e273df2e82e28b758faf3de3a9acb9" +dependencies = [ + "arrayref", + "blake2 0.8.1", + "chacha", + "keystream", +] + +[[package]] +name = "lock_api" +version = "0.4.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07af8b9cdd281b7915f413fa73f29ebd5d55d0d3f0155584dade1ff18cea1b17" +dependencies = [ + "autocfg", + "scopeguard", +] + +[[package]] +name = "log" +version = "0.4.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24" + +[[package]] +name = "lru" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "718e8fae447df0c7e1ba7f5189829e63fd536945c8988d61444c19039f16b670" +dependencies = [ + "hashbrown 0.13.2", +] + +[[package]] +name = "lru-cache" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "31e24f1ad8321ca0e8a1e0ac13f23cb668e6f5466c2c57319f6a5cf1cc8e3b1c" +dependencies = [ + "linked-hash-map", +] + +[[package]] +name = "lz4" +version = "1.27.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a231296ca742e418c43660cb68e082486ff2538e8db432bc818580f3965025ed" +dependencies = [ + "lz4-sys", +] + +[[package]] +name = "lz4-sys" +version = "1.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fcb44a01837a858d47e5a630d2ccf304c8efcc4b83b8f9f75b7a9ee4fcc6e57d" +dependencies = [ + "cc", + "libc", +] + +[[package]] +name = "mach" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b823e83b2affd8f40a9ee8c29dbc56404c1e34cd2710921f2801e2cf29527afa" +dependencies = [ + "libc", +] + +[[package]] +name = "macro_magic" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc33f9f0351468d26fbc53d9ce00a096c8522ecb42f19b50f34f2c422f76d21d" +dependencies = [ + "macro_magic_core", + "macro_magic_macros", + "quote", + "syn 2.0.77", +] + +[[package]] +name = "macro_magic_core" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1687dc887e42f352865a393acae7cf79d98fab6351cde1f58e9e057da89bf150" +dependencies = [ + "const-random", + "derive-syn-parse 0.2.0", + "macro_magic_core_macros", + "proc-macro2", + "quote", + "syn 2.0.77", +] + +[[package]] +name = "macro_magic_core_macros" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b02abfe41815b5bd98dbd4260173db2c116dda171dc0fe7838cb206333b83308" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.77", +] + +[[package]] +name = "macro_magic_macros" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73ea28ee64b88876bf45277ed9a5817c1817df061a74f2b988971a12570e5869" +dependencies = [ + "macro_magic_core", + "quote", + "syn 2.0.77", +] + +[[package]] +name = "mangata-rpc-nonce" +version = "4.0.0-dev" +dependencies = [ + "assert_matches", + "frame-system-rpc-runtime-api", + "futures", + "jsonrpsee", + "log", + "parity-scale-codec", + "sc-client-api", + "sc-rpc-api", + "sc-transaction-pool", + "sc-transaction-pool-api", + "sp-api", + "sp-block-builder", + "sp-blockchain", + "sp-core", + "sp-runtime", + "sp-tracing", + "substrate-test-runtime-client", + "tokio", + "ver-api", +] + +[[package]] +name = "mangata-support" +version = "0.1.0" +source = "git+https://github.com/gasp-xyz/polkadot-sdk?branch=eth-rollup-develop#e5bbc9b3eaa4f69cd39000bb69f6963f36dd93b8" +dependencies = [ + "frame-support", + "mangata-types", + "parity-scale-codec", + "sp-core", + "sp-runtime", + "sp-std", +] + +[[package]] +name = "mangata-types" +version = "0.1.0" +source = "git+https://github.com/gasp-xyz/polkadot-sdk?branch=eth-rollup-develop#e5bbc9b3eaa4f69cd39000bb69f6963f36dd93b8" +dependencies = [ + "parity-scale-codec", + "scale-info", + "serde", + "sp-core", + "sp-runtime", + "sp-std", +] + +[[package]] +name = "maplit" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3e2e65a1a2e43cfcb47a895c4c8b10d1f4a61097f9f254f183aee60cad9c651d" + +[[package]] +name = "market-rpc" +version = "1.0.0" +dependencies = [ + "jsonrpsee", + "pallet-market", + "parity-scale-codec", + "serde", + "sp-api", + "sp-blockchain", + "sp-core", + "sp-rpc", + "sp-runtime", + "sp-std", +] + +[[package]] +name = "match_cfg" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ffbee8634e0d45d258acb448e7eaab3fce7a0a467395d4d9f228e3c1f01fb2e4" + +[[package]] +name = "matchers" +version = "0.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f099785f7595cc4b4553a174ce30dd7589ef93391ff414dbb67f62392b9e0ce1" +dependencies = [ + "regex-automata 0.1.10", +] + +[[package]] +name = "matches" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2532096657941c2fea9c289d370a250971c689d4f143798ff67113ec042024a5" + +[[package]] +name = "matrixmultiply" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9380b911e3e96d10c1f415da0876389aaf1b56759054eeb0de7df940c456ba1a" +dependencies = [ + "autocfg", + "rawpointer", +] + +[[package]] +name = "memchr" +version = "2.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" + +[[package]] +name = "memfd" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2cffa4ad52c6f791f4f8b15f0c05f9824b2ced1160e88cc393d64fff9a8ac64" +dependencies = [ + "rustix 0.38.37", +] + +[[package]] +name = "memmap2" +version = "0.5.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "83faa42c0a078c393f6b29d5db232d8be22776a891f8f56e5284faee4a20b327" +dependencies = [ + "libc", +] + +[[package]] +name = "memmap2" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fd3f7eed9d3848f8b98834af67102b720745c4ec028fcd0aa0239277e7de374f" +dependencies = [ + "libc", +] + +[[package]] +name = "memoffset" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d61c719bcfbcf5d62b3a09efa6088de8c54bc0bfcd3ea7ae39fcc186108b8de1" +dependencies = [ + "autocfg", +] + +[[package]] +name = "memory-db" +version = "0.32.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "808b50db46293432a45e63bc15ea51e0ab4c0a1647b8eb114e31a3e698dd6fbe" +dependencies = [ + "hash-db", +] + +[[package]] +name = "merlin" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4e261cf0f8b3c42ded9f7d2bb59dea03aa52bc8a1cbc7482f9fc3fd1229d3b42" +dependencies = [ + "byteorder", + "keccak", + "rand_core 0.5.1", + "zeroize", +] + +[[package]] +name = "merlin" +version = "3.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "58c38e2799fc0978b65dfff8023ec7843e2330bb462f19198840b34b6582397d" +dependencies = [ + "byteorder", + "keccak", + "rand_core 0.6.4", + "zeroize", +] + +[[package]] +name = "metamask-signature-rpc" +version = "2.0.0" +dependencies = [ + "array-bytes 6.2.3", + "jsonrpsee", + "mangata-types", + "metamask-signature-runtime-api", + "parity-scale-codec", + "serde", + "sp-api", + "sp-blockchain", + "sp-core", + "sp-rpc", + "sp-runtime", + "sp-std", +] + +[[package]] +name = "metamask-signature-runtime-api" +version = "2.0.0" +dependencies = [ + "parity-scale-codec", + "sp-api", + "sp-std", +] + +[[package]] +name = "minimal-lexical" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" + +[[package]] +name = "miniz_oxide" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2d80299ef12ff69b16a84bb182e3b9df68b5a91574d3d4fa6e41b65deec4df1" +dependencies = [ + "adler2", +] + +[[package]] +name = "mio" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "80e04d1dcff3aae0704555fe5fee3bcfaf3d1fdf8a7e521d5b9d2b42acb52cec" +dependencies = [ + "hermit-abi 0.3.9", + "libc", + "wasi 0.11.0+wasi-snapshot-preview1", + "windows-sys 0.52.0", +] + +[[package]] +name = "mixnet" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "daa3eb39495d8e2e2947a1d862852c90cc6a4a8845f8b41c8829cb9fcc047f4a" +dependencies = [ + "arrayref", + "arrayvec 0.7.6", + "bitflags 1.3.2", + "blake2 0.10.6", + "c2-chacha", + "curve25519-dalek 4.1.1", + "either", + "hashlink", + "lioness", + "log", + "parking_lot 0.12.3", + "rand 0.8.5", + "rand_chacha 0.3.1", + "rand_distr", + "subtle 2.6.1", + "thiserror", + "zeroize", +] + +[[package]] +name = "mockall" +version = "0.11.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c84490118f2ee2d74570d114f3d0493cbf02790df303d2707606c3e14e07c96" +dependencies = [ + "cfg-if", + "downcast", + "fragile", + "lazy_static", + "mockall_derive", + "predicates", + "predicates-tree", +] + +[[package]] +name = "mockall_derive" +version = "0.11.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22ce75669015c4f47b289fd4d4f56e894e4c96003ffdf3ac51313126f94c6cbb" +dependencies = [ + "cfg-if", + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "multiaddr" +version = "0.17.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b36f567c7099511fa8612bbbb52dda2419ce0bdbacf31714e3a5ffdb766d3bd" +dependencies = [ + "arrayref", + "byteorder", + "data-encoding", + "log", + "multibase", + "multihash", + "percent-encoding", + "serde", + "static_assertions", + "unsigned-varint", + "url", +] + +[[package]] +name = "multibase" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b3539ec3c1f04ac9748a260728e855f261b4977f5c3406612c884564f329404" +dependencies = [ + "base-x", + "data-encoding", + "data-encoding-macro", +] + +[[package]] +name = "multihash" +version = "0.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "835d6ff01d610179fbce3de1694d007e500bf33a7f29689838941d6bf783ae40" +dependencies = [ + "blake2b_simd", + "blake2s_simd", + "blake3", + "core2", + "digest 0.10.7", + "multihash-derive", + "sha2 0.10.8", + "sha3", + "unsigned-varint", +] + +[[package]] +name = "multihash-derive" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d6d4752e6230d8ef7adf7bd5d8c4b1f6561c1014c5ba9a37445ccefe18aa1db" +dependencies = [ + "proc-macro-crate 1.1.3", + "proc-macro-error", + "proc-macro2", + "quote", + "syn 1.0.109", + "synstructure", +] + +[[package]] +name = "multimap" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5ce46fe64a9d73be07dcbe690a38ce1b293be448fd8ce1e6c1b8062c9f72c6a" + +[[package]] +name = "multistream-select" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8552ab875c1313b97b8d20cb857b9fd63e2d1d6a0a1b53ce9821e575405f27a" +dependencies = [ + "bytes", + "futures", + "log", + "pin-project", + "smallvec", + "unsigned-varint", +] + +[[package]] +name = "nalgebra" +version = "0.32.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b5c17de023a86f59ed79891b2e5d5a94c705dbe904a5b5c9c952ea6221b03e4" +dependencies = [ + "approx", + "matrixmultiply", + "nalgebra-macros", + "num-complex", + "num-rational", + "num-traits", + "simba", + "typenum", +] + +[[package]] +name = "nalgebra-macros" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "254a5372af8fc138e36684761d3c0cdb758a4410e938babcff1c860ce14ddbfc" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.77", +] + +[[package]] +name = "names" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7bddcd3bf5144b6392de80e04c347cd7fab2508f6df16a85fc496ecd5cec39bc" +dependencies = [ + "rand 0.8.5", +] + +[[package]] +name = "netlink-packet-core" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "345b8ab5bd4e71a2986663e88c56856699d060e78e152e6e9d7966fcd5491297" +dependencies = [ + "anyhow", + "byteorder", + "libc", + "netlink-packet-utils", +] + +[[package]] +name = "netlink-packet-route" +version = "0.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9ea4302b9759a7a88242299225ea3688e63c85ea136371bb6cf94fd674efaab" +dependencies = [ + "anyhow", + "bitflags 1.3.2", + "byteorder", + "libc", + "netlink-packet-core", + "netlink-packet-utils", +] + +[[package]] +name = "netlink-packet-utils" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ede8a08c71ad5a95cdd0e4e52facd37190977039a4704eb82a283f713747d34" +dependencies = [ + "anyhow", + "byteorder", + "paste", + "thiserror", +] + +[[package]] +name = "netlink-proto" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "65b4b14489ab424703c092062176d52ba55485a89c076b4f9db05092b7223aa6" +dependencies = [ + "bytes", + "futures", + "log", + "netlink-packet-core", + "netlink-sys", + "thiserror", + "tokio", +] + +[[package]] +name = "netlink-sys" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "416060d346fbaf1f23f9512963e3e878f1a78e707cb699ba9215761754244307" +dependencies = [ + "bytes", + "futures", + "libc", + "log", + "tokio", +] + +[[package]] +name = "nix" +version = "0.24.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa52e972a9a719cecb6864fb88568781eb706bac2cd1d4f04a648542dbf78069" +dependencies = [ + "bitflags 1.3.2", + "cfg-if", + "libc", +] + +[[package]] +name = "no-std-compat" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b93853da6d84c2e3c7d730d6473e8817692dd89be387eb01b94d7f108ecb5b8c" + +[[package]] +name = "nodrop" +version = "0.1.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72ef4a56884ca558e5ddb05a1d1e7e1bfd9a68d9ed024c21704cc98872dae1bb" + +[[package]] +name = "nohash-hasher" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2bf50223579dc7cdcfb3bfcacf7069ff68243f8c363f62ffa99cf000a6b9c451" + +[[package]] +name = "nom" +version = "7.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a" +dependencies = [ + "memchr", + "minimal-lexical", +] + +[[package]] +name = "nonzero_ext" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38bf9645c8b145698bb0b18a4637dcacbc421ea49bef2317e4fd8065a387cf21" + +[[package]] +name = "normalize-line-endings" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "61807f77802ff30975e01f4f071c8ba10c022052f98b3294119f3e615d13e5be" + +[[package]] +name = "num-bigint" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a5e44f723f1133c9deac646763579fdb3ac745e418f2a7af9cd0c431da1f20b9" +dependencies = [ + "num-integer", + "num-traits", +] + +[[package]] +name = "num-complex" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73f88a1307638156682bada9d7604135552957b7818057dcef22705b4d509495" +dependencies = [ + "num-traits", +] + +[[package]] +name = "num-conv" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9" + +[[package]] +name = "num-format" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a652d9771a63711fd3c3deb670acfbe5c30a4072e664d7a3bf5a9e1056ac72c3" +dependencies = [ + "arrayvec 0.7.6", + "itoa", +] + +[[package]] +name = "num-integer" +version = "0.1.46" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f" +dependencies = [ + "num-traits", +] + +[[package]] +name = "num-rational" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f83d14da390562dca69fc84082e73e548e1ad308d24accdedd2720017cb37824" +dependencies = [ + "num-integer", + "num-traits", +] + +[[package]] +name = "num-traits" +version = "0.2.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" +dependencies = [ + "autocfg", + "libm 0.2.8", +] + +[[package]] +name = "num_cpus" +version = "1.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4161fcb6d602d4d2081af7c3a45852d875a03dd337a6bfdd6e06407b61342a43" +dependencies = [ + "hermit-abi 0.3.9", + "libc", +] + +[[package]] +name = "number_prefix" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "830b246a0e5f20af87141b25c173cd1b609bd7779a4617d6ec582abaf90870f3" + +[[package]] +name = "object" +version = "0.30.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "03b4680b86d9cfafba8fc491dc9b6df26b68cf40e9e6cd73909194759a63c385" +dependencies = [ + "crc32fast", + "hashbrown 0.13.2", + "indexmap 1.9.3", + "memchr", +] + +[[package]] +name = "object" +version = "0.32.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a6a622008b6e321afc04970976f62ee297fdbaa6f95318ca343e3eebb9648441" +dependencies = [ + "memchr", +] + +[[package]] +name = "object" +version = "0.36.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "084f1a5821ac4c651660a94a7153d27ac9d8a53736203f58b31945ded098070a" +dependencies = [ + "memchr", +] + +[[package]] +name = "oid-registry" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9bedf36ffb6ba96c2eb7144ef6270557b52e54b20c0a8e1eb2ff99a6c6959bff" +dependencies = [ + "asn1-rs", +] + +[[package]] +name = "once_cell" +version = "1.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" + +[[package]] +name = "opaque-debug" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2839e79665f131bdb5782e51f2c6c9599c133c6098982a54c794358bf432529c" + +[[package]] +name = "opaque-debug" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c08d65885ee38876c4f86fa503fb49d7b507c2b62552df7c70b2fce627e06381" + +[[package]] +name = "openssl-probe" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf" + +[[package]] +name = "option-ext" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d" + +[[package]] +name = "orml-asset-registry" +version = "0.9.0" +source = "git+https://github.com/gasp-xyz/open-runtime-module-library?branch=eth-rollup-develop#2cae70d92e2b8bb352b8e73612cb2e114db2ed3f" +dependencies = [ + "frame-benchmarking", + "frame-support", + "frame-system", + "log", + "mangata-types", + "orml-traits", + "parity-scale-codec", + "scale-info", + "serde", + "sp-io", + "sp-runtime", + "sp-std", +] + +[[package]] +name = "orml-tokens" +version = "0.9.0" +source = "git+https://github.com/gasp-xyz/open-runtime-module-library?branch=eth-rollup-develop#2cae70d92e2b8bb352b8e73612cb2e114db2ed3f" +dependencies = [ + "frame-benchmarking", + "frame-support", + "frame-system", + "log", + "orml-traits", + "parity-scale-codec", + "scale-info", + "serde", + "sp-arithmetic", + "sp-core", + "sp-runtime", + "sp-std", +] + +[[package]] +name = "orml-traits" +version = "0.9.0" +source = "git+https://github.com/gasp-xyz/open-runtime-module-library?branch=eth-rollup-develop#2cae70d92e2b8bb352b8e73612cb2e114db2ed3f" +dependencies = [ + "frame-support", + "impl-trait-for-tuples", + "num-traits", + "orml-utilities", + "parity-scale-codec", + "paste", + "scale-info", + "serde", + "sp-core", + "sp-io", + "sp-runtime", + "sp-std", +] + +[[package]] +name = "orml-utilities" +version = "0.9.0" +source = "git+https://github.com/gasp-xyz/open-runtime-module-library?branch=eth-rollup-develop#2cae70d92e2b8bb352b8e73612cb2e114db2ed3f" +dependencies = [ + "frame-support", + "parity-scale-codec", + "scale-info", + "serde", + "sp-core", + "sp-io", + "sp-runtime", + "sp-std", +] + +[[package]] +name = "pallet-aura" +version = "27.0.0" +source = "git+https://github.com/gasp-xyz/polkadot-sdk?branch=eth-rollup-develop#e5bbc9b3eaa4f69cd39000bb69f6963f36dd93b8" +dependencies = [ + "frame-support", + "frame-system", + "log", + "pallet-timestamp", + "parity-scale-codec", + "scale-info", + "sp-application-crypto", + "sp-consensus-aura", + "sp-runtime", + "sp-std", +] + +[[package]] +name = "pallet-authorship" +version = "28.0.0" +source = "git+https://github.com/gasp-xyz/polkadot-sdk?branch=eth-rollup-develop#e5bbc9b3eaa4f69cd39000bb69f6963f36dd93b8" +dependencies = [ + "frame-support", + "frame-system", + "impl-trait-for-tuples", + "log", + "parity-scale-codec", + "scale-info", + "sp-runtime", + "sp-std", +] + +[[package]] +name = "pallet-babe" +version = "28.0.0" +source = "git+https://github.com/gasp-xyz/polkadot-sdk?branch=eth-rollup-develop#e5bbc9b3eaa4f69cd39000bb69f6963f36dd93b8" +dependencies = [ + "frame-benchmarking", + "frame-support", + "frame-system", + "log", + "pallet-authorship", + "pallet-session", + "pallet-timestamp", + "parity-scale-codec", + "scale-info", + "sp-application-crypto", + "sp-consensus-babe", + "sp-core", + "sp-io", + "sp-runtime", + "sp-session", + "sp-staking", + "sp-std", +] + +[[package]] +name = "pallet-balances" +version = "28.0.0" +source = "git+https://github.com/gasp-xyz/polkadot-sdk?branch=eth-rollup-develop#e5bbc9b3eaa4f69cd39000bb69f6963f36dd93b8" +dependencies = [ + "docify", + "frame-benchmarking", + "frame-support", + "frame-system", + "log", + "parity-scale-codec", + "scale-info", + "sp-runtime", + "sp-std", +] + +[[package]] +name = "pallet-bootstrap" +version = "0.1.0" +dependencies = [ + "frame-benchmarking", + "frame-executive", + "frame-support", + "frame-system", + "frame-try-runtime", + "lazy_static", + "log", + "mangata-support", + "mangata-types", + "mockall", + "orml-tokens", + "orml-traits", + "pallet-issuance", + "pallet-proof-of-stake", + "pallet-vesting-mangata", + "pallet-xyk", + "parity-scale-codec", + "scale-info", + "serde", + "serial_test", + "sp-arithmetic", + "sp-core", + "sp-io", + "sp-runtime", + "sp-std", + "test-case", +] + +[[package]] +name = "pallet-collective-mangata" +version = "28.0.0" +source = "git+https://github.com/gasp-xyz/polkadot-sdk?branch=eth-rollup-develop#e5bbc9b3eaa4f69cd39000bb69f6963f36dd93b8" +dependencies = [ + "frame-benchmarking", + "frame-support", + "frame-system", + "log", + "parity-scale-codec", + "scale-info", + "sp-core", + "sp-io", + "sp-runtime", + "sp-std", +] + +[[package]] +name = "pallet-crowdloan-rewards" +version = "0.6.0" +dependencies = [ + "frame-benchmarking", + "frame-support", + "frame-system", + "log", + "mangata-types", + "orml-tokens", + "orml-traits", + "pallet-utility", + "pallet-vesting-mangata", + "parity-scale-codec", + "scale-info", + "serde", + "sp-application-crypto", + "sp-core", + "sp-io", + "sp-keystore", + "sp-runtime", + "sp-std", + "sp-trie", +] + +[[package]] +name = "pallet-fee-lock" +version = "0.1.0" +dependencies = [ + "env_logger 0.9.3", + "frame-benchmarking", + "frame-support", + "frame-system", + "hex", + "hex-literal 0.3.4", + "lazy_static", + "log", + "mangata-support", + "mangata-types", + "mockall", + "orml-tokens", + "orml-traits", + "pallet-xyk", + "parity-scale-codec", + "scale-info", + "serde", + "serial_test", + "sp-arithmetic", + "sp-core", + "sp-io", + "sp-runtime", + "sp-std", + "test-case", +] + +[[package]] +name = "pallet-grandpa" +version = "28.0.0" +source = "git+https://github.com/gasp-xyz/polkadot-sdk?branch=eth-rollup-develop#e5bbc9b3eaa4f69cd39000bb69f6963f36dd93b8" +dependencies = [ + "frame-benchmarking", + "frame-support", + "frame-system", + "log", + "pallet-authorship", + "pallet-session", + "parity-scale-codec", + "scale-info", + "sp-application-crypto", + "sp-consensus-grandpa", + "sp-core", + "sp-io", + "sp-runtime", + "sp-session", + "sp-staking", + "sp-std", +] + +[[package]] +name = "pallet-identity" +version = "28.0.0" +source = "git+https://github.com/gasp-xyz/polkadot-sdk?branch=eth-rollup-develop#e5bbc9b3eaa4f69cd39000bb69f6963f36dd93b8" +dependencies = [ + "enumflags2", + "frame-benchmarking", + "frame-support", + "frame-system", + "log", + "parity-scale-codec", + "scale-info", + "sp-io", + "sp-runtime", + "sp-std", +] + +[[package]] +name = "pallet-issuance" +version = "2.0.0" +dependencies = [ + "cfg-if", + "frame-benchmarking", + "frame-executive", + "frame-support", + "frame-system", + "frame-try-runtime", + "lazy_static", + "log", + "mangata-support", + "mangata-types", + "orml-tokens", + "orml-traits", + "pallet-vesting-mangata", + "parity-scale-codec", + "scale-info", + "serde", + "sp-core", + "sp-io", + "sp-runtime", + "sp-std", +] + +[[package]] +name = "pallet-maintenance" +version = "0.1.0" +dependencies = [ + "env_logger 0.9.3", + "frame-benchmarking", + "frame-support", + "frame-system", + "hex", + "hex-literal 0.3.4", + "lazy_static", + "log", + "mangata-support", + "mangata-types", + "orml-tokens", + "orml-traits", + "parity-scale-codec", + "scale-info", + "serde", + "serial_test", + "sp-core", + "sp-io", + "sp-runtime", + "sp-std", +] + +[[package]] +name = "pallet-market" +version = "1.0.0" +dependencies = [ + "frame-benchmarking", + "frame-support", + "frame-system", + "log", + "mangata-support", + "mangata-types", + "mockall", + "orml-tokens", + "orml-traits", + "pallet-stable-swap", + "pallet-vesting-mangata", + "pallet-xyk", + "parity-scale-codec", + "primitive-types", + "scale-info", + "serde", + "sp-api", + "sp-arithmetic", + "sp-core", + "sp-io", + "sp-runtime", + "sp-std", +] + +[[package]] +name = "pallet-membership" +version = "28.0.0" +source = "git+https://github.com/gasp-xyz/polkadot-sdk?branch=eth-rollup-develop#e5bbc9b3eaa4f69cd39000bb69f6963f36dd93b8" +dependencies = [ + "frame-benchmarking", + "frame-support", + "frame-system", + "log", + "parity-scale-codec", + "scale-info", + "sp-core", + "sp-io", + "sp-runtime", + "sp-std", +] + +[[package]] +name = "pallet-metamask-signature" +version = "0.1.0" +dependencies = [ + "alloy-sol-types", + "frame-benchmarking", + "frame-support", + "frame-system", + "log", + "parity-scale-codec", + "scale-info", + "serde", + "serde_json", + "sp-core", + "sp-runtime", + "sp-std", +] + +[[package]] +name = "pallet-multipurpose-liquidity" +version = "0.1.0" +dependencies = [ + "env_logger 0.9.3", + "frame-benchmarking", + "frame-executive", + "frame-support", + "frame-system", + "frame-try-runtime", + "hex", + "hex-literal 0.3.4", + "lazy_static", + "log", + "mangata-support", + "mangata-types", + "orml-tokens", + "orml-traits", + "pallet-vesting-mangata", + "parachain-staking", + "parity-scale-codec", + "scale-info", + "serde", + "serial_test", + "sp-core", + "sp-io", + "sp-runtime", + "sp-std", +] + +[[package]] +name = "pallet-proof-of-stake" +version = "0.1.0" +dependencies = [ + "env_logger 0.9.3", + "frame-benchmarking", + "frame-executive", + "frame-support", + "frame-system", + "frame-try-runtime", + "hex", + "hex-literal 0.3.4", + "lazy_static", + "libm 0.2.1", + "log", + "mangata-support", + "mangata-types", + "mockall", + "orml-tokens", + "orml-traits", + "pallet-bootstrap", + "pallet-issuance", + "pallet-vesting-mangata", + "pallet-xyk", + "parity-scale-codec", + "scale-info", + "serde", + "serial_test", + "similar-asserts", + "sp-arithmetic", + "sp-core", + "sp-io", + "sp-runtime", + "sp-std", + "test-case", +] + +[[package]] +name = "pallet-proxy" +version = "28.0.0" +source = "git+https://github.com/gasp-xyz/polkadot-sdk?branch=eth-rollup-develop#e5bbc9b3eaa4f69cd39000bb69f6963f36dd93b8" +dependencies = [ + "frame-benchmarking", + "frame-support", + "frame-system", + "parity-scale-codec", + "scale-info", + "sp-io", + "sp-runtime", + "sp-std", +] + +[[package]] +name = "pallet-rolldown" +version = "0.1.0" +dependencies = [ + "alloy-primitives", + "alloy-sol-types", + "array-bytes 6.2.3", + "env_logger 0.9.3", + "frame-benchmarking", + "frame-support", + "frame-system", + "hex", + "hex-literal 0.3.4", + "itertools 0.10.5", + "lazy_static", + "log", + "mangata-support", + "mangata-types", + "mockall", + "orml-tokens", + "orml-traits", + "pallet-sequencer-staking", + "parity-scale-codec", + "rs_merkle", + "scale-info", + "serde", + "serde_json", + "serial_test", + "sp-core", + "sp-crypto-hashing", + "sp-io", + "sp-runtime", + "sp-std", +] + +[[package]] +name = "pallet-sequencer-staking" +version = "0.1.0" +dependencies = [ + "env_logger 0.9.3", + "frame-benchmarking", + "frame-support", + "frame-system", + "hex", + "hex-literal 0.3.4", + "lazy_static", + "log", + "mangata-support", + "mangata-types", + "mockall", + "orml-tokens", + "orml-traits", + "pallet-issuance", + "parity-scale-codec", + "scale-info", + "serial_test", + "sp-core", + "sp-io", + "sp-runtime", + "sp-std", +] + +[[package]] +name = "pallet-session" +version = "28.0.0" +source = "git+https://github.com/gasp-xyz/polkadot-sdk?branch=eth-rollup-develop#e5bbc9b3eaa4f69cd39000bb69f6963f36dd93b8" +dependencies = [ + "frame-benchmarking", + "frame-support", + "frame-system", + "impl-trait-for-tuples", + "log", + "pallet-timestamp", + "parity-scale-codec", + "scale-info", + "sp-core", + "sp-io", + "sp-runtime", + "sp-session", + "sp-staking", + "sp-state-machine", + "sp-std", + "sp-trie", +] + +[[package]] +name = "pallet-stable-swap" +version = "1.0.0" +dependencies = [ + "frame-benchmarking", + "frame-support", + "frame-system", + "log", + "mangata-support", + "mangata-types", + "orml-tokens", + "orml-traits", + "parity-scale-codec", + "primitive-types", + "scale-info", + "sp-arithmetic", + "sp-core", + "sp-io", + "sp-runtime", + "sp-std", +] + +[[package]] +name = "pallet-sudo-mangata" +version = "28.0.0" +source = "git+https://github.com/gasp-xyz/polkadot-sdk?branch=eth-rollup-develop#e5bbc9b3eaa4f69cd39000bb69f6963f36dd93b8" +dependencies = [ + "docify", + "frame-benchmarking", + "frame-support", + "frame-system", + "log", + "parity-scale-codec", + "scale-info", + "sp-io", + "sp-runtime", + "sp-std", +] + +[[package]] +name = "pallet-sudo-origin" +version = "4.0.0-dev" +dependencies = [ + "frame-executive", + "frame-support", + "frame-system", + "frame-try-runtime", + "log", + "parity-scale-codec", + "scale-info", + "sp-core", + "sp-io", + "sp-runtime", + "sp-std", +] + +[[package]] +name = "pallet-timestamp" +version = "27.0.0" +source = "git+https://github.com/gasp-xyz/polkadot-sdk?branch=eth-rollup-develop#e5bbc9b3eaa4f69cd39000bb69f6963f36dd93b8" +dependencies = [ + "docify", + "frame-benchmarking", + "frame-support", + "frame-system", + "log", + "parity-scale-codec", + "scale-info", + "sp-inherents", + "sp-io", + "sp-runtime", + "sp-std", + "sp-storage", + "sp-timestamp", +] + +[[package]] +name = "pallet-transaction-payment" +version = "28.0.0" +source = "git+https://github.com/gasp-xyz/polkadot-sdk?branch=eth-rollup-develop#e5bbc9b3eaa4f69cd39000bb69f6963f36dd93b8" +dependencies = [ + "frame-support", + "frame-system", + "parity-scale-codec", + "scale-info", + "serde", + "sp-core", + "sp-io", + "sp-runtime", + "sp-std", +] + +[[package]] +name = "pallet-transaction-payment-rpc" +version = "30.0.0" +source = "git+https://github.com/gasp-xyz/polkadot-sdk?branch=eth-rollup-develop#e5bbc9b3eaa4f69cd39000bb69f6963f36dd93b8" +dependencies = [ + "jsonrpsee", + "pallet-transaction-payment-rpc-runtime-api", + "parity-scale-codec", + "sp-api", + "sp-blockchain", + "sp-core", + "sp-rpc", + "sp-runtime", + "sp-weights", +] + +[[package]] +name = "pallet-transaction-payment-rpc-runtime-api" +version = "28.0.0" +source = "git+https://github.com/gasp-xyz/polkadot-sdk?branch=eth-rollup-develop#e5bbc9b3eaa4f69cd39000bb69f6963f36dd93b8" +dependencies = [ + "pallet-transaction-payment", + "parity-scale-codec", + "sp-api", + "sp-runtime", + "sp-weights", +] + +[[package]] +name = "pallet-treasury" +version = "27.0.0" +source = "git+https://github.com/gasp-xyz/polkadot-sdk?branch=eth-rollup-develop#e5bbc9b3eaa4f69cd39000bb69f6963f36dd93b8" +dependencies = [ + "docify", + "frame-benchmarking", + "frame-support", + "frame-system", + "impl-trait-for-tuples", + "pallet-balances", + "parity-scale-codec", + "scale-info", + "serde", + "sp-core", + "sp-runtime", + "sp-std", +] + +[[package]] +name = "pallet-utility" +version = "28.0.0" +source = "git+https://github.com/gasp-xyz/polkadot-sdk?branch=eth-rollup-develop#e5bbc9b3eaa4f69cd39000bb69f6963f36dd93b8" +dependencies = [ + "frame-benchmarking", + "frame-support", + "frame-system", + "parity-scale-codec", + "scale-info", + "sp-core", + "sp-io", + "sp-runtime", + "sp-std", +] + +[[package]] +name = "pallet-utility-mangata" +version = "4.0.0-dev" +source = "git+https://github.com/gasp-xyz/polkadot-sdk?branch=eth-rollup-develop#e5bbc9b3eaa4f69cd39000bb69f6963f36dd93b8" +dependencies = [ + "frame-benchmarking", + "frame-support", + "frame-system", + "parity-scale-codec", + "scale-info", + "sp-core", + "sp-io", + "sp-runtime", + "sp-std", +] + +[[package]] +name = "pallet-vesting-mangata" +version = "4.0.0-dev" +source = "git+https://github.com/gasp-xyz/polkadot-sdk?branch=eth-rollup-develop#e5bbc9b3eaa4f69cd39000bb69f6963f36dd93b8" +dependencies = [ + "frame-benchmarking", + "frame-support", + "frame-system", + "log", + "parity-scale-codec", + "scale-info", + "sp-runtime", + "sp-std", +] + +[[package]] +name = "pallet-xyk" +version = "0.1.0" +dependencies = [ + "env_logger 0.9.3", + "frame-benchmarking", + "frame-executive", + "frame-support", + "frame-system", + "frame-try-runtime", + "hex", + "hex-literal 0.3.4", + "lazy_static", + "libm 0.2.1", + "log", + "mangata-support", + "mangata-types", + "mockall", + "orml-tokens", + "orml-traits", + "pallet-bootstrap", + "pallet-issuance", + "pallet-proof-of-stake", + "pallet-vesting-mangata", + "parity-scale-codec", + "scale-info", + "serde", + "serial_test", + "similar-asserts", + "sp-arithmetic", + "sp-core", + "sp-debug-derive", + "sp-io", + "sp-runtime", + "sp-std", + "test-case", +] + +[[package]] +name = "parachain-staking" +version = "3.0.0" +dependencies = [ + "aquamarine 0.1.12", + "frame-benchmarking", + "frame-support", + "frame-system", + "itertools 0.10.5", + "log", + "mangata-support", + "mangata-types", + "orml-tokens", + "orml-traits", + "pallet-authorship", + "pallet-collective-mangata", + "pallet-issuance", + "pallet-session", + "pallet-vesting-mangata", + "parity-scale-codec", + "scale-info", + "serde", + "similar-asserts", + "sp-arithmetic", + "sp-core", + "sp-io", + "sp-runtime", + "sp-staking", + "sp-std", +] + +[[package]] +name = "parity-bip39" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4e69bf016dc406eff7d53a7d3f7cf1c2e72c82b9088aac1118591e36dd2cd3e9" +dependencies = [ + "bitcoin_hashes", + "rand 0.8.5", + "rand_core 0.6.4", + "serde", + "unicode-normalization", +] + +[[package]] +name = "parity-db" +version = "0.4.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "592a28a24b09c9dc20ac8afaa6839abc417c720afe42c12e1e4a9d6aa2508d2e" +dependencies = [ + "blake2 0.10.6", + "crc32fast", + "fs2", + "hex", + "libc", + "log", + "lz4", + "memmap2 0.5.10", + "parking_lot 0.12.3", + "rand 0.8.5", + "siphasher", + "snap", + "winapi", +] + +[[package]] +name = "parity-scale-codec" +version = "3.6.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "306800abfa29c7f16596b5970a588435e3d5b3149683d00c12b699cc19f895ee" +dependencies = [ + "arrayvec 0.7.6", + "bitvec", + "byte-slice-cast", + "bytes", + "impl-trait-for-tuples", + "parity-scale-codec-derive", + "serde", +] + +[[package]] +name = "parity-scale-codec-derive" +version = "3.6.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d830939c76d294956402033aee57a6da7b438f2294eb94864c37b0569053a42c" +dependencies = [ + "proc-macro-crate 3.2.0", + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "parity-send-wrapper" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa9777aa91b8ad9dd5aaa04a9b6bcb02c7f1deb952fca5a66034d5e63afc5c6f" + +[[package]] +name = "parity-util-mem" +version = "0.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d32c34f4f5ca7f9196001c0aba5a1f9a5a12382c8944b8b0f90233282d1e8f8" +dependencies = [ + "cfg-if", + "impl-trait-for-tuples", + "parity-util-mem-derive", + "parking_lot 0.12.3", + "primitive-types", + "winapi", +] + +[[package]] +name = "parity-util-mem-derive" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f557c32c6d268a07c921471619c0295f5efad3a0e76d4f97a05c091a51d110b2" +dependencies = [ + "proc-macro2", + "syn 1.0.109", + "synstructure", +] + +[[package]] +name = "parity-wasm" +version = "0.45.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e1ad0aff30c1da14b1254fcb2af73e1fa9a28670e584a626f53a369d0e157304" + +[[package]] +name = "parking" +version = "2.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f38d5652c16fde515bb1ecef450ab0f6a219d619a7274976324d5e377f7dceba" + +[[package]] +name = "parking_lot" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7d17b78036a60663b797adeaee46f5c9dfebb86948d1255007a1d6be0271ff99" +dependencies = [ + "instant", + "lock_api", + "parking_lot_core 0.8.6", +] + +[[package]] +name = "parking_lot" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1bf18183cf54e8d6059647fc3063646a1801cf30896933ec2311622cc4b9a27" +dependencies = [ + "lock_api", + "parking_lot_core 0.9.10", +] + +[[package]] +name = "parking_lot_core" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "60a2cfe6f0ad2bfc16aefa463b497d5c7a5ecd44a23efa72aa342d90177356dc" +dependencies = [ + "cfg-if", + "instant", + "libc", + "redox_syscall 0.2.16", + "smallvec", + "winapi", +] + +[[package]] +name = "parking_lot_core" +version = "0.9.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e401f977ab385c9e4e3ab30627d6f26d00e2c73eef317493c4ec6d468726cf8" +dependencies = [ + "cfg-if", + "libc", + "redox_syscall 0.5.4", + "smallvec", + "windows-targets 0.52.6", +] + +[[package]] +name = "partial_sort" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7924d1d0ad836f665c9065e26d016c673ece3993f30d340068b16f282afc1156" + +[[package]] +name = "password-hash" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "346f04948ba92c43e8469c1ee6736c7563d71012b17d40745260fe106aac2166" +dependencies = [ + "base64ct", + "rand_core 0.6.4", + "subtle 2.6.1", +] + +[[package]] +name = "paste" +version = "1.0.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" + +[[package]] +name = "pbkdf2" +version = "0.12.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8ed6a7761f76e3b9f92dfb0a60a6a6477c61024b775147ff0973a02653abaf2" +dependencies = [ + "digest 0.10.7", + "password-hash", +] + +[[package]] +name = "peeking_take_while" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19b17cddbe7ec3f8bc800887bab5e717348c95ea2ca0b1bf0837fb964dc67099" + +[[package]] +name = "pem" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8835c273a76a90455d7344889b0964598e3316e2a79ede8e36f16bdcf2228b8" +dependencies = [ + "base64 0.13.1", +] + +[[package]] +name = "percent-encoding" +version = "2.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" + +[[package]] +name = "pest" +version = "2.7.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c73c26c01b8c87956cea613c907c9d6ecffd8d18a2a5908e5de0adfaa185cea" +dependencies = [ + "memchr", + "thiserror", + "ucd-trie", +] + +[[package]] +name = "pest_derive" +version = "2.7.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "664d22978e2815783adbdd2c588b455b1bd625299ce36b2a99881ac9627e6d8d" +dependencies = [ + "pest", + "pest_generator", +] + +[[package]] +name = "pest_generator" +version = "2.7.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2d5487022d5d33f4c30d91c22afa240ce2a644e87fe08caad974d4eab6badbe" +dependencies = [ + "pest", + "pest_meta", + "proc-macro2", + "quote", + "syn 2.0.77", +] + +[[package]] +name = "pest_meta" +version = "2.7.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0091754bbd0ea592c4deb3a122ce8ecbb0753b738aa82bc055fcc2eccc8d8174" +dependencies = [ + "once_cell", + "pest", + "sha2 0.10.8", +] + +[[package]] +name = "petgraph" +version = "0.6.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4c5cc86750666a3ed20bdaf5ca2a0344f9c67674cae0515bec2da16fbaa47db" +dependencies = [ + "fixedbitset", + "indexmap 2.5.0", +] + +[[package]] +name = "pin-project" +version = "1.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6bf43b791c5b9e34c3d182969b4abb522f9343702850a2e57f460d00d09b4b3" +dependencies = [ + "pin-project-internal", +] + +[[package]] +name = "pin-project-internal" +version = "1.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2f38a4412a78282e09a2cf38d195ea5420d15ba0602cb375210efbc877243965" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.77", +] + +[[package]] +name = "pin-project-lite" +version = "0.1.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "257b64915a082f7811703966789728173279bdebb956b143dbcd23f6f970a777" + +[[package]] +name = "pin-project-lite" +version = "0.2.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bda66fc9667c18cb2758a2ac84d1167245054bcf85d5d1aaa6923f45801bdd02" + +[[package]] +name = "pin-utils" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" + +[[package]] +name = "pkcs8" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f950b2377845cebe5cf8b5165cb3cc1a5e0fa5cfa3e1f7f55707d8fd82e0a7b7" +dependencies = [ + "der", + "spki", +] + +[[package]] +name = "pkg-config" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d231b230927b5e4ad203db57bbcbee2802f6bce620b1e4a9024a07d94e2907ec" + +[[package]] +name = "platforms" +version = "3.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0e4c7666f2019727f9e8e14bf14456e99c707d780922869f1ba473eee101fa49" + +[[package]] +name = "polkavm" +version = "0.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a3693e5efdb2bf74e449cd25fd777a28bd7ed87e41f5d5da75eb31b4de48b94" +dependencies = [ + "libc", + "log", + "polkavm-assembler", + "polkavm-common", + "polkavm-linux-raw", +] + +[[package]] +name = "polkavm-assembler" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fa96d6d868243acc12de813dd48e756cbadcc8e13964c70d272753266deadc1" +dependencies = [ + "log", +] + +[[package]] +name = "polkavm-common" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d9428a5cfcc85c5d7b9fc4b6a18c4b802d0173d768182a51cc7751640f08b92" +dependencies = [ + "log", +] + +[[package]] +name = "polkavm-derive" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae8c4bea6f3e11cd89bb18bcdddac10bd9a24015399bd1c485ad68a985a19606" +dependencies = [ + "polkavm-derive-impl-macro", +] + +[[package]] +name = "polkavm-derive-impl" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c4fdfc49717fb9a196e74a5d28e0bc764eb394a2c803eb11133a31ac996c60c" +dependencies = [ + "polkavm-common", + "proc-macro2", + "quote", + "syn 2.0.77", +] + +[[package]] +name = "polkavm-derive-impl-macro" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ba81f7b5faac81e528eb6158a6f3c9e0bb1008e0ffa19653bc8dea925ecb429" +dependencies = [ + "polkavm-derive-impl", + "syn 2.0.77", +] + +[[package]] +name = "polkavm-linker" +version = "0.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c7be503e60cf56c0eb785f90aaba4b583b36bff00e93997d93fef97f9553c39" +dependencies = [ + "gimli 0.28.1", + "hashbrown 0.14.5", + "log", + "object 0.32.2", + "polkavm-common", + "regalloc2 0.9.3", + "rustc-demangle", +] + +[[package]] +name = "polkavm-linux-raw" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26e85d3456948e650dff0cfc85603915847faf893ed1e66b020bb82ef4557120" + +[[package]] +name = "polling" +version = "3.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc2790cd301dec6cd3b7a025e4815cf825724a51c98dccfe6a3e55f05ffb6511" +dependencies = [ + "cfg-if", + "concurrent-queue", + "hermit-abi 0.4.0", + "pin-project-lite 0.2.14", + "rustix 0.38.37", + "tracing", + "windows-sys 0.59.0", +] + +[[package]] +name = "poly1305" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8159bd90725d2df49889a078b54f4f79e87f1f8a8444194cdca81d38f5393abf" +dependencies = [ + "cpufeatures", + "opaque-debug 0.3.1", + "universal-hash", +] + +[[package]] +name = "polyval" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d1fe60d06143b2430aa532c94cfe9e29783047f06c0d7fd359a9a51b729fa25" +dependencies = [ + "cfg-if", + "cpufeatures", + "opaque-debug 0.3.1", + "universal-hash", +] + +[[package]] +name = "portable-atomic" +version = "1.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da544ee218f0d287a911e9c99a39a8c9bc8fcad3cb8db5959940044ecfc67265" + +[[package]] +name = "powerfmt" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" + +[[package]] +name = "ppv-lite86" +version = "0.2.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77957b295656769bb8ad2b6a6b09d897d94f05c41b069aede1fcdaa675eaea04" +dependencies = [ + "zerocopy", +] + +[[package]] +name = "predicates" +version = "2.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59230a63c37f3e18569bdb90e4a89cbf5bf8b06fea0b84e65ea10cc4df47addd" +dependencies = [ + "difflib", + "float-cmp", + "itertools 0.10.5", + "normalize-line-endings", + "predicates-core", + "regex", +] + +[[package]] +name = "predicates-core" +version = "1.0.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae8177bee8e75d6846599c6b9ff679ed51e882816914eec639944d7c9aa11931" + +[[package]] +name = "predicates-tree" +version = "1.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41b740d195ed3166cd147c8047ec98db0e22ec019eb8eeb76d343b795304fb13" +dependencies = [ + "predicates-core", + "termtree", +] + +[[package]] +name = "prettyplease" +version = "0.1.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c8646e95016a7a6c4adea95bafa8a16baab64b583356217f2c85db4a39d9a86" +dependencies = [ + "proc-macro2", + "syn 1.0.109", +] + +[[package]] +name = "prettyplease" +version = "0.2.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "479cf940fbbb3426c32c5d5176f62ad57549a0bb84773423ba8be9d089f5faba" +dependencies = [ + "proc-macro2", + "syn 2.0.77", +] + +[[package]] +name = "primitive-types" +version = "0.12.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b34d9fd68ae0b74a41b21c03c2f62847aa0ffea044eee893b4c140b37e244e2" +dependencies = [ + "fixed-hash", + "impl-codec", + "impl-num-traits", + "impl-serde 0.4.0", + "scale-info", + "uint", +] + +[[package]] +name = "proc-macro-crate" +version = "1.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e17d47ce914bf4de440332250b0edd23ce48c005f59fab39d3335866b114f11a" +dependencies = [ + "thiserror", + "toml 0.5.11", +] + +[[package]] +name = "proc-macro-crate" +version = "3.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ecf48c7ca261d60b74ab1a7b20da18bede46776b2e55535cb958eb595c5fa7b" +dependencies = [ + "toml_edit", +] + +[[package]] +name = "proc-macro-error" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c" +dependencies = [ + "proc-macro-error-attr", + "proc-macro2", + "quote", + "syn 1.0.109", + "version_check", +] + +[[package]] +name = "proc-macro-error-attr" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869" +dependencies = [ + "proc-macro2", + "quote", + "version_check", +] + +[[package]] +name = "proc-macro-warning" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "834da187cfe638ae8abb0203f0b33e5ccdb02a28e7199f2f47b3e2754f50edca" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.77", +] + +[[package]] +name = "proc-macro2" +version = "1.0.86" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e719e8df665df0d1c8fbfd238015744736151d4445ec0836b8e628aae103b77" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "prometheus" +version = "0.13.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d33c28a30771f7f96db69893f78b857f7450d7e0237e9c8fc6427a81bae7ed1" +dependencies = [ + "cfg-if", + "fnv", + "lazy_static", + "memchr", + "parking_lot 0.12.3", + "thiserror", +] + +[[package]] +name = "prometheus-client" +version = "0.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d6fa99d535dd930d1249e6c79cb3c2915f9172a540fe2b02a4c8f9ca954721e" +dependencies = [ + "dtoa", + "itoa", + "parking_lot 0.12.3", + "prometheus-client-derive-encode", +] + +[[package]] +name = "prometheus-client-derive-encode" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "440f724eba9f6996b75d63681b0a92b06947f1457076d503a4d2e2c8f56442b8" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.77", +] + +[[package]] +name = "proof-of-stake-rpc" +version = "2.0.0" +dependencies = [ + "jsonrpsee", + "mangata-types", + "parity-scale-codec", + "proof-of-stake-runtime-api", + "serde", + "sp-api", + "sp-blockchain", + "sp-core", + "sp-rpc", + "sp-runtime", + "sp-std", +] + +[[package]] +name = "proof-of-stake-runtime-api" +version = "2.0.0" +dependencies = [ + "frame-support", + "frame-system", + "parity-scale-codec", + "scale-info", + "serde", + "sp-api", + "sp-core", + "sp-runtime", + "sp-std", +] + +[[package]] +name = "proptest" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4c2511913b88df1637da85cc8d96ec8e43a3f8bb8ccb71ee1ac240d6f3df58d" +dependencies = [ + "bit-set", + "bit-vec", + "bitflags 2.6.0", + "lazy_static", + "num-traits", + "rand 0.8.5", + "rand_chacha 0.3.1", + "rand_xorshift", + "regex-syntax 0.8.4", + "rusty-fork", + "tempfile", + "unarray", +] + +[[package]] +name = "prost" +version = "0.11.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b82eaa1d779e9a4bc1c3217db8ffbeabaae1dca241bf70183242128d48681cd" +dependencies = [ + "bytes", + "prost-derive 0.11.9", +] + +[[package]] +name = "prost" +version = "0.12.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "deb1435c188b76130da55f17a466d252ff7b1418b2ad3e037d127b94e3411f29" +dependencies = [ + "bytes", + "prost-derive 0.12.6", +] + +[[package]] +name = "prost-build" +version = "0.11.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "119533552c9a7ffacc21e099c24a0ac8bb19c2a2a3f363de84cd9b844feab270" +dependencies = [ + "bytes", + "heck 0.4.1", + "itertools 0.10.5", + "lazy_static", + "log", + "multimap", + "petgraph", + "prettyplease 0.1.25", + "prost 0.11.9", + "prost-types", + "regex", + "syn 1.0.109", + "tempfile", + "which", +] + +[[package]] +name = "prost-derive" +version = "0.11.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5d2d8d10f3c6ded6da8b05b5fb3b8a5082514344d56c9f871412d29b4e075b4" +dependencies = [ + "anyhow", + "itertools 0.10.5", + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "prost-derive" +version = "0.12.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "81bddcdb20abf9501610992b6759a4c888aef7d1a7247ef75e2404275ac24af1" +dependencies = [ + "anyhow", + "itertools 0.12.1", + "proc-macro2", + "quote", + "syn 2.0.77", +] + +[[package]] +name = "prost-types" +version = "0.11.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "213622a1460818959ac1181aaeb2dc9c7f63df720db7d788b3e24eacd1983e13" +dependencies = [ + "prost 0.11.9", +] + +[[package]] +name = "psm" +version = "0.1.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa37f80ca58604976033fae9515a8a2989fc13797d953f7c04fb8fa36a11f205" +dependencies = [ + "cc", +] + +[[package]] +name = "quanta" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e5167a477619228a0b284fac2674e3c388cba90631d7b7de620e6f1fcd08da5" +dependencies = [ + "crossbeam-utils", + "libc", + "once_cell", + "raw-cpuid", + "wasi 0.11.0+wasi-snapshot-preview1", + "web-sys", + "winapi", +] + +[[package]] +name = "quick-error" +version = "1.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1d01941d82fa2ab50be1e79e6714289dd7cde78eba4c074bc5a4374f650dfe0" + +[[package]] +name = "quick-protobuf" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d6da84cc204722a989e01ba2f6e1e276e190f22263d0cb6ce8526fcdb0d2e1f" +dependencies = [ + "byteorder", +] + +[[package]] +name = "quick-protobuf-codec" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1693116345026436eb2f10b677806169c1a1260c1c60eaaffe3fb5a29ae23d8b" +dependencies = [ + "asynchronous-codec", + "bytes", + "quick-protobuf", + "thiserror", + "unsigned-varint", +] + +[[package]] +name = "quicksink" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77de3c815e5a160b1539c6592796801df2043ae35e123b46d73380cfa57af858" +dependencies = [ + "futures-core", + "futures-sink", + "pin-project-lite 0.1.12", +] + +[[package]] +name = "quinn-proto" +version = "0.9.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94b0b33c13a79f669c85defaf4c275dc86a0c0372807d0ca3d78e0bb87274863" +dependencies = [ + "bytes", + "rand 0.8.5", + "ring 0.16.20", + "rustc-hash", + "rustls 0.20.9", + "slab", + "thiserror", + "tinyvec", + "tracing", + "webpki", +] + +[[package]] +name = "quote" +version = "1.0.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5b9d34b8991d19d98081b46eacdd8eb58c6f2b201139f7c5f643cc155a633af" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "radium" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc33ff2d4973d518d823d61aa239014831e521c75da58e3df4840d3f47749d09" + +[[package]] +name = "rand" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a6b1679d49b24bbfe0c803429aa1874472f50d9b363131f0e89fc356b544d03" +dependencies = [ + "getrandom 0.1.16", + "libc", + "rand_chacha 0.2.2", + "rand_core 0.5.1", + "rand_hc", +] + +[[package]] +name = "rand" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" +dependencies = [ + "libc", + "rand_chacha 0.3.1", + "rand_core 0.6.4", +] + +[[package]] +name = "rand_chacha" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f4c8ed856279c9737206bf725bf36935d8666ead7aa69b52be55af369d193402" +dependencies = [ + "ppv-lite86", + "rand_core 0.5.1", +] + +[[package]] +name = "rand_chacha" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" +dependencies = [ + "ppv-lite86", + "rand_core 0.6.4", +] + +[[package]] +name = "rand_core" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "90bde5296fc891b0cef12a6d03ddccc162ce7b2aff54160af9338f8d40df6d19" +dependencies = [ + "getrandom 0.1.16", +] + +[[package]] +name = "rand_core" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" +dependencies = [ + "getrandom 0.2.15", +] + +[[package]] +name = "rand_distr" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32cb0b9bc82b0a0876c2dd994a7e7a2683d3e7390ca40e6886785ef0c7e3ee31" +dependencies = [ + "num-traits", + "rand 0.8.5", +] + +[[package]] +name = "rand_hc" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca3129af7b92a17112d59ad498c6f81eaf463253766b90396d39ea7a39d6613c" +dependencies = [ + "rand_core 0.5.1", +] + +[[package]] +name = "rand_pcg" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59cad018caf63deb318e5a4586d99a24424a364f40f1e5778c29aca23f4fc73e" +dependencies = [ + "rand_core 0.6.4", +] + +[[package]] +name = "rand_xorshift" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d25bf25ec5ae4a3f1b92f929810509a2f53d7dca2f50b794ff57e3face536c8f" +dependencies = [ + "rand_core 0.6.4", +] + +[[package]] +name = "raw-cpuid" +version = "11.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb9ee317cfe3fbd54b36a511efc1edd42e216903c9cd575e686dd68a2ba90d8d" +dependencies = [ + "bitflags 2.6.0", +] + +[[package]] +name = "rawpointer" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "60a357793950651c4ed0f3f52338f53b2f809f32d83a07f72909fa13e4c6c1e3" + +[[package]] +name = "rayon" +version = "1.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b418a60154510ca1a002a752ca9714984e21e4241e804d32555251faf8b78ffa" +dependencies = [ + "either", + "rayon-core", +] + +[[package]] +name = "rayon-core" +version = "1.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1465873a3dfdaa8ae7cb14b4383657caab0b3e8a0aa9ae8e04b044854c8dfce2" +dependencies = [ + "crossbeam-deque", + "crossbeam-utils", +] + +[[package]] +name = "rcgen" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ffbe84efe2f38dea12e9bfc1f65377fdf03e53a18cb3b995faedf7934c7e785b" +dependencies = [ + "pem", + "ring 0.16.20", + "time", + "yasna", +] + +[[package]] +name = "redox_syscall" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fb5a58c1855b4b6819d59012155603f0b22ad30cad752600aadfcb695265519a" +dependencies = [ + "bitflags 1.3.2", +] + +[[package]] +name = "redox_syscall" +version = "0.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0884ad60e090bf1345b93da0a5de8923c93884cd03f40dfcfddd3b4bee661853" +dependencies = [ + "bitflags 2.6.0", +] + +[[package]] +name = "redox_users" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba009ff324d1fc1b900bd1fdb31564febe58a8ccc8a6fdbb93b543d33b13ca43" +dependencies = [ + "getrandom 0.2.15", + "libredox", + "thiserror", +] + +[[package]] +name = "ref-cast" +version = "1.0.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ccf0a6f84d5f1d581da8b41b47ec8600871962f2a528115b542b362d4b744931" +dependencies = [ + "ref-cast-impl", +] + +[[package]] +name = "ref-cast-impl" +version = "1.0.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bcc303e793d3734489387d205e9b186fac9c6cfacedd98cbb2e8a5943595f3e6" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.77", +] + +[[package]] +name = "regalloc2" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "80535183cae11b149d618fbd3c37e38d7cda589d82d7769e196ca9a9042d7621" +dependencies = [ + "fxhash", + "log", + "slice-group-by", + "smallvec", +] + +[[package]] +name = "regalloc2" +version = "0.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ad156d539c879b7a24a363a2016d77961786e71f48f2e2fc8302a92abd2429a6" +dependencies = [ + "hashbrown 0.13.2", + "log", + "rustc-hash", + "slice-group-by", + "smallvec", +] + +[[package]] +name = "regex" +version = "1.10.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4219d74c6b67a3654a9fbebc4b419e22126d13d2f3c4a07ee0cb61ff79a79619" +dependencies = [ + "aho-corasick", + "memchr", + "regex-automata 0.4.7", + "regex-syntax 0.8.4", +] + +[[package]] +name = "regex-automata" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c230d73fb8d8c1b9c0b3135c5142a8acee3a0558fb8db5cf1cb65f8d7862132" +dependencies = [ + "regex-syntax 0.6.29", +] + +[[package]] +name = "regex-automata" +version = "0.4.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38caf58cc5ef2fed281f89292ef23f6365465ed9a41b7a7754eb4e26496c92df" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax 0.8.4", +] + +[[package]] +name = "regex-syntax" +version = "0.6.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f162c6dd7b008981e4d40210aca20b4bd0f9b60ca9271061b07f78537722f2e1" + +[[package]] +name = "regex-syntax" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a66a03ae7c801facd77a29370b4faec201768915ac14a721ba36f20bc9c209b" + +[[package]] +name = "resolv-conf" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "52e44394d2086d010551b14b53b1f24e31647570cd1deb0379e2c21b329aba00" +dependencies = [ + "hostname", + "quick-error", +] + +[[package]] +name = "rfc6979" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8dd2a808d456c4a54e300a23e9f5a67e122c3024119acbfd73e3bf664491cb2" +dependencies = [ + "hmac 0.12.1", + "subtle 2.6.1", +] + +[[package]] +name = "ring" +version = "0.1.0" +source = "git+https://github.com/w3f/ring-proof#1eedf08d97effe1921f4aa2e926575088b068e2b" +dependencies = [ + "ark-ec 0.5.0", + "ark-ff 0.5.0", + "ark-poly 0.5.0", + "ark-serialize 0.5.0", + "ark-std 0.5.0", + "ark-transcript 0.0.3", + "blake2 0.10.6", + "common", + "fflonk", +] + +[[package]] +name = "ring" +version = "0.16.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3053cf52e236a3ed746dfc745aa9cacf1b791d846bdaf412f60a8d7d6e17c8fc" +dependencies = [ + "cc", + "libc", + "once_cell", + "spin 0.5.2", + "untrusted 0.7.1", + "web-sys", + "winapi", +] + +[[package]] +name = "ring" +version = "0.17.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c17fa4cb658e3583423e915b9f3acc01cceaee1860e33d59ebae66adc3a2dc0d" +dependencies = [ + "cc", + "cfg-if", + "getrandom 0.2.15", + "libc", + "spin 0.9.8", + "untrusted 0.9.0", + "windows-sys 0.52.0", +] + +[[package]] +name = "rlp" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb919243f34364b6bd2fc10ef797edbfa75f33c252e7998527479c6d6b47e1ec" +dependencies = [ + "bytes", + "rustc-hex", +] + +[[package]] +name = "rocksdb" +version = "0.21.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb6f170a4041d50a0ce04b0d2e14916d6ca863ea2e422689a5b694395d299ffe" +dependencies = [ + "libc", + "librocksdb-sys", +] + +[[package]] +name = "rolldown-rpc" +version = "2.0.0" +dependencies = [ + "array-bytes 6.2.3", + "jsonrpsee", + "mangata-types", + "parity-scale-codec", + "rolldown-runtime-api", + "serde", + "sp-api", + "sp-blockchain", + "sp-core", + "sp-rpc", + "sp-runtime", + "sp-std", +] + +[[package]] +name = "rolldown-runtime-api" +version = "2.0.0" +dependencies = [ + "sp-api", + "sp-core", + "sp-std", +] + +[[package]] +name = "rollup-node" +version = "1.0.0" +dependencies = [ + "array-bytes 6.2.3", + "clap", + "color-print", + "frame-benchmarking", + "frame-benchmarking-cli", + "frame-system", + "futures", + "hex", + "hex-literal 0.3.4", + "jsonrpsee", + "log", + "mangata-rpc-nonce", + "market-rpc", + "metamask-signature-rpc", + "pallet-market", + "pallet-rolldown", + "pallet-transaction-payment", + "pallet-transaction-payment-rpc", + "parity-scale-codec", + "proof-of-stake-rpc", + "proof-of-stake-runtime-api", + "rand 0.8.5", + "rolldown-rpc", + "rolldown-runtime-api", + "rollup-runtime", + "sc-basic-authorship-ver", + "sc-chain-spec", + "sc-cli", + "sc-client-api", + "sc-consensus", + "sc-consensus-aura", + "sc-consensus-grandpa", + "sc-executor", + "sc-network", + "sc-network-sync", + "sc-offchain", + "sc-rpc", + "sc-rpc-api", + "sc-service", + "sc-sysinfo", + "sc-telemetry", + "sc-tracing", + "sc-transaction-pool", + "sc-transaction-pool-api", + "serde", + "serde_json", + "sp-api", + "sp-block-builder", + "sp-blockchain", + "sp-consensus-aura", + "sp-consensus-grandpa", + "sp-core", + "sp-inherents", + "sp-io", + "sp-keyring", + "sp-keystore", + "sp-offchain", + "sp-runtime", + "sp-session", + "sp-statement-store", + "sp-std", + "sp-timestamp", + "sp-transaction-pool", + "sp-ver", + "substrate-build-script-utils", + "substrate-prometheus-endpoint", + "tempfile", + "try-runtime-cli", + "ver-api", + "xyk-rpc", + "xyk-runtime-api", +] + +[[package]] +name = "rollup-runtime" +version = "1.0.0" +dependencies = [ + "array-bytes 6.2.3", + "frame-benchmarking", + "frame-executive", + "frame-support", + "frame-system", + "frame-system-benchmarking", + "frame-system-rpc-runtime-api", + "frame-try-runtime", + "hex-literal 0.3.4", + "log", + "mangata-support", + "mangata-types", + "metamask-signature-runtime-api", + "orml-asset-registry", + "orml-tokens", + "orml-traits", + "pallet-aura", + "pallet-authorship", + "pallet-bootstrap", + "pallet-collective-mangata", + "pallet-crowdloan-rewards", + "pallet-fee-lock", + "pallet-grandpa", + "pallet-identity", + "pallet-issuance", + "pallet-maintenance", + "pallet-market", + "pallet-membership", + "pallet-metamask-signature", + "pallet-multipurpose-liquidity", + "pallet-proof-of-stake", + "pallet-proxy", + "pallet-rolldown", + "pallet-sequencer-staking", + "pallet-session", + "pallet-stable-swap", + "pallet-sudo-mangata", + "pallet-sudo-origin", + "pallet-timestamp", + "pallet-transaction-payment", + "pallet-transaction-payment-rpc-runtime-api", + "pallet-treasury", + "pallet-utility-mangata", + "pallet-vesting-mangata", + "pallet-xyk", + "parachain-staking", + "parity-scale-codec", + "primitive-types", + "proof-of-stake-runtime-api", + "rolldown-runtime-api", + "scale-info", + "smallvec", + "sp-api", + "sp-application-crypto", + "sp-block-builder", + "sp-consensus-aura", + "sp-consensus-grandpa", + "sp-core", + "sp-inherents", + "sp-io", + "sp-offchain", + "sp-runtime", + "sp-session", + "sp-std", + "sp-storage", + "sp-transaction-pool", + "sp-ver", + "sp-version", + "sp-weights", + "static_assertions", + "substrate-wasm-builder", + "ver-api", + "xyk-runtime-api", +] + +[[package]] +name = "rollup-runtime-integration-test" +version = "0.1.0" +dependencies = [ + "env_logger 0.9.3", + "frame-support", + "frame-system", + "hex-literal 0.3.4", + "log", + "mangata-support", + "mangata-types", + "orml-asset-registry", + "orml-tokens", + "orml-traits", + "pallet-balances", + "pallet-bootstrap", + "pallet-fee-lock", + "pallet-identity", + "pallet-issuance", + "pallet-maintenance", + "pallet-market", + "pallet-membership", + "pallet-multipurpose-liquidity", + "pallet-proof-of-stake", + "pallet-proxy", + "pallet-rolldown", + "pallet-session", + "pallet-stable-swap", + "pallet-sudo-origin", + "pallet-transaction-payment", + "pallet-xyk", + "parachain-staking", + "parity-scale-codec", + "rolldown-runtime-api", + "rollup-runtime", + "sp-io", + "sp-runtime", + "sp-storage", + "xyk-runtime-api", +] + +[[package]] +name = "route-recognizer" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "afab94fb28594581f62d981211a9a4d53cc8130bbcbbb89a0440d9b8e81a7746" + +[[package]] +name = "rpassword" +version = "7.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "80472be3c897911d0137b2d2b9055faf6eeac5b14e324073d83bc17b191d7e3f" +dependencies = [ + "libc", + "rtoolbox", + "windows-sys 0.48.0", +] + +[[package]] +name = "rs_merkle" +version = "1.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b241d2e59b74ef9e98d94c78c47623d04c8392abaf82014dfd372a16041128f" +dependencies = [ + "sha2 0.10.8", +] + +[[package]] +name = "rtnetlink" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "322c53fd76a18698f1c27381d58091de3a043d356aa5bd0d510608b565f469a0" +dependencies = [ + "futures", + "log", + "netlink-packet-route", + "netlink-proto", + "nix", + "thiserror", + "tokio", +] + +[[package]] +name = "rtoolbox" +version = "0.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c247d24e63230cdb56463ae328478bd5eac8b8faa8c69461a77e8e323afac90e" +dependencies = [ + "libc", + "windows-sys 0.48.0", +] + +[[package]] +name = "ruint" +version = "1.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c3cc4c2511671f327125da14133d0c5c5d137f006a1017a16f557bc85b16286" +dependencies = [ + "alloy-rlp", + "ark-ff 0.3.0", + "ark-ff 0.4.2", + "bytes", + "fastrlp", + "num-bigint", + "num-traits", + "parity-scale-codec", + "primitive-types", + "proptest", + "rand 0.8.5", + "rlp", + "ruint-macro", + "serde", + "valuable", + "zeroize", +] + +[[package]] +name = "ruint-macro" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "48fd7bd8a6377e15ad9d42a8ec25371b94ddc67abe7c8b9127bec79bebaaae18" + +[[package]] +name = "rustc-demangle" +version = "0.1.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f" + +[[package]] +name = "rustc-hash" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" + +[[package]] +name = "rustc-hex" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3e75f6a532d0fd9f7f13144f392b6ad56a32696bfcd9c78f797f16bbb6f072d6" + +[[package]] +name = "rustc_version" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0dfe2087c51c460008730de8b57e6a320782fbfb312e1f4d520e6c6fae155ee" +dependencies = [ + "semver 0.11.0", +] + +[[package]] +name = "rustc_version" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92" +dependencies = [ + "semver 1.0.23", +] + +[[package]] +name = "rusticata-macros" +version = "4.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "faf0c4a6ece9950b9abdb62b1cfcf2a68b3b67a10ba445b3bb85be2a293d0632" +dependencies = [ + "nom", +] + +[[package]] +name = "rustix" +version = "0.36.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "305efbd14fde4139eb501df5f136994bb520b033fa9fbdce287507dc23b8c7ed" +dependencies = [ + "bitflags 1.3.2", + "errno", + "io-lifetimes", + "libc", + "linux-raw-sys 0.1.4", + "windows-sys 0.45.0", +] + +[[package]] +name = "rustix" +version = "0.38.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8acb788b847c24f28525660c4d7758620a7210875711f79e7f663cc152726811" +dependencies = [ + "bitflags 2.6.0", + "errno", + "libc", + "linux-raw-sys 0.4.14", + "windows-sys 0.52.0", +] + +[[package]] +name = "rustls" +version = "0.20.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b80e3dec595989ea8510028f30c408a4630db12c9cbb8de34203b89d6577e99" +dependencies = [ + "log", + "ring 0.16.20", + "sct", + "webpki", +] + +[[package]] +name = "rustls" +version = "0.21.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f56a14d1f48b391359b22f731fd4bd7e43c97f3c50eee276f3aa09c94784d3e" +dependencies = [ + "log", + "ring 0.17.8", + "rustls-webpki 0.101.7", + "sct", +] + +[[package]] +name = "rustls" +version = "0.22.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf4ef73721ac7bcd79b2b315da7779d8fc09718c6b3d2d1b2d94850eb8c18432" +dependencies = [ + "log", + "ring 0.17.8", + "rustls-pki-types", + "rustls-webpki 0.102.8", + "subtle 2.6.1", + "zeroize", +] + +[[package]] +name = "rustls-native-certs" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a9aace74cb666635c918e9c12bc0d348266037aa8eb599b5cba565709a8dff00" +dependencies = [ + "openssl-probe", + "rustls-pemfile 1.0.4", + "schannel", + "security-framework", +] + +[[package]] +name = "rustls-native-certs" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5bfb394eeed242e909609f56089eecfe5fda225042e8b171791b9c95f5931e5" +dependencies = [ + "openssl-probe", + "rustls-pemfile 2.1.3", + "rustls-pki-types", + "schannel", + "security-framework", +] + +[[package]] +name = "rustls-pemfile" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1c74cae0a4cf6ccbbf5f359f08efdf8ee7e1dc532573bf0db71968cb56b1448c" +dependencies = [ + "base64 0.21.7", +] + +[[package]] +name = "rustls-pemfile" +version = "2.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "196fe16b00e106300d3e45ecfcb764fa292a535d7326a29a5875c579c7417425" +dependencies = [ + "base64 0.22.1", + "rustls-pki-types", +] + +[[package]] +name = "rustls-pki-types" +version = "1.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc0a2ce646f8655401bb81e7927b812614bd5d91dbc968696be50603510fcaf0" + +[[package]] +name = "rustls-webpki" +version = "0.101.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b6275d1ee7a1cd780b64aca7726599a1dbc893b1e64144529e55c3c2f745765" +dependencies = [ + "ring 0.17.8", + "untrusted 0.9.0", +] + +[[package]] +name = "rustls-webpki" +version = "0.102.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "64ca1bc8749bd4cf37b5ce386cc146580777b4e8572c7b97baf22c83f444bee9" +dependencies = [ + "ring 0.17.8", + "rustls-pki-types", + "untrusted 0.9.0", +] + +[[package]] +name = "rustversion" +version = "1.0.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "955d28af4278de8121b7ebeb796b6a45735dc01436d898801014aced2773a3d6" + +[[package]] +name = "rusty-fork" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb3dcc6e454c328bb824492db107ab7c0ae8fcffe4ad210136ef014458c1bc4f" +dependencies = [ + "fnv", + "quick-error", + "tempfile", + "wait-timeout", +] + +[[package]] +name = "rw-stream-sink" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26338f5e09bb721b85b135ea05af7767c90b52f6de4f087d4f4a3a9d64e7dc04" +dependencies = [ + "futures", + "pin-project", + "static_assertions", +] + +[[package]] +name = "ryu" +version = "1.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f" + +[[package]] +name = "safe_arch" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3460605018fdc9612bce72735cba0d27efbcd9904780d44c7e3a9948f96148a" +dependencies = [ + "bytemuck", +] + +[[package]] +name = "same-file" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" +dependencies = [ + "winapi-util", +] + +[[package]] +name = "sc-allocator" +version = "23.0.0" +source = "git+https://github.com/gasp-xyz/polkadot-sdk?branch=eth-rollup-develop#e5bbc9b3eaa4f69cd39000bb69f6963f36dd93b8" +dependencies = [ + "log", + "sp-core", + "sp-wasm-interface", + "thiserror", +] + +[[package]] +name = "sc-basic-authorship-ver" +version = "0.10.0-dev" +source = "git+https://github.com/gasp-xyz/polkadot-sdk?branch=eth-rollup-develop#e5bbc9b3eaa4f69cd39000bb69f6963f36dd93b8" +dependencies = [ + "aquamarine 0.3.3", + "futures", + "futures-timer", + "log", + "parity-scale-codec", + "sc-block-builder-ver", + "sc-proposer-metrics", + "sc-telemetry", + "sc-transaction-pool-api", + "sp-api", + "sp-blockchain", + "sp-consensus", + "sp-core", + "sp-inherents", + "sp-runtime", + "sp-trie", + "sp-ver", + "substrate-prometheus-endpoint", + "ver-api", +] + +[[package]] +name = "sc-block-builder" +version = "0.33.0" +source = "git+https://github.com/gasp-xyz/polkadot-sdk?branch=eth-rollup-develop#e5bbc9b3eaa4f69cd39000bb69f6963f36dd93b8" +dependencies = [ + "parity-scale-codec", + "sp-api", + "sp-block-builder", + "sp-blockchain", + "sp-core", + "sp-inherents", + "sp-runtime", + "sp-trie", +] + +[[package]] +name = "sc-block-builder-ver" +version = "0.33.0" +source = "git+https://github.com/gasp-xyz/polkadot-sdk?branch=eth-rollup-develop#e5bbc9b3eaa4f69cd39000bb69f6963f36dd93b8" +dependencies = [ + "aquamarine 0.1.12", + "extrinsic-shuffler", + "log", + "parity-scale-codec", + "sp-api", + "sp-block-builder", + "sp-blockchain", + "sp-core", + "sp-inherents", + "sp-runtime", + "sp-trie", + "sp-ver", + "ver-api", +] + +[[package]] +name = "sc-chain-spec" +version = "27.0.0" +source = "git+https://github.com/gasp-xyz/polkadot-sdk?branch=eth-rollup-develop#e5bbc9b3eaa4f69cd39000bb69f6963f36dd93b8" +dependencies = [ + "array-bytes 6.2.3", + "docify", + "log", + "memmap2 0.9.5", + "parity-scale-codec", + "sc-chain-spec-derive", + "sc-client-api", + "sc-executor", + "sc-network", + "sc-telemetry", + "serde", + "serde_json", + "sp-blockchain", + "sp-core", + "sp-crypto-hashing", + "sp-genesis-builder", + "sp-io", + "sp-runtime", + "sp-state-machine", +] + +[[package]] +name = "sc-chain-spec-derive" +version = "11.0.0" +source = "git+https://github.com/gasp-xyz/polkadot-sdk?branch=eth-rollup-develop#e5bbc9b3eaa4f69cd39000bb69f6963f36dd93b8" +dependencies = [ + "proc-macro-crate 3.2.0", + "proc-macro2", + "quote", + "syn 2.0.77", +] + +[[package]] +name = "sc-cli" +version = "0.36.0" +source = "git+https://github.com/gasp-xyz/polkadot-sdk?branch=eth-rollup-develop#e5bbc9b3eaa4f69cd39000bb69f6963f36dd93b8" +dependencies = [ + "array-bytes 6.2.3", + "chrono", + "clap", + "fdlimit", + "futures", + "itertools 0.10.5", + "libp2p-identity", + "log", + "names", + "parity-bip39", + "parity-scale-codec", + "rand 0.8.5", + "regex", + "rpassword", + "sc-client-api", + "sc-client-db", + "sc-keystore", + "sc-mixnet", + "sc-network", + "sc-service", + "sc-telemetry", + "sc-tracing", + "sc-utils", + "serde", + "serde_json", + "sp-blockchain", + "sp-core", + "sp-keyring", + "sp-keystore", + "sp-panic-handler", + "sp-runtime", + "sp-version", + "thiserror", + "tokio", +] + +[[package]] +name = "sc-client-api" +version = "28.0.0" +source = "git+https://github.com/gasp-xyz/polkadot-sdk?branch=eth-rollup-develop#e5bbc9b3eaa4f69cd39000bb69f6963f36dd93b8" +dependencies = [ + "fnv", + "futures", + "log", + "parity-scale-codec", + "parking_lot 0.12.3", + "sc-executor", + "sc-transaction-pool-api", + "sc-utils", + "sp-api", + "sp-blockchain", + "sp-consensus", + "sp-core", + "sp-database", + "sp-externalities", + "sp-runtime", + "sp-state-machine", + "sp-statement-store", + "sp-storage", + "sp-trie", + "substrate-prometheus-endpoint", +] + +[[package]] +name = "sc-client-db" +version = "0.35.0" +source = "git+https://github.com/gasp-xyz/polkadot-sdk?branch=eth-rollup-develop#e5bbc9b3eaa4f69cd39000bb69f6963f36dd93b8" +dependencies = [ + "hash-db", + "kvdb", + "kvdb-memorydb", + "kvdb-rocksdb", + "linked-hash-map", + "log", + "parity-db", + "parity-scale-codec", + "parking_lot 0.12.3", + "sc-client-api", + "sc-state-db", + "schnellru", + "sp-arithmetic", + "sp-blockchain", + "sp-core", + "sp-database", + "sp-runtime", + "sp-state-machine", + "sp-trie", +] + +[[package]] +name = "sc-consensus" +version = "0.33.0" +source = "git+https://github.com/gasp-xyz/polkadot-sdk?branch=eth-rollup-develop#e5bbc9b3eaa4f69cd39000bb69f6963f36dd93b8" +dependencies = [ + "async-trait", + "futures", + "futures-timer", + "libp2p-identity", + "log", + "mockall", + "parking_lot 0.12.3", + "sc-client-api", + "sc-utils", + "serde", + "sp-api", + "sp-blockchain", + "sp-consensus", + "sp-core", + "sp-runtime", + "sp-state-machine", + "substrate-prometheus-endpoint", + "thiserror", +] + +[[package]] +name = "sc-consensus-aura" +version = "0.34.0" +source = "git+https://github.com/gasp-xyz/polkadot-sdk?branch=eth-rollup-develop#e5bbc9b3eaa4f69cd39000bb69f6963f36dd93b8" +dependencies = [ + "async-trait", + "futures", + "log", + "parity-scale-codec", + "sc-block-builder", + "sc-client-api", + "sc-consensus", + "sc-consensus-slots", + "sc-telemetry", + "sp-api", + "sp-application-crypto", + "sp-block-builder", + "sp-blockchain", + "sp-consensus", + "sp-consensus-aura", + "sp-consensus-slots", + "sp-core", + "sp-inherents", + "sp-keystore", + "sp-runtime", + "substrate-prometheus-endpoint", + "thiserror", +] + +[[package]] +name = "sc-consensus-grandpa" +version = "0.19.0" +source = "git+https://github.com/gasp-xyz/polkadot-sdk?branch=eth-rollup-develop#e5bbc9b3eaa4f69cd39000bb69f6963f36dd93b8" +dependencies = [ + "ahash 0.8.11", + "array-bytes 6.2.3", + "async-trait", + "dyn-clone", + "finality-grandpa", + "fork-tree", + "futures", + "futures-timer", + "log", + "parity-scale-codec", + "parking_lot 0.12.3", + "rand 0.8.5", + "sc-block-builder", + "sc-chain-spec", + "sc-client-api", + "sc-consensus", + "sc-network", + "sc-network-common", + "sc-network-gossip", + "sc-network-sync", + "sc-telemetry", + "sc-transaction-pool-api", + "sc-utils", + "serde_json", + "sp-api", + "sp-application-crypto", + "sp-arithmetic", + "sp-blockchain", + "sp-consensus", + "sp-consensus-grandpa", + "sp-core", + "sp-crypto-hashing", + "sp-keystore", + "sp-runtime", + "substrate-prometheus-endpoint", + "thiserror", +] + +[[package]] +name = "sc-consensus-slots" +version = "0.33.0" +source = "git+https://github.com/gasp-xyz/polkadot-sdk?branch=eth-rollup-develop#e5bbc9b3eaa4f69cd39000bb69f6963f36dd93b8" +dependencies = [ + "async-trait", + "futures", + "futures-timer", + "log", + "parity-scale-codec", + "sc-client-api", + "sc-consensus", + "sc-telemetry", + "sp-arithmetic", + "sp-blockchain", + "sp-consensus", + "sp-consensus-slots", + "sp-core", + "sp-inherents", + "sp-keystore", + "sp-runtime", + "sp-state-machine", + "sp-ver", +] + +[[package]] +name = "sc-executor" +version = "0.32.0" +source = "git+https://github.com/gasp-xyz/polkadot-sdk?branch=eth-rollup-develop#e5bbc9b3eaa4f69cd39000bb69f6963f36dd93b8" +dependencies = [ + "parity-scale-codec", + "parking_lot 0.12.3", + "sc-executor-common", + "sc-executor-polkavm", + "sc-executor-wasmtime", + "schnellru", + "sp-api", + "sp-core", + "sp-externalities", + "sp-io", + "sp-panic-handler", + "sp-runtime-interface", + "sp-trie", + "sp-version", + "sp-wasm-interface", + "tracing", +] + +[[package]] +name = "sc-executor-common" +version = "0.29.0" +source = "git+https://github.com/gasp-xyz/polkadot-sdk?branch=eth-rollup-develop#e5bbc9b3eaa4f69cd39000bb69f6963f36dd93b8" +dependencies = [ + "polkavm", + "sc-allocator", + "sp-maybe-compressed-blob", + "sp-wasm-interface", + "thiserror", + "wasm-instrument", +] + +[[package]] +name = "sc-executor-polkavm" +version = "0.29.0" +source = "git+https://github.com/gasp-xyz/polkadot-sdk?branch=eth-rollup-develop#e5bbc9b3eaa4f69cd39000bb69f6963f36dd93b8" +dependencies = [ + "log", + "polkavm", + "sc-executor-common", + "sp-wasm-interface", +] + +[[package]] +name = "sc-executor-wasmtime" +version = "0.29.0" +source = "git+https://github.com/gasp-xyz/polkadot-sdk?branch=eth-rollup-develop#e5bbc9b3eaa4f69cd39000bb69f6963f36dd93b8" +dependencies = [ + "anyhow", + "cfg-if", + "libc", + "log", + "parking_lot 0.12.3", + "rustix 0.36.17", + "sc-allocator", + "sc-executor-common", + "sp-runtime-interface", + "sp-wasm-interface", + "wasmtime", +] + +[[package]] +name = "sc-informant" +version = "0.33.0" +source = "git+https://github.com/gasp-xyz/polkadot-sdk?branch=eth-rollup-develop#e5bbc9b3eaa4f69cd39000bb69f6963f36dd93b8" +dependencies = [ + "ansi_term", + "futures", + "futures-timer", + "log", + "sc-client-api", + "sc-network", + "sc-network-common", + "sc-network-sync", + "sp-blockchain", + "sp-runtime", +] + +[[package]] +name = "sc-keystore" +version = "25.0.0" +source = "git+https://github.com/gasp-xyz/polkadot-sdk?branch=eth-rollup-develop#e5bbc9b3eaa4f69cd39000bb69f6963f36dd93b8" +dependencies = [ + "array-bytes 6.2.3", + "parking_lot 0.12.3", + "serde_json", + "sp-application-crypto", + "sp-core", + "sp-keystore", + "thiserror", +] + +[[package]] +name = "sc-mixnet" +version = "0.4.0" +source = "git+https://github.com/gasp-xyz/polkadot-sdk?branch=eth-rollup-develop#e5bbc9b3eaa4f69cd39000bb69f6963f36dd93b8" +dependencies = [ + "array-bytes 4.2.0", + "arrayvec 0.7.6", + "blake2 0.10.6", + "bytes", + "futures", + "futures-timer", + "libp2p-identity", + "log", + "mixnet", + "multiaddr", + "parity-scale-codec", + "parking_lot 0.12.3", + "sc-client-api", + "sc-network", + "sc-transaction-pool-api", + "sp-api", + "sp-consensus", + "sp-core", + "sp-keystore", + "sp-mixnet", + "sp-runtime", + "thiserror", +] + +[[package]] +name = "sc-network" +version = "0.34.0" +source = "git+https://github.com/gasp-xyz/polkadot-sdk?branch=eth-rollup-develop#e5bbc9b3eaa4f69cd39000bb69f6963f36dd93b8" +dependencies = [ + "array-bytes 6.2.3", + "async-channel", + "async-trait", + "asynchronous-codec", + "bytes", + "either", + "fnv", + "futures", + "futures-timer", + "ip_network", + "libp2p", + "linked_hash_set", + "log", + "mockall", + "parity-scale-codec", + "parking_lot 0.12.3", + "partial_sort", + "pin-project", + "rand 0.8.5", + "sc-client-api", + "sc-network-common", + "sc-utils", + "serde", + "serde_json", + "smallvec", + "sp-arithmetic", + "sp-blockchain", + "sp-core", + "sp-runtime", + "substrate-prometheus-endpoint", + "thiserror", + "tokio", + "tokio-stream", + "unsigned-varint", + "wasm-timer", + "zeroize", +] + +[[package]] +name = "sc-network-bitswap" +version = "0.33.0" +source = "git+https://github.com/gasp-xyz/polkadot-sdk?branch=eth-rollup-develop#e5bbc9b3eaa4f69cd39000bb69f6963f36dd93b8" +dependencies = [ + "async-channel", + "cid", + "futures", + "libp2p-identity", + "log", + "prost 0.12.6", + "prost-build", + "sc-client-api", + "sc-network", + "sp-blockchain", + "sp-runtime", + "thiserror", + "unsigned-varint", +] + +[[package]] +name = "sc-network-common" +version = "0.33.0" +source = "git+https://github.com/gasp-xyz/polkadot-sdk?branch=eth-rollup-develop#e5bbc9b3eaa4f69cd39000bb69f6963f36dd93b8" +dependencies = [ + "async-trait", + "bitflags 1.3.2", + "futures", + "libp2p-identity", + "parity-scale-codec", + "prost-build", + "sc-consensus", + "sp-consensus", + "sp-consensus-grandpa", + "sp-runtime", +] + +[[package]] +name = "sc-network-gossip" +version = "0.34.0" +source = "git+https://github.com/gasp-xyz/polkadot-sdk?branch=eth-rollup-develop#e5bbc9b3eaa4f69cd39000bb69f6963f36dd93b8" +dependencies = [ + "ahash 0.8.11", + "futures", + "futures-timer", + "libp2p", + "log", + "sc-network", + "sc-network-common", + "sc-network-sync", + "schnellru", + "sp-runtime", + "substrate-prometheus-endpoint", + "tracing", +] + +[[package]] +name = "sc-network-light" +version = "0.33.0" +source = "git+https://github.com/gasp-xyz/polkadot-sdk?branch=eth-rollup-develop#e5bbc9b3eaa4f69cd39000bb69f6963f36dd93b8" +dependencies = [ + "array-bytes 6.2.3", + "async-channel", + "futures", + "libp2p-identity", + "log", + "parity-scale-codec", + "prost 0.12.6", + "prost-build", + "sc-client-api", + "sc-network", + "sp-blockchain", + "sp-core", + "sp-runtime", + "thiserror", +] + +[[package]] +name = "sc-network-sync" +version = "0.33.0" +source = "git+https://github.com/gasp-xyz/polkadot-sdk?branch=eth-rollup-develop#e5bbc9b3eaa4f69cd39000bb69f6963f36dd93b8" +dependencies = [ + "array-bytes 6.2.3", + "async-channel", + "async-trait", + "fork-tree", + "futures", + "futures-timer", + "libp2p", + "log", + "mockall", + "parity-scale-codec", + "prost 0.12.6", + "prost-build", + "sc-client-api", + "sc-consensus", + "sc-network", + "sc-network-common", + "sc-utils", + "schnellru", + "smallvec", + "sp-arithmetic", + "sp-blockchain", + "sp-consensus", + "sp-consensus-grandpa", + "sp-core", + "sp-runtime", + "substrate-prometheus-endpoint", + "thiserror", + "tokio", + "tokio-stream", +] + +[[package]] +name = "sc-network-transactions" +version = "0.33.0" +source = "git+https://github.com/gasp-xyz/polkadot-sdk?branch=eth-rollup-develop#e5bbc9b3eaa4f69cd39000bb69f6963f36dd93b8" +dependencies = [ + "array-bytes 6.2.3", + "futures", + "libp2p", + "log", + "parity-scale-codec", + "sc-network", + "sc-network-common", + "sc-network-sync", + "sc-utils", + "sp-consensus", + "sp-runtime", + "substrate-prometheus-endpoint", +] + +[[package]] +name = "sc-offchain" +version = "29.0.0" +source = "git+https://github.com/gasp-xyz/polkadot-sdk?branch=eth-rollup-develop#e5bbc9b3eaa4f69cd39000bb69f6963f36dd93b8" +dependencies = [ + "array-bytes 6.2.3", + "bytes", + "fnv", + "futures", + "futures-timer", + "hyper", + "hyper-rustls", + "libp2p", + "log", + "num_cpus", + "once_cell", + "parity-scale-codec", + "parking_lot 0.12.3", + "rand 0.8.5", + "sc-client-api", + "sc-network", + "sc-network-common", + "sc-transaction-pool-api", + "sc-utils", + "sp-api", + "sp-core", + "sp-externalities", + "sp-keystore", + "sp-offchain", + "sp-runtime", + "threadpool", + "tracing", +] + +[[package]] +name = "sc-proposer-metrics" +version = "0.17.0" +source = "git+https://github.com/gasp-xyz/polkadot-sdk?branch=eth-rollup-develop#e5bbc9b3eaa4f69cd39000bb69f6963f36dd93b8" +dependencies = [ + "log", + "substrate-prometheus-endpoint", +] + +[[package]] +name = "sc-rpc" +version = "29.0.0" +source = "git+https://github.com/gasp-xyz/polkadot-sdk?branch=eth-rollup-develop#e5bbc9b3eaa4f69cd39000bb69f6963f36dd93b8" +dependencies = [ + "futures", + "jsonrpsee", + "log", + "parity-scale-codec", + "parking_lot 0.12.3", + "sc-block-builder", + "sc-chain-spec", + "sc-client-api", + "sc-mixnet", + "sc-rpc-api", + "sc-tracing", + "sc-transaction-pool-api", + "sc-utils", + "serde_json", + "sp-api", + "sp-blockchain", + "sp-core", + "sp-keystore", + "sp-offchain", + "sp-rpc", + "sp-runtime", + "sp-session", + "sp-statement-store", + "sp-version", + "tokio", +] + +[[package]] +name = "sc-rpc-api" +version = "0.33.0" +source = "git+https://github.com/gasp-xyz/polkadot-sdk?branch=eth-rollup-develop#e5bbc9b3eaa4f69cd39000bb69f6963f36dd93b8" +dependencies = [ + "jsonrpsee", + "parity-scale-codec", + "sc-chain-spec", + "sc-mixnet", + "sc-transaction-pool-api", + "scale-info", + "serde", + "serde_json", + "sp-core", + "sp-rpc", + "sp-runtime", + "sp-version", + "thiserror", +] + +[[package]] +name = "sc-rpc-server" +version = "11.0.0" +source = "git+https://github.com/gasp-xyz/polkadot-sdk?branch=eth-rollup-develop#e5bbc9b3eaa4f69cd39000bb69f6963f36dd93b8" +dependencies = [ + "futures", + "governor", + "http", + "hyper", + "jsonrpsee", + "log", + "serde_json", + "substrate-prometheus-endpoint", + "tokio", + "tower", + "tower-http", +] + +[[package]] +name = "sc-rpc-spec-v2" +version = "0.34.0" +source = "git+https://github.com/gasp-xyz/polkadot-sdk?branch=eth-rollup-develop#e5bbc9b3eaa4f69cd39000bb69f6963f36dd93b8" +dependencies = [ + "array-bytes 6.2.3", + "futures", + "futures-util", + "hex", + "jsonrpsee", + "log", + "parity-scale-codec", + "parking_lot 0.12.3", + "rand 0.8.5", + "sc-chain-spec", + "sc-client-api", + "sc-rpc", + "sc-transaction-pool-api", + "sc-utils", + "serde", + "sp-api", + "sp-blockchain", + "sp-core", + "sp-rpc", + "sp-runtime", + "sp-version", + "thiserror", + "tokio", + "tokio-stream", +] + +[[package]] +name = "sc-service" +version = "0.35.0" +source = "git+https://github.com/gasp-xyz/polkadot-sdk?branch=eth-rollup-develop#e5bbc9b3eaa4f69cd39000bb69f6963f36dd93b8" +dependencies = [ + "async-trait", + "directories", + "exit-future", + "futures", + "futures-timer", + "jsonrpsee", + "log", + "parity-scale-codec", + "parking_lot 0.12.3", + "pin-project", + "rand 0.8.5", + "sc-chain-spec", + "sc-client-api", + "sc-client-db", + "sc-consensus", + "sc-executor", + "sc-informant", + "sc-keystore", + "sc-network", + "sc-network-bitswap", + "sc-network-common", + "sc-network-light", + "sc-network-sync", + "sc-network-transactions", + "sc-rpc", + "sc-rpc-server", + "sc-rpc-spec-v2", + "sc-sysinfo", + "sc-telemetry", + "sc-tracing", + "sc-transaction-pool", + "sc-transaction-pool-api", + "sc-utils", + "schnellru", + "serde", + "serde_json", + "sp-api", + "sp-blockchain", + "sp-consensus", + "sp-core", + "sp-externalities", + "sp-keystore", + "sp-runtime", + "sp-session", + "sp-state-machine", + "sp-storage", + "sp-transaction-pool", + "sp-transaction-storage-proof", + "sp-trie", + "sp-version", + "static_init", + "substrate-prometheus-endpoint", + "tempfile", + "thiserror", + "tokio", + "tracing", + "tracing-futures", + "ver-api", +] + +[[package]] +name = "sc-state-db" +version = "0.30.0" +source = "git+https://github.com/gasp-xyz/polkadot-sdk?branch=eth-rollup-develop#e5bbc9b3eaa4f69cd39000bb69f6963f36dd93b8" +dependencies = [ + "log", + "parity-scale-codec", + "parking_lot 0.12.3", + "sp-core", +] + +[[package]] +name = "sc-sysinfo" +version = "27.0.0" +source = "git+https://github.com/gasp-xyz/polkadot-sdk?branch=eth-rollup-develop#e5bbc9b3eaa4f69cd39000bb69f6963f36dd93b8" +dependencies = [ + "derive_more", + "futures", + "libc", + "log", + "rand 0.8.5", + "rand_pcg", + "regex", + "sc-telemetry", + "serde", + "serde_json", + "sp-core", + "sp-crypto-hashing", + "sp-io", + "sp-std", +] + +[[package]] +name = "sc-telemetry" +version = "15.0.0" +source = "git+https://github.com/gasp-xyz/polkadot-sdk?branch=eth-rollup-develop#e5bbc9b3eaa4f69cd39000bb69f6963f36dd93b8" +dependencies = [ + "chrono", + "futures", + "libp2p", + "log", + "parking_lot 0.12.3", + "pin-project", + "rand 0.8.5", + "sc-utils", + "serde", + "serde_json", + "thiserror", + "wasm-timer", +] + +[[package]] +name = "sc-tracing" +version = "28.0.0" +source = "git+https://github.com/gasp-xyz/polkadot-sdk?branch=eth-rollup-develop#e5bbc9b3eaa4f69cd39000bb69f6963f36dd93b8" +dependencies = [ + "ansi_term", + "chrono", + "is-terminal", + "lazy_static", + "libc", + "log", + "parity-scale-codec", + "parking_lot 0.12.3", + "regex", + "rustc-hash", + "sc-client-api", + "sc-tracing-proc-macro", + "serde", + "sp-api", + "sp-blockchain", + "sp-core", + "sp-rpc", + "sp-runtime", + "sp-tracing", + "thiserror", + "tracing", + "tracing-log", + "tracing-subscriber", +] + +[[package]] +name = "sc-tracing-proc-macro" +version = "11.0.0" +source = "git+https://github.com/gasp-xyz/polkadot-sdk?branch=eth-rollup-develop#e5bbc9b3eaa4f69cd39000bb69f6963f36dd93b8" +dependencies = [ + "proc-macro-crate 3.2.0", + "proc-macro2", + "quote", + "syn 2.0.77", +] + +[[package]] +name = "sc-transaction-pool" +version = "28.0.0" +source = "git+https://github.com/gasp-xyz/polkadot-sdk?branch=eth-rollup-develop#e5bbc9b3eaa4f69cd39000bb69f6963f36dd93b8" +dependencies = [ + "async-trait", + "futures", + "futures-timer", + "linked-hash-map", + "log", + "parity-scale-codec", + "parking_lot 0.12.3", + "sc-client-api", + "sc-transaction-pool-api", + "sc-utils", + "serde", + "sp-api", + "sp-blockchain", + "sp-core", + "sp-crypto-hashing", + "sp-runtime", + "sp-tracing", + "sp-transaction-pool", + "substrate-prometheus-endpoint", + "thiserror", +] + +[[package]] +name = "sc-transaction-pool-api" +version = "28.0.0" +source = "git+https://github.com/gasp-xyz/polkadot-sdk?branch=eth-rollup-develop#e5bbc9b3eaa4f69cd39000bb69f6963f36dd93b8" +dependencies = [ + "async-trait", + "futures", + "log", + "parity-scale-codec", + "serde", + "sp-blockchain", + "sp-core", + "sp-runtime", + "thiserror", +] + +[[package]] +name = "sc-utils" +version = "14.0.0" +source = "git+https://github.com/gasp-xyz/polkadot-sdk?branch=eth-rollup-develop#e5bbc9b3eaa4f69cd39000bb69f6963f36dd93b8" +dependencies = [ + "async-channel", + "futures", + "futures-timer", + "lazy_static", + "log", + "parking_lot 0.12.3", + "prometheus", + "sp-arithmetic", +] + +[[package]] +name = "scale-info" +version = "2.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eca070c12893629e2cc820a9761bedf6ce1dcddc9852984d1dc734b8bd9bd024" +dependencies = [ + "bitvec", + "cfg-if", + "derive_more", + "parity-scale-codec", + "scale-info-derive", + "serde", +] + +[[package]] +name = "scale-info-derive" +version = "2.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2d35494501194174bda522a32605929eefc9ecf7e0a326c26db1fdd85881eb62" +dependencies = [ + "proc-macro-crate 3.2.0", + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "schannel" +version = "0.1.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e9aaafd5a2b6e3d657ff009d82fbd630b6bd54dd4eb06f21693925cdf80f9b8b" +dependencies = [ + "windows-sys 0.59.0", +] + +[[package]] +name = "schnellru" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c9a8ef13a93c54d20580de1e5c413e624e53121d42fc7e2c11d10ef7f8b02367" +dependencies = [ + "ahash 0.8.11", + "cfg-if", + "hashbrown 0.13.2", +] + +[[package]] +name = "schnorrkel" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "021b403afe70d81eea68f6ea12f6b3c9588e5d536a94c3bf80f15e7faa267862" +dependencies = [ + "arrayref", + "arrayvec 0.5.2", + "curve25519-dalek 2.1.3", + "getrandom 0.1.16", + "merlin 2.0.1", + "rand 0.7.3", + "rand_core 0.5.1", + "sha2 0.8.2", + "subtle 2.6.1", + "zeroize", +] + +[[package]] +name = "schnorrkel" +version = "0.11.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8de18f6d8ba0aad7045f5feae07ec29899c1112584a38509a84ad7b04451eaa0" +dependencies = [ + "aead", + "arrayref", + "arrayvec 0.7.6", + "curve25519-dalek 4.1.1", + "getrandom_or_panic", + "merlin 3.0.0", + "rand_core 0.6.4", + "serde_bytes", + "sha2 0.10.8", + "subtle 2.6.1", + "zeroize", +] + +[[package]] +name = "scopeguard" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" + +[[package]] +name = "scratch" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a3cf7c11c38cb994f3d40e8a8cde3bbd1f72a435e4c49e85d6553d8312306152" + +[[package]] +name = "sct" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da046153aa2352493d6cb7da4b6e5c0c057d8a1d0a9aa8560baffdd945acd414" +dependencies = [ + "ring 0.17.8", + "untrusted 0.9.0", +] + +[[package]] +name = "sec1" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3e97a565f76233a6003f9f5c54be1d9c5bdfa3eccfb189469f11ec4901c47dc" +dependencies = [ + "base16ct", + "der", + "generic-array 0.14.7", + "pkcs8", + "serdect", + "subtle 2.6.1", + "zeroize", +] + +[[package]] +name = "secp256k1" +version = "0.28.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d24b59d129cdadea20aea4fb2352fa053712e5d713eee47d700cd4b2bc002f10" +dependencies = [ + "secp256k1-sys", +] + +[[package]] +name = "secp256k1-sys" +version = "0.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5d1746aae42c19d583c3c1a8c646bfad910498e2051c551a7f2e3c0c9fbb7eb" +dependencies = [ + "cc", +] + +[[package]] +name = "secrecy" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9bd1c54ea06cfd2f6b63219704de0b9b4f72dcc2b8fdef820be6cd799780e91e" +dependencies = [ + "zeroize", +] + +[[package]] +name = "security-framework" +version = "2.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "897b2245f0b511c87893af39b033e5ca9cce68824c4d7e7630b5a1d339658d02" +dependencies = [ + "bitflags 2.6.0", + "core-foundation", + "core-foundation-sys", + "libc", + "security-framework-sys", +] + +[[package]] +name = "security-framework-sys" +version = "2.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75da29fe9b9b08fe9d6b22b5b4bcbc75d8db3aa31e639aa56bb62e9d46bfceaf" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "semver" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a3186ec9e65071a2095434b1f5bb24838d4e8e130f584c790f6033c79943537" +dependencies = [ + "semver-parser 0.7.0", +] + +[[package]] +name = "semver" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f301af10236f6df4160f7c3f04eec6dbc70ace82d23326abad5edee88801c6b6" +dependencies = [ + "semver-parser 0.10.2", +] + +[[package]] +name = "semver" +version = "1.0.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "61697e0a1c7e512e84a621326239844a24d8207b4669b41bc18b32ea5cbf988b" +dependencies = [ + "serde", +] + +[[package]] +name = "semver-parser" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3" + +[[package]] +name = "semver-parser" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "00b0bef5b7f9e0df16536d3961cfb6e84331c065b4066afb39768d0e319411f7" +dependencies = [ + "pest", +] + +[[package]] +name = "send_wrapper" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f638d531eccd6e23b980caf34876660d38e265409d8e99b397ab71eb3612fad0" + +[[package]] +name = "serde" +version = "1.0.210" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8e3592472072e6e22e0a54d5904d9febf8508f65fb8552499a1abc7d1078c3a" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_bytes" +version = "0.11.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "387cc504cb06bb40a96c8e04e951fe01854cf6bc921053c954e4a606d9675c6a" +dependencies = [ + "serde", +] + +[[package]] +name = "serde_derive" +version = "1.0.210" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "243902eda00fad750862fc144cea25caca5e20d615af0a81bee94ca738f1df1f" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.77", +] + +[[package]] +name = "serde_json" +version = "1.0.128" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ff5456707a1de34e7e37f2a6fd3d3f808c318259cbd01ab6377795054b483d8" +dependencies = [ + "itoa", + "memchr", + "ryu", + "serde", +] + +[[package]] +name = "serde_spanned" +version = "0.6.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eb5b1b31579f3811bf615c144393417496f152e12ac8b7663bf664f4a815306d" +dependencies = [ + "serde", +] + +[[package]] +name = "serdect" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a84f14a19e9a014bb9f4512488d9829a68e04ecabffb0f9904cd1ace94598177" +dependencies = [ + "base16ct", + "serde", +] + +[[package]] +name = "serial_test" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5bcc41d18f7a1d50525d080fd3e953be87c4f9f1a974f3c21798ca00d54ec15" +dependencies = [ + "lazy_static", + "parking_lot 0.11.2", + "serial_test_derive", +] + +[[package]] +name = "serial_test_derive" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2881bccd7d60fb32dfa3d7b3136385312f8ad75e2674aab2852867a09790cae8" +dependencies = [ + "proc-macro-error", + "proc-macro2", + "quote", + "rustversion", + "syn 1.0.109", +] + +[[package]] +name = "sha-1" +version = "0.9.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "99cd6713db3cf16b6c84e06321e049a9b9f699826e16096d23bbcc44d15d51a6" +dependencies = [ + "block-buffer 0.9.0", + "cfg-if", + "cpufeatures", + "digest 0.9.0", + "opaque-debug 0.3.1", +] + +[[package]] +name = "sha2" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a256f46ea78a0c0d9ff00077504903ac881a1dafdc20da66545699e7776b3e69" +dependencies = [ + "block-buffer 0.7.3", + "digest 0.8.1", + "fake-simd", + "opaque-debug 0.2.3", +] + +[[package]] +name = "sha2" +version = "0.9.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4d58a1e1bf39749807d89cf2d98ac2dfa0ff1cb3faa38fbb64dd88ac8013d800" +dependencies = [ + "block-buffer 0.9.0", + "cfg-if", + "cpufeatures", + "digest 0.9.0", + "opaque-debug 0.3.1", +] + +[[package]] +name = "sha2" +version = "0.10.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "793db75ad2bcafc3ffa7c68b215fee268f537982cd901d132f89c6343f3a3dc8" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest 0.10.7", +] + +[[package]] +name = "sha3" +version = "0.10.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75872d278a8f37ef87fa0ddbda7802605cb18344497949862c0d4dcb291eba60" +dependencies = [ + "digest 0.10.7", + "keccak", +] + +[[package]] +name = "sha3-asm" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c28efc5e327c837aa837c59eae585fc250715ef939ac32881bcc11677cd02d46" +dependencies = [ + "cc", + "cfg-if", +] + +[[package]] +name = "sharded-slab" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f40ca3c46823713e0d4209592e8d6e826aa57e928f09752619fc696c499637f6" +dependencies = [ + "lazy_static", +] + +[[package]] +name = "shlex" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" + +[[package]] +name = "signal-hook-registry" +version = "1.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a9e9e0b4211b72e7b8b6e85c807d36c212bdb33ea8587f7569562a84df5465b1" +dependencies = [ + "libc", +] + +[[package]] +name = "signature" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77549399552de45a898a580c1b41d445bf730df867cc44e6c0233bbc4b8329de" +dependencies = [ + "digest 0.10.7", + "rand_core 0.6.4", +] + +[[package]] +name = "simba" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "061507c94fc6ab4ba1c9a0305018408e312e17c041eb63bef8aa726fa33aceae" +dependencies = [ + "approx", + "num-complex", + "num-traits", + "paste", + "wide", +] + +[[package]] +name = "similar" +version = "2.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1de1d4f81173b03af4c0cbed3c898f6bff5b870e4a7f5d6f4057d62a7a4b686e" +dependencies = [ + "bstr", + "unicode-segmentation", +] + +[[package]] +name = "similar-asserts" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cfe85670573cd6f0fa97940f26e7e6601213c3b0555246c24234131f88c5709e" +dependencies = [ + "console", + "similar", +] + +[[package]] +name = "simple-mermaid" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "620a1d43d70e142b1d46a929af51d44f383db9c7a2ec122de2cd992ccfcf3c18" + +[[package]] +name = "siphasher" +version = "0.3.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38b58827f4464d87d377d175e90bf58eb00fd8716ff0a62f80356b5e61555d0d" + +[[package]] +name = "slab" +version = "0.4.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f92a496fb766b417c996b9c5e57daf2f7ad3b0bebe1ccfca4856390e3d3bb67" +dependencies = [ + "autocfg", +] + +[[package]] +name = "slice-group-by" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "826167069c09b99d56f31e9ae5c99049e932a98c9dc2dac47645b08dbbf76ba7" + +[[package]] +name = "smallvec" +version = "1.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67" + +[[package]] +name = "snap" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b6b67fb9a61334225b5b790716f609cd58395f895b3fe8b328786812a40bc3b" + +[[package]] +name = "snow" +version = "0.9.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "850948bee068e713b8ab860fe1adc4d109676ab4c3b621fd8147f06b261f2f85" +dependencies = [ + "aes-gcm", + "blake2 0.10.6", + "chacha20poly1305", + "curve25519-dalek 4.1.1", + "rand_core 0.6.4", + "ring 0.17.8", + "rustc_version 0.4.1", + "sha2 0.10.8", + "subtle 2.6.1", +] + +[[package]] +name = "socket2" +version = "0.4.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f7916fc008ca5542385b89a3d3ce689953c143e9304a9bf8beec1de48994c0d" +dependencies = [ + "libc", + "winapi", +] + +[[package]] +name = "socket2" +version = "0.5.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce305eb0b4296696835b71df73eb912e0f1ffd2556a501fcede6e0c50349191c" +dependencies = [ + "libc", + "windows-sys 0.52.0", +] + +[[package]] +name = "soketto" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41d1c5305e39e09653383c2c7244f2f78b3bcae37cf50c64cb4789c9f5096ec2" +dependencies = [ + "base64 0.13.1", + "bytes", + "flate2", + "futures", + "http", + "httparse", + "log", + "rand 0.8.5", + "sha-1", +] + +[[package]] +name = "sp-api" +version = "26.0.0" +source = "git+https://github.com/gasp-xyz/polkadot-sdk?branch=eth-rollup-develop#e5bbc9b3eaa4f69cd39000bb69f6963f36dd93b8" +dependencies = [ + "hash-db", + "log", + "parity-scale-codec", + "scale-info", + "sp-api-proc-macro", + "sp-core", + "sp-externalities", + "sp-metadata-ir", + "sp-runtime", + "sp-runtime-interface", + "sp-state-machine", + "sp-std", + "sp-trie", + "sp-version", + "thiserror", +] + +[[package]] +name = "sp-api-proc-macro" +version = "15.0.0" +source = "git+https://github.com/gasp-xyz/polkadot-sdk?branch=eth-rollup-develop#e5bbc9b3eaa4f69cd39000bb69f6963f36dd93b8" +dependencies = [ + "Inflector", + "blake2 0.10.6", + "expander", + "proc-macro-crate 3.2.0", + "proc-macro2", + "quote", + "syn 2.0.77", +] + +[[package]] +name = "sp-application-crypto" +version = "30.0.0" +source = "git+https://github.com/gasp-xyz/polkadot-sdk?branch=eth-rollup-develop#e5bbc9b3eaa4f69cd39000bb69f6963f36dd93b8" +dependencies = [ + "parity-scale-codec", + "scale-info", + "serde", + "sp-core", + "sp-io", + "sp-std", +] + +[[package]] +name = "sp-arithmetic" +version = "23.0.0" +source = "git+https://github.com/gasp-xyz/polkadot-sdk?branch=eth-rollup-develop#e5bbc9b3eaa4f69cd39000bb69f6963f36dd93b8" +dependencies = [ + "integer-sqrt", + "num-traits", + "parity-scale-codec", + "scale-info", + "serde", + "sp-std", + "static_assertions", +] + +[[package]] +name = "sp-ark-bls12-381" +version = "0.4.2" +source = "git+https://github.com/paritytech/arkworks-substrate#caa2eed74beb885dd07c7db5f916f2281dad818f" +dependencies = [ + "ark-bls12-381-ext", + "sp-crypto-ec-utils", +] + +[[package]] +name = "sp-ark-ed-on-bls12-381-bandersnatch" +version = "0.4.2" +source = "git+https://github.com/paritytech/arkworks-substrate#caa2eed74beb885dd07c7db5f916f2281dad818f" +dependencies = [ + "ark-ed-on-bls12-381-bandersnatch-ext", + "sp-crypto-ec-utils", +] + +[[package]] +name = "sp-block-builder" +version = "26.0.0" +source = "git+https://github.com/gasp-xyz/polkadot-sdk?branch=eth-rollup-develop#e5bbc9b3eaa4f69cd39000bb69f6963f36dd93b8" +dependencies = [ + "sp-api", + "sp-inherents", + "sp-runtime", + "sp-std", +] + +[[package]] +name = "sp-blockchain" +version = "28.0.0" +source = "git+https://github.com/gasp-xyz/polkadot-sdk?branch=eth-rollup-develop#e5bbc9b3eaa4f69cd39000bb69f6963f36dd93b8" +dependencies = [ + "futures", + "log", + "parity-scale-codec", + "parking_lot 0.12.3", + "schnellru", + "sp-api", + "sp-consensus", + "sp-database", + "sp-runtime", + "sp-state-machine", + "thiserror", +] + +[[package]] +name = "sp-consensus" +version = "0.32.0" +source = "git+https://github.com/gasp-xyz/polkadot-sdk?branch=eth-rollup-develop#e5bbc9b3eaa4f69cd39000bb69f6963f36dd93b8" +dependencies = [ + "async-trait", + "futures", + "log", + "sp-core", + "sp-inherents", + "sp-runtime", + "sp-state-machine", + "thiserror", +] + +[[package]] +name = "sp-consensus-aura" +version = "0.32.0" +source = "git+https://github.com/gasp-xyz/polkadot-sdk?branch=eth-rollup-develop#e5bbc9b3eaa4f69cd39000bb69f6963f36dd93b8" +dependencies = [ + "async-trait", + "parity-scale-codec", + "scale-info", + "sp-api", + "sp-application-crypto", + "sp-consensus-slots", + "sp-inherents", + "sp-runtime", + "sp-std", + "sp-timestamp", +] + +[[package]] +name = "sp-consensus-babe" +version = "0.32.0" +source = "git+https://github.com/gasp-xyz/polkadot-sdk?branch=eth-rollup-develop#e5bbc9b3eaa4f69cd39000bb69f6963f36dd93b8" +dependencies = [ + "async-trait", + "parity-scale-codec", + "scale-info", + "serde", + "sp-api", + "sp-application-crypto", + "sp-consensus-slots", + "sp-core", + "sp-inherents", + "sp-runtime", + "sp-std", + "sp-timestamp", +] + +[[package]] +name = "sp-consensus-grandpa" +version = "13.0.0" +source = "git+https://github.com/gasp-xyz/polkadot-sdk?branch=eth-rollup-develop#e5bbc9b3eaa4f69cd39000bb69f6963f36dd93b8" +dependencies = [ + "finality-grandpa", + "log", + "parity-scale-codec", + "scale-info", + "serde", + "sp-api", + "sp-application-crypto", + "sp-core", + "sp-keystore", + "sp-runtime", + "sp-std", +] + +[[package]] +name = "sp-consensus-slots" +version = "0.32.0" +source = "git+https://github.com/gasp-xyz/polkadot-sdk?branch=eth-rollup-develop#e5bbc9b3eaa4f69cd39000bb69f6963f36dd93b8" +dependencies = [ + "parity-scale-codec", + "scale-info", + "serde", + "sp-std", + "sp-timestamp", +] + +[[package]] +name = "sp-core" +version = "28.0.0" +source = "git+https://github.com/gasp-xyz/polkadot-sdk?branch=eth-rollup-develop#e5bbc9b3eaa4f69cd39000bb69f6963f36dd93b8" +dependencies = [ + "array-bytes 6.2.3", + "bandersnatch_vrfs", + "bitflags 1.3.2", + "blake2 0.10.6", + "bounded-collections", + "bs58 0.5.1", + "dyn-clonable", + "ed25519-zebra", + "futures", + "hash-db", + "hash256-std-hasher", + "impl-serde 0.4.0", + "itertools 0.10.5", + "k256", + "libsecp256k1", + "log", + "merlin 3.0.0", + "parity-bip39", + "parity-scale-codec", + "parking_lot 0.12.3", + "paste", + "primitive-types", + "rand 0.8.5", + "scale-info", + "schnorrkel 0.11.4", + "secp256k1", + "secrecy", + "serde", + "sp-crypto-hashing", + "sp-debug-derive", + "sp-externalities", + "sp-runtime-interface", + "sp-std", + "sp-storage", + "ss58-registry", + "substrate-bip39", + "thiserror", + "tracing", + "w3f-bls", + "zeroize", +] + +[[package]] +name = "sp-crypto-ec-utils" +version = "0.10.0" +source = "git+https://github.com/gasp-xyz/polkadot-sdk?branch=eth-rollup-develop#6b71a3c1276a1e0d8a75eaf5932d9afb82bb477a" +dependencies = [ + "ark-bls12-377", + "ark-bls12-377-ext", + "ark-bls12-381", + "ark-bls12-381-ext", + "ark-bw6-761", + "ark-bw6-761-ext", + "ark-ec 0.4.2", + "ark-ed-on-bls12-377", + "ark-ed-on-bls12-377-ext", + "ark-ed-on-bls12-381-bandersnatch", + "ark-ed-on-bls12-381-bandersnatch-ext", + "ark-scale", + "sp-runtime-interface", + "sp-std", +] + +[[package]] +name = "sp-crypto-hashing" +version = "0.0.0" +source = "git+https://github.com/gasp-xyz/polkadot-sdk?branch=eth-rollup-develop#e5bbc9b3eaa4f69cd39000bb69f6963f36dd93b8" +dependencies = [ + "blake2b_simd", + "byteorder", + "digest 0.10.7", + "sha2 0.10.8", + "sha3", + "twox-hash", +] + +[[package]] +name = "sp-crypto-hashing-proc-macro" +version = "0.0.0" +source = "git+https://github.com/gasp-xyz/polkadot-sdk?branch=eth-rollup-develop#e5bbc9b3eaa4f69cd39000bb69f6963f36dd93b8" +dependencies = [ + "quote", + "sp-crypto-hashing", + "syn 2.0.77", +] + +[[package]] +name = "sp-database" +version = "10.0.0" +source = "git+https://github.com/gasp-xyz/polkadot-sdk?branch=eth-rollup-develop#e5bbc9b3eaa4f69cd39000bb69f6963f36dd93b8" +dependencies = [ + "kvdb", + "parking_lot 0.12.3", +] + +[[package]] +name = "sp-debug-derive" +version = "14.0.0" +source = "git+https://github.com/gasp-xyz/polkadot-sdk?branch=eth-rollup-develop#e5bbc9b3eaa4f69cd39000bb69f6963f36dd93b8" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.77", +] + +[[package]] +name = "sp-externalities" +version = "0.25.0" +source = "git+https://github.com/gasp-xyz/polkadot-sdk?branch=eth-rollup-develop#e5bbc9b3eaa4f69cd39000bb69f6963f36dd93b8" +dependencies = [ + "environmental", + "parity-scale-codec", + "sp-std", + "sp-storage", +] + +[[package]] +name = "sp-genesis-builder" +version = "0.7.0" +source = "git+https://github.com/gasp-xyz/polkadot-sdk?branch=eth-rollup-develop#e5bbc9b3eaa4f69cd39000bb69f6963f36dd93b8" +dependencies = [ + "serde_json", + "sp-api", + "sp-runtime", + "sp-std", +] + +[[package]] +name = "sp-inherents" +version = "26.0.0" +source = "git+https://github.com/gasp-xyz/polkadot-sdk?branch=eth-rollup-develop#e5bbc9b3eaa4f69cd39000bb69f6963f36dd93b8" +dependencies = [ + "async-trait", + "impl-trait-for-tuples", + "parity-scale-codec", + "scale-info", + "sp-runtime", + "sp-std", + "thiserror", +] + +[[package]] +name = "sp-io" +version = "30.0.0" +source = "git+https://github.com/gasp-xyz/polkadot-sdk?branch=eth-rollup-develop#e5bbc9b3eaa4f69cd39000bb69f6963f36dd93b8" +dependencies = [ + "bytes", + "ed25519-dalek", + "libsecp256k1", + "log", + "parity-scale-codec", + "polkavm-derive", + "rustversion", + "secp256k1", + "sp-core", + "sp-crypto-hashing", + "sp-externalities", + "sp-keystore", + "sp-runtime-interface", + "sp-state-machine", + "sp-std", + "sp-tracing", + "sp-trie", + "tracing", + "tracing-core", +] + +[[package]] +name = "sp-keyring" +version = "31.0.0" +source = "git+https://github.com/gasp-xyz/polkadot-sdk?branch=eth-rollup-develop#e5bbc9b3eaa4f69cd39000bb69f6963f36dd93b8" +dependencies = [ + "sp-core", + "sp-runtime", + "strum 0.24.1", +] + +[[package]] +name = "sp-keystore" +version = "0.34.0" +source = "git+https://github.com/gasp-xyz/polkadot-sdk?branch=eth-rollup-develop#e5bbc9b3eaa4f69cd39000bb69f6963f36dd93b8" +dependencies = [ + "parity-scale-codec", + "parking_lot 0.12.3", + "sp-core", + "sp-externalities", +] + +[[package]] +name = "sp-maybe-compressed-blob" +version = "11.0.0" +source = "git+https://github.com/gasp-xyz/polkadot-sdk?branch=eth-rollup-develop#e5bbc9b3eaa4f69cd39000bb69f6963f36dd93b8" +dependencies = [ + "thiserror", + "zstd 0.12.4", +] + +[[package]] +name = "sp-metadata-ir" +version = "0.6.0" +source = "git+https://github.com/gasp-xyz/polkadot-sdk?branch=eth-rollup-develop#e5bbc9b3eaa4f69cd39000bb69f6963f36dd93b8" +dependencies = [ + "frame-metadata", + "parity-scale-codec", + "scale-info", + "sp-std", +] + +[[package]] +name = "sp-mixnet" +version = "0.4.0" +source = "git+https://github.com/gasp-xyz/polkadot-sdk?branch=eth-rollup-develop#e5bbc9b3eaa4f69cd39000bb69f6963f36dd93b8" +dependencies = [ + "parity-scale-codec", + "scale-info", + "sp-api", + "sp-application-crypto", + "sp-std", +] + +[[package]] +name = "sp-offchain" +version = "26.0.0" +source = "git+https://github.com/gasp-xyz/polkadot-sdk?branch=eth-rollup-develop#e5bbc9b3eaa4f69cd39000bb69f6963f36dd93b8" +dependencies = [ + "sp-api", + "sp-core", + "sp-runtime", +] + +[[package]] +name = "sp-panic-handler" +version = "13.0.0" +source = "git+https://github.com/gasp-xyz/polkadot-sdk?branch=eth-rollup-develop#e5bbc9b3eaa4f69cd39000bb69f6963f36dd93b8" +dependencies = [ + "backtrace", + "lazy_static", + "regex", +] + +[[package]] +name = "sp-rpc" +version = "26.0.0" +source = "git+https://github.com/gasp-xyz/polkadot-sdk?branch=eth-rollup-develop#e5bbc9b3eaa4f69cd39000bb69f6963f36dd93b8" +dependencies = [ + "rustc-hash", + "serde", + "sp-core", +] + +[[package]] +name = "sp-runtime" +version = "31.0.1" +source = "git+https://github.com/gasp-xyz/polkadot-sdk?branch=eth-rollup-develop#e5bbc9b3eaa4f69cd39000bb69f6963f36dd93b8" +dependencies = [ + "alloy-primitives", + "alloy-sol-types", + "blake2-rfc", + "docify", + "either", + "hash256-std-hasher", + "impl-serde 0.3.2", + "impl-trait-for-tuples", + "libsecp256k1", + "log", + "parity-scale-codec", + "parity-util-mem", + "paste", + "rand 0.8.5", + "scale-info", + "serde", + "sha3", + "simple-mermaid", + "sp-application-crypto", + "sp-arithmetic", + "sp-core", + "sp-io", + "sp-std", + "sp-weights", +] + +[[package]] +name = "sp-runtime-interface" +version = "24.0.0" +source = "git+https://github.com/gasp-xyz/polkadot-sdk?branch=eth-rollup-develop#e5bbc9b3eaa4f69cd39000bb69f6963f36dd93b8" +dependencies = [ + "bytes", + "impl-trait-for-tuples", + "parity-scale-codec", + "polkavm-derive", + "primitive-types", + "sp-externalities", + "sp-runtime-interface-proc-macro", + "sp-std", + "sp-storage", + "sp-tracing", + "sp-wasm-interface", + "static_assertions", +] + +[[package]] +name = "sp-runtime-interface-proc-macro" +version = "17.0.0" +source = "git+https://github.com/gasp-xyz/polkadot-sdk?branch=eth-rollup-develop#e5bbc9b3eaa4f69cd39000bb69f6963f36dd93b8" +dependencies = [ + "Inflector", + "expander", + "proc-macro-crate 3.2.0", + "proc-macro2", + "quote", + "syn 2.0.77", +] + +[[package]] +name = "sp-session" +version = "27.0.0" +source = "git+https://github.com/gasp-xyz/polkadot-sdk?branch=eth-rollup-develop#e5bbc9b3eaa4f69cd39000bb69f6963f36dd93b8" +dependencies = [ + "parity-scale-codec", + "scale-info", + "sp-api", + "sp-core", + "sp-keystore", + "sp-runtime", + "sp-staking", + "sp-std", +] + +[[package]] +name = "sp-staking" +version = "26.0.0" +source = "git+https://github.com/gasp-xyz/polkadot-sdk?branch=eth-rollup-develop#e5bbc9b3eaa4f69cd39000bb69f6963f36dd93b8" +dependencies = [ + "impl-trait-for-tuples", + "parity-scale-codec", + "scale-info", + "serde", + "sp-core", + "sp-runtime", + "sp-std", +] + +[[package]] +name = "sp-state-machine" +version = "0.35.0" +source = "git+https://github.com/gasp-xyz/polkadot-sdk?branch=eth-rollup-develop#e5bbc9b3eaa4f69cd39000bb69f6963f36dd93b8" +dependencies = [ + "hash-db", + "log", + "parity-scale-codec", + "parking_lot 0.12.3", + "rand 0.8.5", + "smallvec", + "sp-core", + "sp-externalities", + "sp-panic-handler", + "sp-std", + "sp-trie", + "thiserror", + "tracing", + "trie-db", +] + +[[package]] +name = "sp-statement-store" +version = "10.0.0" +source = "git+https://github.com/gasp-xyz/polkadot-sdk?branch=eth-rollup-develop#e5bbc9b3eaa4f69cd39000bb69f6963f36dd93b8" +dependencies = [ + "aes-gcm", + "curve25519-dalek 4.1.1", + "ed25519-dalek", + "hkdf", + "parity-scale-codec", + "rand 0.8.5", + "scale-info", + "sha2 0.10.8", + "sp-api", + "sp-application-crypto", + "sp-core", + "sp-crypto-hashing", + "sp-externalities", + "sp-runtime", + "sp-runtime-interface", + "sp-std", + "thiserror", + "x25519-dalek 2.0.1", +] + +[[package]] +name = "sp-std" +version = "14.0.0" +source = "git+https://github.com/gasp-xyz/polkadot-sdk?branch=eth-rollup-develop#e5bbc9b3eaa4f69cd39000bb69f6963f36dd93b8" + +[[package]] +name = "sp-storage" +version = "19.0.0" +source = "git+https://github.com/gasp-xyz/polkadot-sdk?branch=eth-rollup-develop#e5bbc9b3eaa4f69cd39000bb69f6963f36dd93b8" +dependencies = [ + "impl-serde 0.4.0", + "parity-scale-codec", + "ref-cast", + "serde", + "sp-debug-derive", + "sp-std", +] + +[[package]] +name = "sp-timestamp" +version = "26.0.0" +source = "git+https://github.com/gasp-xyz/polkadot-sdk?branch=eth-rollup-develop#e5bbc9b3eaa4f69cd39000bb69f6963f36dd93b8" +dependencies = [ + "async-trait", + "parity-scale-codec", + "sp-inherents", + "sp-runtime", + "sp-std", + "thiserror", +] + +[[package]] +name = "sp-tracing" +version = "16.0.0" +source = "git+https://github.com/gasp-xyz/polkadot-sdk?branch=eth-rollup-develop#e5bbc9b3eaa4f69cd39000bb69f6963f36dd93b8" +dependencies = [ + "parity-scale-codec", + "sp-std", + "tracing", + "tracing-core", + "tracing-subscriber", +] + +[[package]] +name = "sp-transaction-pool" +version = "26.0.0" +source = "git+https://github.com/gasp-xyz/polkadot-sdk?branch=eth-rollup-develop#e5bbc9b3eaa4f69cd39000bb69f6963f36dd93b8" +dependencies = [ + "sp-api", + "sp-runtime", +] + +[[package]] +name = "sp-transaction-storage-proof" +version = "26.0.0" +source = "git+https://github.com/gasp-xyz/polkadot-sdk?branch=eth-rollup-develop#e5bbc9b3eaa4f69cd39000bb69f6963f36dd93b8" +dependencies = [ + "async-trait", + "parity-scale-codec", + "scale-info", + "sp-core", + "sp-inherents", + "sp-runtime", + "sp-std", + "sp-trie", +] + +[[package]] +name = "sp-trie" +version = "29.0.0" +source = "git+https://github.com/gasp-xyz/polkadot-sdk?branch=eth-rollup-develop#e5bbc9b3eaa4f69cd39000bb69f6963f36dd93b8" +dependencies = [ + "ahash 0.8.11", + "hash-db", + "lazy_static", + "memory-db", + "nohash-hasher", + "parity-scale-codec", + "parking_lot 0.12.3", + "rand 0.8.5", + "scale-info", + "schnellru", + "sp-core", + "sp-externalities", + "sp-std", + "thiserror", + "tracing", + "trie-db", + "trie-root", +] + +[[package]] +name = "sp-ver" +version = "4.0.0-dev" +source = "git+https://github.com/gasp-xyz/polkadot-sdk?branch=eth-rollup-develop#e5bbc9b3eaa4f69cd39000bb69f6963f36dd93b8" +dependencies = [ + "async-trait", + "parity-scale-codec", + "scale-info", + "schnorrkel 0.9.1", + "serde", + "sp-core", + "sp-inherents", + "sp-keystore", + "sp-runtime", + "sp-std", +] + +[[package]] +name = "sp-version" +version = "29.0.0" +source = "git+https://github.com/gasp-xyz/polkadot-sdk?branch=eth-rollup-develop#e5bbc9b3eaa4f69cd39000bb69f6963f36dd93b8" +dependencies = [ + "impl-serde 0.4.0", + "parity-scale-codec", + "parity-wasm", + "scale-info", + "serde", + "sp-crypto-hashing-proc-macro", + "sp-runtime", + "sp-std", + "sp-version-proc-macro", + "thiserror", +] + +[[package]] +name = "sp-version-proc-macro" +version = "13.0.0" +source = "git+https://github.com/gasp-xyz/polkadot-sdk?branch=eth-rollup-develop#e5bbc9b3eaa4f69cd39000bb69f6963f36dd93b8" +dependencies = [ + "parity-scale-codec", + "proc-macro2", + "quote", + "syn 2.0.77", +] + +[[package]] +name = "sp-wasm-interface" +version = "20.0.0" +source = "git+https://github.com/gasp-xyz/polkadot-sdk?branch=eth-rollup-develop#e5bbc9b3eaa4f69cd39000bb69f6963f36dd93b8" +dependencies = [ + "anyhow", + "impl-trait-for-tuples", + "log", + "parity-scale-codec", + "sp-std", + "wasmtime", +] + +[[package]] +name = "sp-weights" +version = "27.0.0" +source = "git+https://github.com/gasp-xyz/polkadot-sdk?branch=eth-rollup-develop#e5bbc9b3eaa4f69cd39000bb69f6963f36dd93b8" +dependencies = [ + "bounded-collections", + "parity-scale-codec", + "scale-info", + "serde", + "smallvec", + "sp-arithmetic", + "sp-debug-derive", + "sp-std", +] + +[[package]] +name = "spin" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d" + +[[package]] +name = "spin" +version = "0.9.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" + +[[package]] +name = "spinners" +version = "4.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a0ef947f358b9c238923f764c72a4a9d42f2d637c46e059dbd319d6e7cfb4f82" +dependencies = [ + "lazy_static", + "maplit", + "strum 0.24.1", +] + +[[package]] +name = "spinning_top" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d96d2d1d716fb500937168cc09353ffdc7a012be8475ac7308e1bdf0e3923300" +dependencies = [ + "lock_api", +] + +[[package]] +name = "spki" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d91ed6c858b01f942cd56b37a94b3e0a1798290327d1236e4d9cf4eaca44d29d" +dependencies = [ + "base64ct", + "der", +] + +[[package]] +name = "ss58-registry" +version = "1.50.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43fce22ed1df64d04b262351c8f9d5c6da4f76f79f25ad15529792f893fad25d" +dependencies = [ + "Inflector", + "num-format", + "proc-macro2", + "quote", + "serde", + "serde_json", + "unicode-xid", +] + +[[package]] +name = "stable_deref_trait" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" + +[[package]] +name = "static_assertions" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" + +[[package]] +name = "static_init" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a2a1c578e98c1c16fc3b8ec1328f7659a500737d7a0c6d625e73e830ff9c1f6" +dependencies = [ + "bitflags 1.3.2", + "cfg_aliases", + "libc", + "parking_lot 0.11.2", + "parking_lot_core 0.8.6", + "static_init_macro", + "winapi", +] + +[[package]] +name = "static_init_macro" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70a2595fc3aa78f2d0e45dd425b22282dd863273761cc77780914b2cf3003acf" +dependencies = [ + "cfg_aliases", + "memchr", + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "strsim" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" + +[[package]] +name = "strum" +version = "0.24.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "063e6045c0e62079840579a7e47a355ae92f60eb74daaf156fb1e84ba164e63f" +dependencies = [ + "strum_macros 0.24.3", +] + +[[package]] +name = "strum" +version = "0.26.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8fec0f0aef304996cf250b31b5a10dee7980c85da9d759361292b8bca5a18f06" + +[[package]] +name = "strum_macros" +version = "0.24.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e385be0d24f186b4ce2f9982191e7101bb737312ad61c1f2f984f34bcf85d59" +dependencies = [ + "heck 0.4.1", + "proc-macro2", + "quote", + "rustversion", + "syn 1.0.109", +] + +[[package]] +name = "strum_macros" +version = "0.26.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c6bee85a5a24955dc440386795aa378cd9cf82acd5f764469152d2270e581be" +dependencies = [ + "heck 0.5.0", + "proc-macro2", + "quote", + "rustversion", + "syn 2.0.77", +] + +[[package]] +name = "substrate-bip39" +version = "0.4.7" +source = "git+https://github.com/gasp-xyz/polkadot-sdk?branch=eth-rollup-develop#e5bbc9b3eaa4f69cd39000bb69f6963f36dd93b8" +dependencies = [ + "hmac 0.12.1", + "pbkdf2", + "schnorrkel 0.11.4", + "sha2 0.10.8", + "zeroize", +] + +[[package]] +name = "substrate-build-script-utils" +version = "11.0.0" +source = "git+https://github.com/gasp-xyz/polkadot-sdk?branch=eth-rollup-develop#e5bbc9b3eaa4f69cd39000bb69f6963f36dd93b8" + +[[package]] +name = "substrate-prometheus-endpoint" +version = "0.17.0" +source = "git+https://github.com/gasp-xyz/polkadot-sdk?branch=eth-rollup-develop#e5bbc9b3eaa4f69cd39000bb69f6963f36dd93b8" +dependencies = [ + "hyper", + "log", + "prometheus", + "thiserror", + "tokio", +] + +[[package]] +name = "substrate-rpc-client" +version = "0.33.0" +source = "git+https://github.com/gasp-xyz/polkadot-sdk?branch=eth-rollup-develop#6b71a3c1276a1e0d8a75eaf5932d9afb82bb477a" +dependencies = [ + "async-trait", + "jsonrpsee", + "log", + "sc-rpc-api", + "serde", + "sp-runtime", +] + +[[package]] +name = "substrate-test-client" +version = "2.0.1" +source = "git+https://github.com/gasp-xyz/polkadot-sdk?branch=eth-rollup-develop#e5bbc9b3eaa4f69cd39000bb69f6963f36dd93b8" +dependencies = [ + "array-bytes 6.2.3", + "async-trait", + "futures", + "parity-scale-codec", + "sc-client-api", + "sc-client-db", + "sc-consensus", + "sc-executor", + "sc-offchain", + "sc-service", + "serde", + "serde_json", + "sp-blockchain", + "sp-consensus", + "sp-core", + "sp-keyring", + "sp-keystore", + "sp-runtime", + "sp-state-machine", + "tokio", +] + +[[package]] +name = "substrate-test-runtime" +version = "2.0.0" +source = "git+https://github.com/gasp-xyz/polkadot-sdk?branch=eth-rollup-develop#e5bbc9b3eaa4f69cd39000bb69f6963f36dd93b8" +dependencies = [ + "array-bytes 6.2.3", + "frame-executive", + "frame-support", + "frame-system", + "frame-system-rpc-runtime-api", + "log", + "pallet-babe", + "pallet-balances", + "pallet-timestamp", + "parity-scale-codec", + "sc-service", + "scale-info", + "sp-api", + "sp-application-crypto", + "sp-block-builder", + "sp-consensus-aura", + "sp-consensus-babe", + "sp-consensus-grandpa", + "sp-core", + "sp-crypto-hashing", + "sp-externalities", + "sp-genesis-builder", + "sp-inherents", + "sp-io", + "sp-keyring", + "sp-offchain", + "sp-runtime", + "sp-session", + "sp-state-machine", + "sp-std", + "sp-transaction-pool", + "sp-trie", + "sp-version", + "substrate-wasm-builder", + "trie-db", + "ver-api", +] + +[[package]] +name = "substrate-test-runtime-client" +version = "2.0.0" +source = "git+https://github.com/gasp-xyz/polkadot-sdk?branch=eth-rollup-develop#e5bbc9b3eaa4f69cd39000bb69f6963f36dd93b8" +dependencies = [ + "futures", + "sc-block-builder", + "sc-client-api", + "sc-consensus", + "sp-api", + "sp-blockchain", + "sp-consensus", + "sp-core", + "sp-runtime", + "substrate-test-client", + "substrate-test-runtime", + "ver-api", +] + +[[package]] +name = "substrate-wasm-builder" +version = "17.0.0" +source = "git+https://github.com/gasp-xyz/polkadot-sdk?branch=eth-rollup-develop#e5bbc9b3eaa4f69cd39000bb69f6963f36dd93b8" +dependencies = [ + "build-helper", + "cargo_metadata", + "console", + "filetime", + "parity-wasm", + "polkavm-linker", + "sp-maybe-compressed-blob", + "strum 0.24.1", + "tempfile", + "toml 0.8.19", + "walkdir", + "wasm-opt", +] + +[[package]] +name = "subtle" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2d67a5a62ba6e01cb2192ff309324cb4875d0c451d55fe2319433abe7a05a8ee" + +[[package]] +name = "subtle" +version = "2.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" + +[[package]] +name = "syn" +version = "1.0.109" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "syn" +version = "2.0.77" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f35bcdf61fd8e7be6caf75f429fdca8beb3ed76584befb503b1569faee373ed" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "syn-solidity" +version = "0.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91ede2e5b2c6bfac4bc0ff4499957a11725dc12a7ddb86270e827ef575892553" +dependencies = [ + "paste", + "proc-macro2", + "quote", + "syn 2.0.77", +] + +[[package]] +name = "synstructure" +version = "0.12.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f36bdaa60a83aca3921b5259d5400cbf5e90fc51931376a9bd4a0eb79aa7210f" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", + "unicode-xid", +] + +[[package]] +name = "system-configuration" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba3a3adc5c275d719af8cb4272ea1c4a6d668a777f37e115f6d11ddbc1c8e0e7" +dependencies = [ + "bitflags 1.3.2", + "core-foundation", + "system-configuration-sys", +] + +[[package]] +name = "system-configuration-sys" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a75fb188eb626b924683e3b95e3a48e63551fcfb51949de2f06a9d91dbee93c9" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "tap" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369" + +[[package]] +name = "target-lexicon" +version = "0.12.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "61c41af27dd6d1e27b1b16b489db798443478cef1f06a660c96db617ba5de3b1" + +[[package]] +name = "tempfile" +version = "3.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "04cbcdd0c794ebb0d4cf35e88edd2f7d2c4c3e9a5a6dab322839b321c6a87a64" +dependencies = [ + "cfg-if", + "fastrand", + "once_cell", + "rustix 0.38.37", + "windows-sys 0.59.0", +] + +[[package]] +name = "termcolor" +version = "1.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06794f8f6c5c898b3275aebefa6b8a1cb24cd2c6c79397ab15774837a0bc5755" +dependencies = [ + "winapi-util", +] + +[[package]] +name = "terminal_size" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "21bebf2b7c9e0a515f6e0f8c51dc0f8e4696391e6f1ff30379559f8365fb0df7" +dependencies = [ + "rustix 0.38.37", + "windows-sys 0.48.0", +] + +[[package]] +name = "termtree" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3369f5ac52d5eb6ab48c6b4ffdc8efbcad6b89c765749064ba298f2c68a16a76" + +[[package]] +name = "test-case" +version = "2.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "21d6cf5a7dffb3f9dceec8e6b8ca528d9bd71d36c9f074defb548ce161f598c0" +dependencies = [ + "test-case-macros", +] + +[[package]] +name = "test-case-macros" +version = "2.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e45b7bf6e19353ddd832745c8fcf77a17a93171df7151187f26623f2b75b5b26" +dependencies = [ + "cfg-if", + "proc-macro-error", + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "thiserror" +version = "1.0.63" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0342370b38b6a11b6cc11d6a805569958d54cfa061a29969c3b5ce2ea405724" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.63" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4558b58466b9ad7ca0f102865eccc95938dca1a74a856f2b57b6629050da261" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.77", +] + +[[package]] +name = "thousands" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3bf63baf9f5039dadc247375c29eb13706706cfde997d0330d05aa63a77d8820" + +[[package]] +name = "thread_local" +version = "1.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b9ef9bad013ada3808854ceac7b46812a6465ba368859a37e2100283d2d719c" +dependencies = [ + "cfg-if", + "once_cell", +] + +[[package]] +name = "threadpool" +version = "1.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d050e60b33d41c19108b32cea32164033a9013fe3b46cbd4457559bfbf77afaa" +dependencies = [ + "num_cpus", +] + +[[package]] +name = "tikv-jemalloc-sys" +version = "0.5.4+5.3.0-patched" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9402443cb8fd499b6f327e40565234ff34dbda27460c5b47db0db77443dd85d1" +dependencies = [ + "cc", + "libc", +] + +[[package]] +name = "time" +version = "0.3.36" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5dfd88e563464686c916c7e46e623e520ddc6d79fa6641390f2e3fa86e83e885" +dependencies = [ + "deranged", + "itoa", + "num-conv", + "powerfmt", + "serde", + "time-core", + "time-macros", +] + +[[package]] +name = "time-core" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef927ca75afb808a4d64dd374f00a2adf8d0fcff8e7b184af886c3c87ec4a3f3" + +[[package]] +name = "time-macros" +version = "0.2.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f252a68540fde3a3877aeea552b832b40ab9a69e318efd078774a01ddee1ccf" +dependencies = [ + "num-conv", + "time-core", +] + +[[package]] +name = "tiny-keccak" +version = "2.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c9d3793400a45f954c52e73d068316d76b6f4e36977e3fcebb13a2721e80237" +dependencies = [ + "crunchy", +] + +[[package]] +name = "tinyvec" +version = "1.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "445e881f4f6d382d5f27c034e25eb92edd7c784ceab92a0937db7f2e9471b938" +dependencies = [ + "tinyvec_macros", +] + +[[package]] +name = "tinyvec_macros" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" + +[[package]] +name = "tokio" +version = "1.40.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2b070231665d27ad9ec9b8df639893f46727666c6767db40317fbe920a5d998" +dependencies = [ + "backtrace", + "bytes", + "libc", + "mio", + "parking_lot 0.12.3", + "pin-project-lite 0.2.14", + "signal-hook-registry", + "socket2 0.5.7", + "tokio-macros", + "windows-sys 0.52.0", +] + +[[package]] +name = "tokio-macros" +version = "2.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "693d596312e88961bc67d7f1f97af8a70227d9f90c31bba5806eec004978d752" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.77", +] + +[[package]] +name = "tokio-retry" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f57eb36ecbe0fc510036adff84824dd3c24bb781e21bfa67b69d556aa85214f" +dependencies = [ + "pin-project", + "rand 0.8.5", + "tokio", +] + +[[package]] +name = "tokio-rustls" +version = "0.24.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c28327cf380ac148141087fbfb9de9d7bd4e84ab5d2c28fbc911d753de8a7081" +dependencies = [ + "rustls 0.21.12", + "tokio", +] + +[[package]] +name = "tokio-rustls" +version = "0.25.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "775e0c0f0adb3a2f22a00c4745d728b479985fc15ee7ca6a2608388c5569860f" +dependencies = [ + "rustls 0.22.4", + "rustls-pki-types", + "tokio", +] + +[[package]] +name = "tokio-stream" +version = "0.1.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4f4e6ce100d0eb49a2734f8c0812bcd324cf357d21810932c5df6b96ef2b86f1" +dependencies = [ + "futures-core", + "pin-project-lite 0.2.14", + "tokio", + "tokio-util", +] + +[[package]] +name = "tokio-util" +version = "0.7.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "61e7c3654c13bcd040d4a03abee2c75b1d14a37b423cf5a813ceae1cc903ec6a" +dependencies = [ + "bytes", + "futures-core", + "futures-io", + "futures-sink", + "pin-project-lite 0.2.14", + "tokio", +] + +[[package]] +name = "toml" +version = "0.5.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f4f7f0dd8d50a853a531c426359045b1998f04219d88799810762cd4ad314234" +dependencies = [ + "serde", +] + +[[package]] +name = "toml" +version = "0.8.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1ed1f98e3fdc28d6d910e6737ae6ab1a93bf1985935a1193e68f93eeb68d24e" +dependencies = [ + "serde", + "serde_spanned", + "toml_datetime", + "toml_edit", +] + +[[package]] +name = "toml_datetime" +version = "0.6.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0dd7358ecb8fc2f8d014bf86f6f638ce72ba252a2c3a2572f2a795f1d23efb41" +dependencies = [ + "serde", +] + +[[package]] +name = "toml_edit" +version = "0.22.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b072cee73c449a636ffd6f32bd8de3a9f7119139aff882f44943ce2986dc5cf" +dependencies = [ + "indexmap 2.5.0", + "serde", + "serde_spanned", + "toml_datetime", + "winnow", +] + +[[package]] +name = "tower" +version = "0.4.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8fa9be0de6cf49e536ce1851f987bd21a43b771b09473c3549a6c853db37c1c" +dependencies = [ + "futures-core", + "futures-util", + "pin-project", + "pin-project-lite 0.2.14", + "tower-layer", + "tower-service", + "tracing", +] + +[[package]] +name = "tower-http" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "61c5bb1d698276a2443e5ecfabc1008bf15a36c12e6a7176e7bf089ea9131140" +dependencies = [ + "bitflags 2.6.0", + "bytes", + "futures-core", + "futures-util", + "http", + "http-body", + "http-range-header", + "pin-project-lite 0.2.14", + "tower-layer", + "tower-service", +] + +[[package]] +name = "tower-layer" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "121c2a6cda46980bb0fcd1647ffaf6cd3fc79a013de288782836f6df9c48780e" + +[[package]] +name = "tower-service" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8df9b6e13f2d32c91b9bd719c00d1958837bc7dec474d94952798cc8e69eeec3" + +[[package]] +name = "tracing" +version = "0.1.40" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3523ab5a71916ccf420eebdf5521fcef02141234bbc0b8a49f2fdc4544364ef" +dependencies = [ + "log", + "pin-project-lite 0.2.14", + "tracing-attributes", + "tracing-core", +] + +[[package]] +name = "tracing-attributes" +version = "0.1.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.77", +] + +[[package]] +name = "tracing-core" +version = "0.1.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c06d3da6113f116aaee68e4d601191614c9053067f9ab7f6edbcb161237daa54" +dependencies = [ + "once_cell", + "valuable", +] + +[[package]] +name = "tracing-futures" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97d095ae15e245a057c8e8451bab9b3ee1e1f68e9ba2b4fbc18d0ac5237835f2" +dependencies = [ + "pin-project", + "tracing", +] + +[[package]] +name = "tracing-log" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f751112709b4e791d8ce53e32c4ed2d353565a795ce84da2285393f41557bdf2" +dependencies = [ + "log", + "once_cell", + "tracing-core", +] + +[[package]] +name = "tracing-serde" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bc6b213177105856957181934e4920de57730fc69bf42c37ee5bb664d406d9e1" +dependencies = [ + "serde", + "tracing-core", +] + +[[package]] +name = "tracing-subscriber" +version = "0.2.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0e0d2eaa99c3c2e41547cfa109e910a68ea03823cccad4a0525dcbc9b01e8c71" +dependencies = [ + "ansi_term", + "chrono", + "lazy_static", + "matchers", + "parking_lot 0.11.2", + "regex", + "serde", + "serde_json", + "sharded-slab", + "smallvec", + "thread_local", + "tracing", + "tracing-core", + "tracing-log", + "tracing-serde", +] + +[[package]] +name = "trie-db" +version = "0.28.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff28e0f815c2fea41ebddf148e008b077d2faddb026c9555b29696114d602642" +dependencies = [ + "hash-db", + "hashbrown 0.13.2", + "log", + "rustc-hex", + "smallvec", +] + +[[package]] +name = "trie-root" +version = "0.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d4ed310ef5ab98f5fa467900ed906cb9232dd5376597e00fd4cba2a449d06c0b" +dependencies = [ + "hash-db", +] + +[[package]] +name = "trust-dns-proto" +version = "0.22.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4f7f83d1e4a0e4358ac54c5c3681e5d7da5efc5a7a632c90bb6d6669ddd9bc26" +dependencies = [ + "async-trait", + "cfg-if", + "data-encoding", + "enum-as-inner", + "futures-channel", + "futures-io", + "futures-util", + "idna 0.2.3", + "ipnet", + "lazy_static", + "rand 0.8.5", + "smallvec", + "socket2 0.4.10", + "thiserror", + "tinyvec", + "tokio", + "tracing", + "url", +] + +[[package]] +name = "trust-dns-resolver" +version = "0.22.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aff21aa4dcefb0a1afbfac26deb0adc93888c7d295fb63ab273ef276ba2b7cfe" +dependencies = [ + "cfg-if", + "futures-util", + "ipconfig", + "lazy_static", + "lru-cache", + "parking_lot 0.12.3", + "resolv-conf", + "smallvec", + "thiserror", + "tokio", + "tracing", + "trust-dns-proto", +] + +[[package]] +name = "try-lock" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" + +[[package]] +name = "try-runtime-cli" +version = "0.38.0" +source = "git+https://github.com/gasp-xyz/polkadot-sdk?branch=eth-rollup-develop#6b71a3c1276a1e0d8a75eaf5932d9afb82bb477a" +dependencies = [ + "async-trait", + "clap", + "frame-remote-externalities", + "frame-try-runtime", + "hex", + "log", + "parity-scale-codec", + "sc-cli", + "sc-executor", + "serde", + "serde_json", + "sp-api", + "sp-consensus-aura", + "sp-consensus-babe", + "sp-core", + "sp-debug-derive", + "sp-externalities", + "sp-inherents", + "sp-io", + "sp-keystore", + "sp-rpc", + "sp-runtime", + "sp-state-machine", + "sp-timestamp", + "sp-transaction-storage-proof", + "sp-version", + "sp-weights", + "substrate-rpc-client", + "zstd 0.12.4", +] + +[[package]] +name = "tt-call" +version = "1.0.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f4f195fd851901624eee5a58c4bb2b4f06399148fcd0ed336e6f1cb60a9881df" + +[[package]] +name = "twox-hash" +version = "1.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97fee6b57c6a41524a810daee9286c02d7752c4253064d0b05472833a438f675" +dependencies = [ + "cfg-if", + "digest 0.10.7", + "rand 0.8.5", + "static_assertions", +] + +[[package]] +name = "typenum" +version = "1.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825" + +[[package]] +name = "ucd-trie" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed646292ffc8188ef8ea4d1e0e0150fb15a5c2e12ad9b8fc191ae7a8a7f3c4b9" + +[[package]] +name = "uint" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76f64bba2c53b04fcab63c01a7d7427eadc821e3bc48c34dc9ba29c501164b52" +dependencies = [ + "byteorder", + "crunchy", + "hex", + "static_assertions", +] + +[[package]] +name = "unarray" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eaea85b334db583fe3274d12b4cd1880032beab409c0d774be044d4480ab9a94" + +[[package]] +name = "unicode-bidi" +version = "0.3.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08f95100a766bf4f8f28f90d77e0a5461bbdb219042e7679bebe79004fed8d75" + +[[package]] +name = "unicode-ident" +version = "1.0.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e91b56cd4cadaeb79bbf1a5645f6b4f8dc5bde8834ad5894a8db35fda9efa1fe" + +[[package]] +name = "unicode-normalization" +version = "0.1.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c5713f0fc4b5db668a2ac63cdb7bb4469d8c9fed047b1d0292cc7b0ce2ba921" +dependencies = [ + "tinyvec", +] + +[[package]] +name = "unicode-segmentation" +version = "1.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6ccf251212114b54433ec949fd6a7841275f9ada20dddd2f29e9ceea4501493" + +[[package]] +name = "unicode-width" +version = "0.1.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0336d538f7abc86d282a4189614dfaa90810dfc2c6f6427eaf88e16311dd225d" + +[[package]] +name = "unicode-xid" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "229730647fbc343e3a80e463c1db7f78f3855d3f3739bee0dda773c9a037c90a" + +[[package]] +name = "universal-hash" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc1de2c688dc15305988b563c3854064043356019f97a4b46276fe734c4f07ea" +dependencies = [ + "crypto-common", + "subtle 2.6.1", +] + +[[package]] +name = "unsigned-varint" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6889a77d49f1f013504cec6bf97a2c730394adedaeb1deb5ea08949a50541105" +dependencies = [ + "asynchronous-codec", + "bytes", + "futures-io", + "futures-util", +] + +[[package]] +name = "untrusted" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a156c684c91ea7d62626509bce3cb4e1d9ed5c4d978f7b4352658f96a4c26b4a" + +[[package]] +name = "untrusted" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" + +[[package]] +name = "url" +version = "2.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22784dbdf76fdde8af1aeda5622b546b422b6fc585325248a2bf9f5e41e94d6c" +dependencies = [ + "form_urlencoded", + "idna 0.5.0", + "percent-encoding", +] + +[[package]] +name = "utf8parse" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" + +[[package]] +name = "valuable" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "830b7e5d4d90034032940e4ace0d9a9a057e7a45cd94e6c007832e39edb82f6d" + +[[package]] +name = "vcpkg" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" + +[[package]] +name = "ver-api" +version = "4.0.0-dev" +source = "git+https://github.com/gasp-xyz/polkadot-sdk?branch=eth-rollup-develop#e5bbc9b3eaa4f69cd39000bb69f6963f36dd93b8" +dependencies = [ + "derive_more", + "frame-support", + "futures", + "log", + "parity-scale-codec", + "serde", + "sp-api", + "sp-blockchain", + "sp-core", + "sp-runtime", + "sp-std", + "sp-ver", + "sp-weights", +] + +[[package]] +name = "version_check" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" + +[[package]] +name = "void" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a02e4885ed3bc0f2de90ea6dd45ebcbb66dacffe03547fadbb0eeae2770887d" + +[[package]] +name = "w3f-bls" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c5da5fa2c6afa2c9158eaa7cd9aee249765eb32b5fb0c63ad8b9e79336a47ec" +dependencies = [ + "ark-bls12-377", + "ark-bls12-381", + "ark-ec 0.4.2", + "ark-ff 0.4.2", + "ark-serialize 0.4.2", + "ark-serialize-derive 0.4.2", + "arrayref", + "constcat", + "digest 0.10.7", + "rand 0.8.5", + "rand_chacha 0.3.1", + "rand_core 0.6.4", + "sha2 0.10.8", + "sha3", + "thiserror", + "zeroize", +] + +[[package]] +name = "wait-timeout" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f200f5b12eb75f8c1ed65abd4b2db8a6e1b138a20de009dacee265a2498f3f6" +dependencies = [ + "libc", +] + +[[package]] +name = "walkdir" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b" +dependencies = [ + "same-file", + "winapi-util", +] + +[[package]] +name = "want" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfa7760aed19e106de2c7c0b581b509f2f25d3dacaf737cb82ac61bc6d760b0e" +dependencies = [ + "try-lock", +] + +[[package]] +name = "wasi" +version = "0.9.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cccddf32554fecc6acb585f82a32a72e28b48f8c4c1883ddfeeeaa96f7d8e519" + +[[package]] +name = "wasi" +version = "0.11.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" + +[[package]] +name = "wasm-bindgen" +version = "0.2.93" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a82edfc16a6c469f5f44dc7b571814045d60404b55a0ee849f9bcfa2e63dd9b5" +dependencies = [ + "cfg-if", + "once_cell", + "wasm-bindgen-macro", +] + +[[package]] +name = "wasm-bindgen-backend" +version = "0.2.93" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9de396da306523044d3302746f1208fa71d7532227f15e347e2d93e4145dd77b" +dependencies = [ + "bumpalo", + "log", + "once_cell", + "proc-macro2", + "quote", + "syn 2.0.77", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-futures" +version = "0.4.43" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "61e9300f63a621e96ed275155c108eb6f843b6a26d053f122ab69724559dc8ed" +dependencies = [ + "cfg-if", + "js-sys", + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.93" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "585c4c91a46b072c92e908d99cb1dcdf95c5218eeb6f3bf1efa991ee7a68cccf" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.93" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "afc340c74d9005395cf9dd098506f7f44e38f2b4a21c6aaacf9a105ea5e1e836" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.77", + "wasm-bindgen-backend", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.93" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c62a0a307cb4a311d3a07867860911ca130c3494e8c2719593806c08bc5d0484" + +[[package]] +name = "wasm-instrument" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a47ecb37b9734d1085eaa5ae1a81e60801fd8c28d4cabdd8aedb982021918bc" +dependencies = [ + "parity-wasm", +] + +[[package]] +name = "wasm-opt" +version = "0.116.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2fd87a4c135535ffed86123b6fb0f0a5a0bc89e50416c942c5f0662c645f679c" +dependencies = [ + "anyhow", + "libc", + "strum 0.24.1", + "strum_macros 0.24.3", + "tempfile", + "thiserror", + "wasm-opt-cxx-sys", + "wasm-opt-sys", +] + +[[package]] +name = "wasm-opt-cxx-sys" +version = "0.116.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8c57b28207aa724318fcec6575fe74803c23f6f266fce10cbc9f3f116762f12e" +dependencies = [ + "anyhow", + "cxx", + "cxx-build", + "wasm-opt-sys", +] + +[[package]] +name = "wasm-opt-sys" +version = "0.116.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a1cce564dc768dacbdb718fc29df2dba80bd21cb47d8f77ae7e3d95ceb98cbe" +dependencies = [ + "anyhow", + "cc", + "cxx", + "cxx-build", +] + +[[package]] +name = "wasm-timer" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "be0ecb0db480561e9a7642b5d3e4187c128914e58aa84330b9493e3eb68c5e7f" +dependencies = [ + "futures", + "js-sys", + "parking_lot 0.11.2", + "pin-utils", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", +] + +[[package]] +name = "wasmparser" +version = "0.102.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "48134de3d7598219ab9eaf6b91b15d8e50d31da76b8519fe4ecfcec2cf35104b" +dependencies = [ + "indexmap 1.9.3", + "url", +] + +[[package]] +name = "wasmtime" +version = "8.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f907fdead3153cb9bfb7a93bbd5b62629472dc06dee83605358c64c52ed3dda9" +dependencies = [ + "anyhow", + "bincode", + "cfg-if", + "indexmap 1.9.3", + "libc", + "log", + "object 0.30.4", + "once_cell", + "paste", + "psm", + "rayon", + "serde", + "target-lexicon", + "wasmparser", + "wasmtime-cache", + "wasmtime-cranelift", + "wasmtime-environ", + "wasmtime-jit", + "wasmtime-runtime", + "windows-sys 0.45.0", +] + +[[package]] +name = "wasmtime-asm-macros" +version = "8.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3b9daa7c14cd4fa3edbf69de994408d5f4b7b0959ac13fa69d465f6597f810d" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "wasmtime-cache" +version = "8.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c86437fa68626fe896e5afc69234bb2b5894949083586535f200385adfd71213" +dependencies = [ + "anyhow", + "base64 0.21.7", + "bincode", + "directories-next", + "file-per-thread-logger", + "log", + "rustix 0.36.17", + "serde", + "sha2 0.10.8", + "toml 0.5.11", + "windows-sys 0.45.0", + "zstd 0.11.2+zstd.1.5.2", +] + +[[package]] +name = "wasmtime-cranelift" +version = "8.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1cefde0cce8cb700b1b21b6298a3837dba46521affd7b8c38a9ee2c869eee04" +dependencies = [ + "anyhow", + "cranelift-codegen", + "cranelift-entity", + "cranelift-frontend", + "cranelift-native", + "cranelift-wasm", + "gimli 0.27.3", + "log", + "object 0.30.4", + "target-lexicon", + "thiserror", + "wasmparser", + "wasmtime-cranelift-shared", + "wasmtime-environ", +] + +[[package]] +name = "wasmtime-cranelift-shared" +version = "8.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd041e382ef5aea1b9fc78442394f1a4f6d676ce457e7076ca4cb3f397882f8b" +dependencies = [ + "anyhow", + "cranelift-codegen", + "cranelift-native", + "gimli 0.27.3", + "object 0.30.4", + "target-lexicon", + "wasmtime-environ", +] + +[[package]] +name = "wasmtime-environ" +version = "8.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a990198cee4197423045235bf89d3359e69bd2ea031005f4c2d901125955c949" +dependencies = [ + "anyhow", + "cranelift-entity", + "gimli 0.27.3", + "indexmap 1.9.3", + "log", + "object 0.30.4", + "serde", + "target-lexicon", + "thiserror", + "wasmparser", + "wasmtime-types", +] + +[[package]] +name = "wasmtime-jit" +version = "8.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0de48df552cfca1c9b750002d3e07b45772dd033b0b206d5c0968496abf31244" +dependencies = [ + "addr2line 0.19.0", + "anyhow", + "bincode", + "cfg-if", + "cpp_demangle", + "gimli 0.27.3", + "log", + "object 0.30.4", + "rustc-demangle", + "serde", + "target-lexicon", + "wasmtime-environ", + "wasmtime-jit-debug", + "wasmtime-jit-icache-coherence", + "wasmtime-runtime", + "windows-sys 0.45.0", +] + +[[package]] +name = "wasmtime-jit-debug" +version = "8.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e0554b84c15a27d76281d06838aed94e13a77d7bf604bbbaf548aa20eb93846" +dependencies = [ + "object 0.30.4", + "once_cell", + "rustix 0.36.17", +] + +[[package]] +name = "wasmtime-jit-icache-coherence" +version = "8.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aecae978b13f7f67efb23bd827373ace4578f2137ec110bbf6a4a7cde4121bbd" +dependencies = [ + "cfg-if", + "libc", + "windows-sys 0.45.0", +] + +[[package]] +name = "wasmtime-runtime" +version = "8.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "658cf6f325232b6760e202e5255d823da5e348fdea827eff0a2a22319000b441" +dependencies = [ + "anyhow", + "cc", + "cfg-if", + "indexmap 1.9.3", + "libc", + "log", + "mach", + "memfd", + "memoffset", + "paste", + "rand 0.8.5", + "rustix 0.36.17", + "wasmtime-asm-macros", + "wasmtime-environ", + "wasmtime-jit-debug", + "windows-sys 0.45.0", +] + +[[package]] +name = "wasmtime-types" +version = "8.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4f6fffd2a1011887d57f07654dd112791e872e3ff4a2e626aee8059ee17f06f" +dependencies = [ + "cranelift-entity", + "serde", + "thiserror", + "wasmparser", +] + +[[package]] +name = "web-sys" +version = "0.3.70" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26fdeaafd9bd129f65e7c031593c24d62186301e0c72c8978fa1678be7d532c0" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "webpki" +version = "0.22.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed63aea5ce73d0ff405984102c42de94fc55a6b75765d621c65262469b3c9b53" +dependencies = [ + "ring 0.17.8", + "untrusted 0.9.0", +] + +[[package]] +name = "webpki-roots" +version = "0.22.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6c71e40d7d2c34a5106301fb632274ca37242cd0c9d3e64dbece371a40a2d87" +dependencies = [ + "webpki", +] + +[[package]] +name = "webpki-roots" +version = "0.26.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0bd24728e5af82c6c4ec1b66ac4844bdf8156257fccda846ec58b42cd0cdbe6a" +dependencies = [ + "rustls-pki-types", +] + +[[package]] +name = "which" +version = "4.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87ba24419a2078cd2b0f2ede2691b6c66d8e47836da3b6db8265ebad47afbfc7" +dependencies = [ + "either", + "home", + "once_cell", + "rustix 0.38.37", +] + +[[package]] +name = "wide" +version = "0.7.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b828f995bf1e9622031f8009f8481a85406ce1f4d4588ff746d872043e855690" +dependencies = [ + "bytemuck", + "safe_arch", +] + +[[package]] +name = "widestring" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7219d36b6eac893fa81e84ebe06485e7dcbb616177469b142df14f1f4deb1311" + +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-util" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb" +dependencies = [ + "windows-sys 0.59.0", +] + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" + +[[package]] +name = "windows" +version = "0.51.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca229916c5ee38c2f2bc1e9d8f04df975b4bd93f9955dc69fabb5d91270045c9" +dependencies = [ + "windows-core", + "windows-targets 0.48.5", +] + +[[package]] +name = "windows-core" +version = "0.51.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1f8cf84f35d2db49a46868f947758c7a1138116f7fac3bc844f43ade1292e64" +dependencies = [ + "windows-targets 0.48.5", +] + +[[package]] +name = "windows-sys" +version = "0.45.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75283be5efb2831d37ea142365f009c02ec203cd29a3ebecbc093d52315b66d0" +dependencies = [ + "windows-targets 0.42.2", +] + +[[package]] +name = "windows-sys" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" +dependencies = [ + "windows-targets 0.48.5", +] + +[[package]] +name = "windows-sys" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" +dependencies = [ + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-sys" +version = "0.59.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" +dependencies = [ + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-targets" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e5180c00cd44c9b1c88adb3693291f1cd93605ded80c250a75d472756b4d071" +dependencies = [ + "windows_aarch64_gnullvm 0.42.2", + "windows_aarch64_msvc 0.42.2", + "windows_i686_gnu 0.42.2", + "windows_i686_msvc 0.42.2", + "windows_x86_64_gnu 0.42.2", + "windows_x86_64_gnullvm 0.42.2", + "windows_x86_64_msvc 0.42.2", +] + +[[package]] +name = "windows-targets" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" +dependencies = [ + "windows_aarch64_gnullvm 0.48.5", + "windows_aarch64_msvc 0.48.5", + "windows_i686_gnu 0.48.5", + "windows_i686_msvc 0.48.5", + "windows_x86_64_gnu 0.48.5", + "windows_x86_64_gnullvm 0.48.5", + "windows_x86_64_msvc 0.48.5", +] + +[[package]] +name = "windows-targets" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" +dependencies = [ + "windows_aarch64_gnullvm 0.52.6", + "windows_aarch64_msvc 0.52.6", + "windows_i686_gnu 0.52.6", + "windows_i686_gnullvm", + "windows_i686_msvc 0.52.6", + "windows_x86_64_gnu 0.52.6", + "windows_x86_64_gnullvm 0.52.6", + "windows_x86_64_msvc 0.52.6", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "597a5118570b68bc08d8d59125332c54f1ba9d9adeedeef5b99b02ba2b0698f8" + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" + +[[package]] +name = "windows_i686_gnu" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c61d927d8da41da96a81f029489353e68739737d3beca43145c8afec9a31a84f" + +[[package]] +name = "windows_i686_gnu" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" + +[[package]] +name = "windows_i686_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" + +[[package]] +name = "windows_i686_msvc" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060" + +[[package]] +name = "windows_i686_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" + +[[package]] +name = "windows_i686_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8de912b8b8feb55c064867cf047dda097f92d51efad5b491dfb98f6bbb70cb36" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26d41b46a36d453748aedef1486d5c7a85db22e56aff34643984ea85514e94a3" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" + +[[package]] +name = "winnow" +version = "0.6.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68a9bda4691f099d435ad181000724da8e5899daa10713c2d432552b9ccd3a6f" +dependencies = [ + "memchr", +] + +[[package]] +name = "winreg" +version = "0.50.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "524e57b2c537c0f9b1e69f1965311ec12182b4122e45035b1508cd24d2adadb1" +dependencies = [ + "cfg-if", + "windows-sys 0.48.0", +] + +[[package]] +name = "wyz" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05f360fc0b24296329c78fda852a1e9ae82de9cf7b27dae4b7f62f118f77b9ed" +dependencies = [ + "tap", +] + +[[package]] +name = "x25519-dalek" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a0c105152107e3b96f6a00a65e86ce82d9b125230e1c4302940eca58ff71f4f" +dependencies = [ + "curve25519-dalek 3.2.0", + "rand_core 0.5.1", + "zeroize", +] + +[[package]] +name = "x25519-dalek" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c7e468321c81fb07fa7f4c636c3972b9100f0346e5b6a9f2bd0603a52f7ed277" +dependencies = [ + "curve25519-dalek 4.1.1", + "rand_core 0.6.4", + "serde", + "zeroize", +] + +[[package]] +name = "x509-parser" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e0ecbeb7b67ce215e40e3cc7f2ff902f94a223acf44995934763467e7b1febc8" +dependencies = [ + "asn1-rs", + "base64 0.13.1", + "data-encoding", + "der-parser", + "lazy_static", + "nom", + "oid-registry", + "rusticata-macros", + "thiserror", + "time", +] + +[[package]] +name = "xyk-rpc" +version = "2.0.0" +dependencies = [ + "jsonrpsee", + "parity-scale-codec", + "serde", + "sp-api", + "sp-blockchain", + "sp-core", + "sp-rpc", + "sp-runtime", + "sp-std", + "xyk-runtime-api", +] + +[[package]] +name = "xyk-runtime-api" +version = "2.0.0" +dependencies = [ + "frame-support", + "frame-system", + "parity-scale-codec", + "scale-info", + "serde", + "serde_json", + "sp-api", + "sp-core", + "sp-runtime", + "sp-std", +] + +[[package]] +name = "yamux" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5d9ba232399af1783a58d8eb26f6b5006fbefe2dc9ef36bd283324792d03ea5" +dependencies = [ + "futures", + "log", + "nohash-hasher", + "parking_lot 0.12.3", + "rand 0.8.5", + "static_assertions", +] + +[[package]] +name = "yasna" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e17bb3549cc1321ae1296b9cdc2698e2b6cb1992adfa19a8c72e5b7a738f44cd" +dependencies = [ + "time", +] + +[[package]] +name = "zerocopy" +version = "0.7.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b9b4fd18abc82b8136838da5d50bae7bdea537c574d8dc1a34ed098d6c166f0" +dependencies = [ + "byteorder", + "zerocopy-derive", +] + +[[package]] +name = "zerocopy-derive" +version = "0.7.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.77", +] + +[[package]] +name = "zeroize" +version = "1.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ced3678a2879b30306d323f4542626697a464a97c0a07c9aebf7ebca65cd4dde" +dependencies = [ + "zeroize_derive", +] + +[[package]] +name = "zeroize_derive" +version = "1.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce36e65b0d2999d2aafac989fb249189a141aee1f53c612c1f37d72631959f69" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.77", +] + +[[package]] +name = "zstd" +version = "0.11.2+zstd.1.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "20cc960326ece64f010d2d2107537f26dc589a6573a316bd5b1dba685fa5fde4" +dependencies = [ + "zstd-safe 5.0.2+zstd.1.5.2", +] + +[[package]] +name = "zstd" +version = "0.12.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a27595e173641171fc74a1232b7b1c7a7cb6e18222c11e9dfb9888fa424c53c" +dependencies = [ + "zstd-safe 6.0.6", +] + +[[package]] +name = "zstd-safe" +version = "5.0.2+zstd.1.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d2a5585e04f9eea4b2a3d1eca508c4dee9592a89ef6f450c11719da0726f4db" +dependencies = [ + "libc", + "zstd-sys", +] + +[[package]] +name = "zstd-safe" +version = "6.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee98ffd0b48ee95e6c5168188e44a54550b1564d9d530ee21d5f0eaed1069581" +dependencies = [ + "libc", + "zstd-sys", +] + +[[package]] +name = "zstd-sys" +version = "2.0.13+zstd.1.5.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38ff0f21cfee8f97d94cef41359e0c89aa6113028ab0291aa8ca0038995a95aa" +dependencies = [ + "cc", + "pkg-config", +] diff --git a/gasp-node/Cargo.toml b/gasp-node/Cargo.toml new file mode 100644 index 000000000..6d02615f7 --- /dev/null +++ b/gasp-node/Cargo.toml @@ -0,0 +1,477 @@ +[workspace.package] +authors = ["Gasp Team"] +edition = "2018" +repository = "https://github.com/gasp-xyz/gasp-monorepo.git" +homepage = "https://gasp.xyz" + +[profile.dev] +split-debuginfo = "unpacked" + +[profile.release] +panic = 'unwind' + +[profile.production] +inherits = "release" +lto = true +codegen-units = 1 + +[workspace] +resolver = "2" +members = [ + 'pallets/*', + 'rpc/nonce', + 'rollup/node', + 'rollup/runtime', + 'rollup/runtime/integration-test', +] + +[workspace.dependencies] +alloy-primitives = { version = "0.5.0", default-features = false } +alloy-sol-types = { version = "0.5.0", default-features = false } +aquamarine = "0.1.12" +array-bytes = "6.1" +codec = { package = "parity-scale-codec", version = "3.0.0", default-features = false } +futures = "0.3.21" +hex = { version = "0.4.0", default-features = false } +hex-literal = { version = "0.3.3", default-features = false } +itertools = { version = "0.10.3", default-features = false } +jsonrpsee = { version = "0.22", default-features = false } +log = { version = "0.4.14", default-features = false } +scale-info = { version = "2.0.0", features = ["derive"], default-features = false } +serde = { version = "1.0.188", default-features = false } +serde_json = { version = "1.0.128", default-features = false } + +#dev +assert_matches = "1.3.0" +env_logger = "0.9.0" +lazy_static = "1.1.1" +mockall = "0.11.0" +serial_test = { version = "0.6.0", default-features = false } +similar-asserts = "1.1.0" +test-case = "2.0.2" +tokio = "1.22.0" + +#orml +orml-asset-registry = { git = "https://github.com/gasp-xyz/open-runtime-module-library", branch = "eth-rollup-develop", default-features = false } +orml-tokens = { git = "https://github.com/gasp-xyz/open-runtime-module-library", branch = "eth-rollup-develop", default-features = false } +orml-traits = { git = "https://github.com/gasp-xyz/open-runtime-module-library", branch = "eth-rollup-develop", default-features = false } + +#polkdadot sdk +extrinsic-shuffler = { git = "https://github.com/gasp-xyz/polkadot-sdk", branch = "eth-rollup-develop", default-features = false } +fork-tree = { git = "https://github.com/gasp-xyz/polkadot-sdk", branch = "eth-rollup-develop", default-features = false } +frame-benchmarking = { git = "https://github.com/gasp-xyz/polkadot-sdk", branch = "eth-rollup-develop", default-features = false } +frame-benchmarking-cli = { git = "https://github.com/gasp-xyz/polkadot-sdk", branch = "eth-rollup-develop", default-features = false } +frame-executive = { git = "https://github.com/gasp-xyz/polkadot-sdk", branch = "eth-rollup-develop", default-features = false } +frame-support = { git = "https://github.com/gasp-xyz/polkadot-sdk", branch = "eth-rollup-develop", default-features = false } +frame-support-procedural = { git = "https://github.com/gasp-xyz/polkadot-sdk", branch = "eth-rollup-develop", default-features = false } +frame-support-procedural-tools = { git = "https://github.com/gasp-xyz/polkadot-sdk", branch = "eth-rollup-develop", default-features = false } +frame-support-procedural-tools-derive = { git = "https://github.com/gasp-xyz/polkadot-sdk", branch = "eth-rollup-develop", default-features = false } +frame-system = { git = "https://github.com/gasp-xyz/polkadot-sdk", branch = "eth-rollup-develop", default-features = false } +frame-system-benchmarking = { git = "https://github.com/gasp-xyz/polkadot-sdk", branch = "eth-rollup-develop", default-features = false } +frame-system-rpc-runtime-api = { git = "https://github.com/gasp-xyz/polkadot-sdk", branch = "eth-rollup-develop", default-features = false } +frame-try-runtime = { git = "https://github.com/gasp-xyz/polkadot-sdk", branch = "eth-rollup-develop", default-features = false } +mangata-support = { git = "https://github.com/gasp-xyz/polkadot-sdk", branch = "eth-rollup-develop", default-features = false } +mangata-types = { git = "https://github.com/gasp-xyz/polkadot-sdk", branch = "eth-rollup-develop", default-features = false } +pallet-aura = { git = "https://github.com/gasp-xyz/polkadot-sdk", branch = "eth-rollup-develop", default-features = false } +pallet-authorship = { git = "https://github.com/gasp-xyz/polkadot-sdk", branch = "eth-rollup-develop", default-features = false } +pallet-babe = { git = "https://github.com/gasp-xyz/polkadot-sdk", branch = "eth-rollup-develop", default-features = false } +pallet-balances = { git = "https://github.com/gasp-xyz/polkadot-sdk", branch = "eth-rollup-develop", default-features = false } +pallet-collective-mangata = { git = "https://github.com/gasp-xyz/polkadot-sdk", branch = "eth-rollup-develop", default-features = false } +pallet-grandpa = { git = "https://github.com/gasp-xyz/polkadot-sdk", branch = "eth-rollup-develop", default-features = false } +pallet-identity = { git = "https://github.com/gasp-xyz/polkadot-sdk", branch = "eth-rollup-develop", default-features = false } +pallet-membership = { git = "https://github.com/gasp-xyz/polkadot-sdk", branch = "eth-rollup-develop", default-features = false } +pallet-proxy = { git = "https://github.com/gasp-xyz/polkadot-sdk", branch = "eth-rollup-develop", default-features = false } +pallet-session = { git = "https://github.com/gasp-xyz/polkadot-sdk", branch = "eth-rollup-develop", default-features = false } +pallet-sudo-mangata = { git = "https://github.com/gasp-xyz/polkadot-sdk", branch = "eth-rollup-develop", default-features = false } +pallet-timestamp = { git = "https://github.com/gasp-xyz/polkadot-sdk", branch = "eth-rollup-develop", default-features = false } +pallet-transaction-payment = { git = "https://github.com/gasp-xyz/polkadot-sdk", branch = "eth-rollup-develop", default-features = false } +pallet-transaction-payment-rpc = { git = "https://github.com/gasp-xyz/polkadot-sdk", branch = "eth-rollup-develop", default-features = false } +pallet-transaction-payment-rpc-runtime-api = { git = "https://github.com/gasp-xyz/polkadot-sdk", branch = "eth-rollup-develop", default-features = false } +pallet-treasury = { git = "https://github.com/gasp-xyz/polkadot-sdk", branch = "eth-rollup-develop", default-features = false } +pallet-utility = { git = "https://github.com/gasp-xyz/polkadot-sdk", branch = "eth-rollup-develop", default-features = false } +pallet-utility-mangata = { git = "https://github.com/gasp-xyz/polkadot-sdk", branch = "eth-rollup-develop", default-features = false } +pallet-vesting-mangata = { git = "https://github.com/gasp-xyz/polkadot-sdk", branch = "eth-rollup-develop", default-features = false } +sc-allocator = { git = "https://github.com/gasp-xyz/polkadot-sdk", branch = "eth-rollup-develop", default-features = false } +sc-basic-authorship-ver = { git = "https://github.com/gasp-xyz/polkadot-sdk", branch = "eth-rollup-develop", default-features = false } +sc-block-builder = { git = "https://github.com/gasp-xyz/polkadot-sdk", branch = "eth-rollup-develop", default-features = false } +sc-block-builder-ver = { git = "https://github.com/gasp-xyz/polkadot-sdk", branch = "eth-rollup-develop", default-features = false } +sc-chain-spec = { git = "https://github.com/gasp-xyz/polkadot-sdk", branch = "eth-rollup-develop", default-features = false } +sc-chain-spec-derive = { git = "https://github.com/gasp-xyz/polkadot-sdk", branch = "eth-rollup-develop", default-features = false } +sc-cli = { git = "https://github.com/gasp-xyz/polkadot-sdk", branch = "eth-rollup-develop", default-features = false } +sc-client-api = { git = "https://github.com/gasp-xyz/polkadot-sdk", branch = "eth-rollup-develop", default-features = false } +sc-client-db = { git = "https://github.com/gasp-xyz/polkadot-sdk", branch = "eth-rollup-develop", default-features = false } +sc-consensus = { git = "https://github.com/gasp-xyz/polkadot-sdk", branch = "eth-rollup-develop", default-features = false } +sc-consensus-aura = { git = "https://github.com/gasp-xyz/polkadot-sdk", branch = "eth-rollup-develop", default-features = false } +sc-consensus-grandpa = { git = "https://github.com/gasp-xyz/polkadot-sdk", branch = "eth-rollup-develop", default-features = false } +sc-consensus-slots = { git = "https://github.com/gasp-xyz/polkadot-sdk", branch = "eth-rollup-develop", default-features = false } +sc-executor = { git = "https://github.com/gasp-xyz/polkadot-sdk", branch = "eth-rollup-develop", default-features = false } +sc-executor-common = { git = "https://github.com/gasp-xyz/polkadot-sdk", branch = "eth-rollup-develop", default-features = false } +sc-executor-polkavm = { git = "https://github.com/gasp-xyz/polkadot-sdk", branch = "eth-rollup-develop", default-features = false } +sc-executor-wasmtime = { git = "https://github.com/gasp-xyz/polkadot-sdk", branch = "eth-rollup-develop", default-features = false } +sc-informant = { git = "https://github.com/gasp-xyz/polkadot-sdk", branch = "eth-rollup-develop", default-features = false } +sc-keystore = { git = "https://github.com/gasp-xyz/polkadot-sdk", branch = "eth-rollup-develop", default-features = false } +sc-mixnet = { git = "https://github.com/gasp-xyz/polkadot-sdk", branch = "eth-rollup-develop", default-features = false } +sc-network = { git = "https://github.com/gasp-xyz/polkadot-sdk", branch = "eth-rollup-develop", default-features = false } +sc-network-bitswap = { git = "https://github.com/gasp-xyz/polkadot-sdk", branch = "eth-rollup-develop", default-features = false } +sc-network-common = { git = "https://github.com/gasp-xyz/polkadot-sdk", branch = "eth-rollup-develop", default-features = false } +sc-network-gossip = { git = "https://github.com/gasp-xyz/polkadot-sdk", branch = "eth-rollup-develop", default-features = false } +sc-network-light = { git = "https://github.com/gasp-xyz/polkadot-sdk", branch = "eth-rollup-develop", default-features = false } +sc-network-sync = { git = "https://github.com/gasp-xyz/polkadot-sdk", branch = "eth-rollup-develop", default-features = false } +sc-network-transactions = { git = "https://github.com/gasp-xyz/polkadot-sdk", branch = "eth-rollup-develop", default-features = false } +sc-offchain = { git = "https://github.com/gasp-xyz/polkadot-sdk", branch = "eth-rollup-develop", default-features = false } +sc-proposer-metrics = { git = "https://github.com/gasp-xyz/polkadot-sdk", branch = "eth-rollup-develop", default-features = false } +sc-rpc = { git = "https://github.com/gasp-xyz/polkadot-sdk", branch = "eth-rollup-develop", default-features = false } +sc-rpc-api = { git = "https://github.com/gasp-xyz/polkadot-sdk", branch = "eth-rollup-develop", default-features = false } +sc-rpc-server = { git = "https://github.com/gasp-xyz/polkadot-sdk", branch = "eth-rollup-develop", default-features = false } +sc-rpc-spec-v2 = { git = "https://github.com/gasp-xyz/polkadot-sdk", branch = "eth-rollup-develop", default-features = false } +sc-service = { git = "https://github.com/gasp-xyz/polkadot-sdk", branch = "eth-rollup-develop", default-features = false } +sc-state-db = { git = "https://github.com/gasp-xyz/polkadot-sdk", branch = "eth-rollup-develop", default-features = false } +sc-sysinfo = { git = "https://github.com/gasp-xyz/polkadot-sdk", branch = "eth-rollup-develop", default-features = false } +sc-telemetry = { git = "https://github.com/gasp-xyz/polkadot-sdk", branch = "eth-rollup-develop", default-features = false } +sc-tracing = { git = "https://github.com/gasp-xyz/polkadot-sdk", branch = "eth-rollup-develop", default-features = false } +sc-tracing-proc-macro = { git = "https://github.com/gasp-xyz/polkadot-sdk", branch = "eth-rollup-develop", default-features = false } +sc-transaction-pool = { git = "https://github.com/gasp-xyz/polkadot-sdk", branch = "eth-rollup-develop", default-features = false } +sc-transaction-pool-api = { git = "https://github.com/gasp-xyz/polkadot-sdk", branch = "eth-rollup-develop", default-features = false } +sc-utils = { git = "https://github.com/gasp-xyz/polkadot-sdk", branch = "eth-rollup-develop", default-features = false } +sp-api = { git = "https://github.com/gasp-xyz/polkadot-sdk", branch = "eth-rollup-develop", default-features = false } +sp-api-proc-macro = { git = "https://github.com/gasp-xyz/polkadot-sdk", branch = "eth-rollup-develop", default-features = false } +sp-application-crypto = { git = "https://github.com/gasp-xyz/polkadot-sdk", branch = "eth-rollup-develop", default-features = false } +sp-arithmetic = { git = "https://github.com/gasp-xyz/polkadot-sdk", branch = "eth-rollup-develop", default-features = false } +sp-block-builder = { git = "https://github.com/gasp-xyz/polkadot-sdk", branch = "eth-rollup-develop", default-features = false } +sp-blockchain = { git = "https://github.com/gasp-xyz/polkadot-sdk", branch = "eth-rollup-develop", default-features = false } +sp-consensus = { git = "https://github.com/gasp-xyz/polkadot-sdk", branch = "eth-rollup-develop", default-features = false } +sp-consensus-aura = { git = "https://github.com/gasp-xyz/polkadot-sdk", branch = "eth-rollup-develop", default-features = false } +sp-consensus-babe = { git = "https://github.com/gasp-xyz/polkadot-sdk", branch = "eth-rollup-develop", default-features = false } +sp-consensus-grandpa = { git = "https://github.com/gasp-xyz/polkadot-sdk", branch = "eth-rollup-develop", default-features = false } +sp-consensus-slots = { git = "https://github.com/gasp-xyz/polkadot-sdk", branch = "eth-rollup-develop", default-features = false } +sp-core = { git = "https://github.com/gasp-xyz/polkadot-sdk", branch = "eth-rollup-develop", default-features = false } +sp-crypto-hashing = { git = "https://github.com/gasp-xyz/polkadot-sdk", branch = "eth-rollup-develop", default-features = false } +sp-crypto-hashing-proc-macro = { git = "https://github.com/gasp-xyz/polkadot-sdk", branch = "eth-rollup-develop", default-features = false } +sp-database = { git = "https://github.com/gasp-xyz/polkadot-sdk", branch = "eth-rollup-develop", default-features = false } +sp-debug-derive = { git = "https://github.com/gasp-xyz/polkadot-sdk", branch = "eth-rollup-develop", default-features = false } +sp-externalities = { git = "https://github.com/gasp-xyz/polkadot-sdk", branch = "eth-rollup-develop", default-features = false } +sp-genesis-builder = { git = "https://github.com/gasp-xyz/polkadot-sdk", branch = "eth-rollup-develop", default-features = false } +sp-inherents = { git = "https://github.com/gasp-xyz/polkadot-sdk", branch = "eth-rollup-develop", default-features = false } +sp-io = { git = "https://github.com/gasp-xyz/polkadot-sdk", branch = "eth-rollup-develop", default-features = false } +sp-keyring = { git = "https://github.com/gasp-xyz/polkadot-sdk", branch = "eth-rollup-develop", default-features = false } +sp-keystore = { git = "https://github.com/gasp-xyz/polkadot-sdk", branch = "eth-rollup-develop", default-features = false } +sp-maybe-compressed-blob = { git = "https://github.com/gasp-xyz/polkadot-sdk", branch = "eth-rollup-develop", default-features = false } +sp-metadata-ir = { git = "https://github.com/gasp-xyz/polkadot-sdk", branch = "eth-rollup-develop", default-features = false } +sp-mixnet = { git = "https://github.com/gasp-xyz/polkadot-sdk", branch = "eth-rollup-develop", default-features = false } +sp-offchain = { git = "https://github.com/gasp-xyz/polkadot-sdk", branch = "eth-rollup-develop", default-features = false } +sp-panic-handler = { git = "https://github.com/gasp-xyz/polkadot-sdk", branch = "eth-rollup-develop", default-features = false } +sp-rpc = { git = "https://github.com/gasp-xyz/polkadot-sdk", branch = "eth-rollup-develop", default-features = false } +sp-runtime = { git = "https://github.com/gasp-xyz/polkadot-sdk", branch = "eth-rollup-develop", default-features = false } +sp-runtime-interface = { git = "https://github.com/gasp-xyz/polkadot-sdk", branch = "eth-rollup-develop", default-features = false } +sp-runtime-interface-proc-macro = { git = "https://github.com/gasp-xyz/polkadot-sdk", branch = "eth-rollup-develop", default-features = false } +sp-session = { git = "https://github.com/gasp-xyz/polkadot-sdk", branch = "eth-rollup-develop", default-features = false } +sp-staking = { git = "https://github.com/gasp-xyz/polkadot-sdk", branch = "eth-rollup-develop", default-features = false } +sp-state-machine = { git = "https://github.com/gasp-xyz/polkadot-sdk", branch = "eth-rollup-develop", default-features = false } +sp-statement-store = { git = "https://github.com/gasp-xyz/polkadot-sdk", branch = "eth-rollup-develop", default-features = false } +sp-std = { git = "https://github.com/gasp-xyz/polkadot-sdk", branch = "eth-rollup-develop", default-features = false } +sp-storage = { git = "https://github.com/gasp-xyz/polkadot-sdk", branch = "eth-rollup-develop", default-features = false } +sp-timestamp = { git = "https://github.com/gasp-xyz/polkadot-sdk", branch = "eth-rollup-develop", default-features = false } +sp-tracing = { git = "https://github.com/gasp-xyz/polkadot-sdk", branch = "eth-rollup-develop", default-features = false } +sp-transaction-pool = { git = "https://github.com/gasp-xyz/polkadot-sdk", branch = "eth-rollup-develop", default-features = false } +sp-transaction-storage-proof = { git = "https://github.com/gasp-xyz/polkadot-sdk", branch = "eth-rollup-develop", default-features = false } +sp-trie = { git = "https://github.com/gasp-xyz/polkadot-sdk", branch = "eth-rollup-develop", default-features = false } +sp-ver = { git = "https://github.com/gasp-xyz/polkadot-sdk", branch = "eth-rollup-develop", default-features = false } +sp-version = { git = "https://github.com/gasp-xyz/polkadot-sdk", branch = "eth-rollup-develop", default-features = false } +sp-version-proc-macro = { git = "https://github.com/gasp-xyz/polkadot-sdk", branch = "eth-rollup-develop", default-features = false } +sp-wasm-interface = { git = "https://github.com/gasp-xyz/polkadot-sdk", branch = "eth-rollup-develop", default-features = false } +sp-weights = { git = "https://github.com/gasp-xyz/polkadot-sdk", branch = "eth-rollup-develop", default-features = false } +substrate-bip39 = { git = "https://github.com/gasp-xyz/polkadot-sdk", branch = "eth-rollup-develop", default-features = false } +substrate-build-script-utils = { git = "https://github.com/gasp-xyz/polkadot-sdk", branch = "eth-rollup-develop", default-features = false } +substrate-prometheus-endpoint = { git = "https://github.com/gasp-xyz/polkadot-sdk", branch = "eth-rollup-develop", default-features = false } +substrate-test-client = { git = "https://github.com/gasp-xyz/polkadot-sdk", branch = "eth-rollup-develop", default-features = false } +substrate-test-runtime = { git = "https://github.com/gasp-xyz/polkadot-sdk", branch = "eth-rollup-develop", default-features = false } +substrate-test-runtime-client = { git = "https://github.com/gasp-xyz/polkadot-sdk", branch = "eth-rollup-develop", default-features = false } +substrate-wasm-builder = { git = "https://github.com/gasp-xyz/polkadot-sdk", branch = "eth-rollup-develop", default-features = false } +try-runtime-cli = { git = "https://github.com/gasp-xyz/polkadot-sdk", branch = "eth-rollup-develop", default-features = false } +ver-api = { git = "https://github.com/gasp-xyz/polkadot-sdk", branch = "eth-rollup-develop", default-features = false } + +[patch."https://github.com/paritytech/polkadot-sdk"] +# ... which satisfies git dependency `sp-crypto-ec-utils` of package `sp-ark-bls12-381 v0.4.2 (https://github.com/paritytech/arkworks-substrate#caa2eed7)` +sp-crypto-ec-utils = { git = "https://github.com/gasp-xyz/polkadot-sdk", branch = "eth-rollup-develop" } + +# # patch generated by './scripts/dev_manifest.sh ../polkadot-sdk' +# [patch."https://github.com/gasp-xyz/polkadot-sdk"] +# substrate-test-runtime = { git = "https://github.com//gasp-xyz/polkadot-sdk", branch = "fix/rewards-market" } +# substrate-test-runtime-client = { git = "https://github.com//gasp-xyz/polkadot-sdk", branch = "fix/rewards-market" } +# substrate-test-client = { git = "https://github.com//gasp-xyz/polkadot-sdk", branch = "fix/rewards-market" } +# pallet-transaction-payment = { git = "https://github.com//gasp-xyz/polkadot-sdk", branch = "fix/rewards-market" } +# pallet-transaction-payment-rpc = { git = "https://github.com//gasp-xyz/polkadot-sdk", branch = "fix/rewards-market" } +# pallet-transaction-payment-rpc-runtime-api = { git = "https://github.com//gasp-xyz/polkadot-sdk", branch = "fix/rewards-market" } +# pallet-collective-mangata = { git = "https://github.com//gasp-xyz/polkadot-sdk", branch = "fix/rewards-market" } +# pallet-proxy = { git = "https://github.com//gasp-xyz/polkadot-sdk", branch = "fix/rewards-market" } +# pallet-identity = { git = "https://github.com//gasp-xyz/polkadot-sdk", branch = "fix/rewards-market" } +# pallet-sudo-mangata = { git = "https://github.com//gasp-xyz/polkadot-sdk", branch = "fix/rewards-market" } +# mangata-support = { git = "https://github.com//gasp-xyz/polkadot-sdk", branch = "fix/rewards-market" } +# pallet-utility-mangata = { git = "https://github.com//gasp-xyz/polkadot-sdk", branch = "fix/rewards-market" } +# pallet-authorship = { git = "https://github.com//gasp-xyz/polkadot-sdk", branch = "fix/rewards-market" } +# pallet-grandpa = { git = "https://github.com//gasp-xyz/polkadot-sdk", branch = "fix/rewards-market" } +# pallet-vesting-mangata = { git = "https://github.com//gasp-xyz/polkadot-sdk", branch = "fix/rewards-market" } +# frame-system = { git = "https://github.com//gasp-xyz/polkadot-sdk", branch = "fix/rewards-market" } +# frame-system-benchmarking = { git = "https://github.com//gasp-xyz/polkadot-sdk", branch = "fix/rewards-market" } +# frame-system-rpc-runtime-api = { git = "https://github.com//gasp-xyz/polkadot-sdk", branch = "fix/rewards-market" } +# frame-support = { git = "https://github.com//gasp-xyz/polkadot-sdk", branch = "fix/rewards-market" } +# frame-support-procedural = { git = "https://github.com//gasp-xyz/polkadot-sdk", branch = "fix/rewards-market" } +# frame-support-procedural-tools = { git = "https://github.com//gasp-xyz/polkadot-sdk", branch = "fix/rewards-market" } +# frame-support-procedural-tools-derive = { git = "https://github.com//gasp-xyz/polkadot-sdk", branch = "fix/rewards-market" } +# pallet-balances = { git = "https://github.com//gasp-xyz/polkadot-sdk", branch = "fix/rewards-market" } +# pallet-aura = { git = "https://github.com//gasp-xyz/polkadot-sdk", branch = "fix/rewards-market" } +# frame-benchmarking = { git = "https://github.com//gasp-xyz/polkadot-sdk", branch = "fix/rewards-market" } +# pallet-treasury = { git = "https://github.com//gasp-xyz/polkadot-sdk", branch = "fix/rewards-market" } +# frame-try-runtime = { git = "https://github.com//gasp-xyz/polkadot-sdk", branch = "fix/rewards-market" } +# pallet-timestamp = { git = "https://github.com//gasp-xyz/polkadot-sdk", branch = "fix/rewards-market" } +# pallet-babe = { git = "https://github.com//gasp-xyz/polkadot-sdk", branch = "fix/rewards-market" } +# pallet-membership = { git = "https://github.com//gasp-xyz/polkadot-sdk", branch = "fix/rewards-market" } +# pallet-utility = { git = "https://github.com//gasp-xyz/polkadot-sdk", branch = "fix/rewards-market" } +# frame-executive = { git = "https://github.com//gasp-xyz/polkadot-sdk", branch = "fix/rewards-market" } +# pallet-session = { git = "https://github.com//gasp-xyz/polkadot-sdk", branch = "fix/rewards-market" } +# substrate-wasm-builder = { git = "https://github.com//gasp-xyz/polkadot-sdk", branch = "fix/rewards-market" } +# fork-tree = { git = "https://github.com//gasp-xyz/polkadot-sdk", branch = "fix/rewards-market" } +# substrate-build-script-utils = { git = "https://github.com//gasp-xyz/polkadot-sdk", branch = "fix/rewards-market" } +# frame-benchmarking-cli = { git = "https://github.com//gasp-xyz/polkadot-sdk", branch = "fix/rewards-market" } +# substrate-bip39 = { git = "https://github.com//gasp-xyz/polkadot-sdk", branch = "fix/rewards-market" } +# substrate-prometheus-endpoint = { git = "https://github.com//gasp-xyz/polkadot-sdk", branch = "fix/rewards-market" } +# sp-database = { git = "https://github.com//gasp-xyz/polkadot-sdk", branch = "fix/rewards-market" } +# sp-runtime-interface = { git = "https://github.com//gasp-xyz/polkadot-sdk", branch = "fix/rewards-market" } +# sp-runtime-interface-proc-macro = { git = "https://github.com//gasp-xyz/polkadot-sdk", branch = "fix/rewards-market" } +# sp-consensus-grandpa = { git = "https://github.com//gasp-xyz/polkadot-sdk", branch = "fix/rewards-market" } +# sp-consensus = { git = "https://github.com//gasp-xyz/polkadot-sdk", branch = "fix/rewards-market" } +# sp-consensus-aura = { git = "https://github.com//gasp-xyz/polkadot-sdk", branch = "fix/rewards-market" } +# sp-consensus-babe = { git = "https://github.com//gasp-xyz/polkadot-sdk", branch = "fix/rewards-market" } +# sp-consensus-slots = { git = "https://github.com//gasp-xyz/polkadot-sdk", branch = "fix/rewards-market" } +# sp-tracing = { git = "https://github.com//gasp-xyz/polkadot-sdk", branch = "fix/rewards-market" } +# sp-crypto-hashing = { git = "https://github.com//gasp-xyz/polkadot-sdk", branch = "fix/rewards-market" } +# sp-crypto-hashing-proc-macro = { git = "https://github.com//gasp-xyz/polkadot-sdk", branch = "fix/rewards-market" } +# sp-ver = { git = "https://github.com//gasp-xyz/polkadot-sdk", branch = "fix/rewards-market" } +# sp-maybe-compressed-blob = { git = "https://github.com//gasp-xyz/polkadot-sdk", branch = "fix/rewards-market" } +# sp-core = { git = "https://github.com//gasp-xyz/polkadot-sdk", branch = "fix/rewards-market" } +# sp-state-machine = { git = "https://github.com//gasp-xyz/polkadot-sdk", branch = "fix/rewards-market" } +# sp-keystore = { git = "https://github.com//gasp-xyz/polkadot-sdk", branch = "fix/rewards-market" } +# sp-metadata-ir = { git = "https://github.com//gasp-xyz/polkadot-sdk", branch = "fix/rewards-market" } +# sp-statement-store = { git = "https://github.com//gasp-xyz/polkadot-sdk", branch = "fix/rewards-market" } +# sp-weights = { git = "https://github.com//gasp-xyz/polkadot-sdk", branch = "fix/rewards-market" } +# sp-io = { git = "https://github.com//gasp-xyz/polkadot-sdk", branch = "fix/rewards-market" } +# sp-runtime = { git = "https://github.com//gasp-xyz/polkadot-sdk", branch = "fix/rewards-market" } +# sp-inherents = { git = "https://github.com//gasp-xyz/polkadot-sdk", branch = "fix/rewards-market" } +# sp-transaction-pool = { git = "https://github.com//gasp-xyz/polkadot-sdk", branch = "fix/rewards-market" } +# sp-std = { git = "https://github.com//gasp-xyz/polkadot-sdk", branch = "fix/rewards-market" } +# sp-storage = { git = "https://github.com//gasp-xyz/polkadot-sdk", branch = "fix/rewards-market" } +# sp-panic-handler = { git = "https://github.com//gasp-xyz/polkadot-sdk", branch = "fix/rewards-market" } +# sp-debug-derive = { git = "https://github.com//gasp-xyz/polkadot-sdk", branch = "fix/rewards-market" } +# sp-blockchain = { git = "https://github.com//gasp-xyz/polkadot-sdk", branch = "fix/rewards-market" } +# sp-externalities = { git = "https://github.com//gasp-xyz/polkadot-sdk", branch = "fix/rewards-market" } +# sp-mixnet = { git = "https://github.com//gasp-xyz/polkadot-sdk", branch = "fix/rewards-market" } +# sp-trie = { git = "https://github.com//gasp-xyz/polkadot-sdk", branch = "fix/rewards-market" } +# mangata-types = { git = "https://github.com//gasp-xyz/polkadot-sdk", branch = "fix/rewards-market" } +# sp-version = { git = "https://github.com//gasp-xyz/polkadot-sdk", branch = "fix/rewards-market" } +# sp-version-proc-macro = { git = "https://github.com//gasp-xyz/polkadot-sdk", branch = "fix/rewards-market" } +# sp-block-builder = { git = "https://github.com//gasp-xyz/polkadot-sdk", branch = "fix/rewards-market" } +# sp-keyring = { git = "https://github.com//gasp-xyz/polkadot-sdk", branch = "fix/rewards-market" } +# sp-staking = { git = "https://github.com//gasp-xyz/polkadot-sdk", branch = "fix/rewards-market" } +# sp-wasm-interface = { git = "https://github.com//gasp-xyz/polkadot-sdk", branch = "fix/rewards-market" } +# sp-transaction-storage-proof = { git = "https://github.com//gasp-xyz/polkadot-sdk", branch = "fix/rewards-market" } +# extrinsic-shuffler = { git = "https://github.com//gasp-xyz/polkadot-sdk", branch = "fix/rewards-market" } +# sp-arithmetic = { git = "https://github.com//gasp-xyz/polkadot-sdk", branch = "fix/rewards-market" } +# sp-api = { git = "https://github.com//gasp-xyz/polkadot-sdk", branch = "fix/rewards-market" } +# sp-api-proc-macro = { git = "https://github.com//gasp-xyz/polkadot-sdk", branch = "fix/rewards-market" } +# sp-offchain = { git = "https://github.com//gasp-xyz/polkadot-sdk", branch = "fix/rewards-market" } +# sp-timestamp = { git = "https://github.com//gasp-xyz/polkadot-sdk", branch = "fix/rewards-market" } +# sp-application-crypto = { git = "https://github.com//gasp-xyz/polkadot-sdk", branch = "fix/rewards-market" } +# sp-rpc = { git = "https://github.com//gasp-xyz/polkadot-sdk", branch = "fix/rewards-market" } +# ver-api = { git = "https://github.com//gasp-xyz/polkadot-sdk", branch = "fix/rewards-market" } +# sp-genesis-builder = { git = "https://github.com//gasp-xyz/polkadot-sdk", branch = "fix/rewards-market" } +# sp-session = { git = "https://github.com//gasp-xyz/polkadot-sdk", branch = "fix/rewards-market" } +# sc-consensus-grandpa = { git = "https://github.com//gasp-xyz/polkadot-sdk", branch = "fix/rewards-market" } +# sc-consensus = { git = "https://github.com//gasp-xyz/polkadot-sdk", branch = "fix/rewards-market" } +# sc-consensus-aura = { git = "https://github.com//gasp-xyz/polkadot-sdk", branch = "fix/rewards-market" } +# sc-consensus-slots = { git = "https://github.com//gasp-xyz/polkadot-sdk", branch = "fix/rewards-market" } +# sc-tracing = { git = "https://github.com//gasp-xyz/polkadot-sdk", branch = "fix/rewards-market" } +# sc-tracing-proc-macro = { git = "https://github.com//gasp-xyz/polkadot-sdk", branch = "fix/rewards-market" } +# sc-rpc-spec-v2 = { git = "https://github.com//gasp-xyz/polkadot-sdk", branch = "fix/rewards-market" } +# sc-state-db = { git = "https://github.com//gasp-xyz/polkadot-sdk", branch = "fix/rewards-market" } +# sc-proposer-metrics = { git = "https://github.com//gasp-xyz/polkadot-sdk", branch = "fix/rewards-market" } +# sc-keystore = { git = "https://github.com//gasp-xyz/polkadot-sdk", branch = "fix/rewards-market" } +# sc-basic-authorship-ver = { git = "https://github.com//gasp-xyz/polkadot-sdk", branch = "fix/rewards-market" } +# sc-rpc-server = { git = "https://github.com//gasp-xyz/polkadot-sdk", branch = "fix/rewards-market" } +# sc-network = { git = "https://github.com//gasp-xyz/polkadot-sdk", branch = "fix/rewards-market" } +# sc-network-bitswap = { git = "https://github.com//gasp-xyz/polkadot-sdk", branch = "fix/rewards-market" } +# sc-network-transactions = { git = "https://github.com//gasp-xyz/polkadot-sdk", branch = "fix/rewards-market" } +# sc-network-common = { git = "https://github.com//gasp-xyz/polkadot-sdk", branch = "fix/rewards-market" } +# sc-network-light = { git = "https://github.com//gasp-xyz/polkadot-sdk", branch = "fix/rewards-market" } +# sc-network-sync = { git = "https://github.com//gasp-xyz/polkadot-sdk", branch = "fix/rewards-market" } +# sc-informant = { git = "https://github.com//gasp-xyz/polkadot-sdk", branch = "fix/rewards-market" } +# sc-transaction-pool = { git = "https://github.com//gasp-xyz/polkadot-sdk", branch = "fix/rewards-market" } +# sc-transaction-pool-api = { git = "https://github.com//gasp-xyz/polkadot-sdk", branch = "fix/rewards-market" } +# sc-utils = { git = "https://github.com//gasp-xyz/polkadot-sdk", branch = "fix/rewards-market" } +# sc-cli = { git = "https://github.com//gasp-xyz/polkadot-sdk", branch = "fix/rewards-market" } +# sc-chain-spec = { git = "https://github.com//gasp-xyz/polkadot-sdk", branch = "fix/rewards-market" } +# sc-chain-spec-derive = { git = "https://github.com//gasp-xyz/polkadot-sdk", branch = "fix/rewards-market" } +# sc-block-builder-ver = { git = "https://github.com//gasp-xyz/polkadot-sdk", branch = "fix/rewards-market" } +# sc-mixnet = { git = "https://github.com//gasp-xyz/polkadot-sdk", branch = "fix/rewards-market" } +# sc-block-builder = { git = "https://github.com//gasp-xyz/polkadot-sdk", branch = "fix/rewards-market" } +# sc-allocator = { git = "https://github.com//gasp-xyz/polkadot-sdk", branch = "fix/rewards-market" } +# sc-network-gossip = { git = "https://github.com//gasp-xyz/polkadot-sdk", branch = "fix/rewards-market" } +# sc-client-db = { git = "https://github.com//gasp-xyz/polkadot-sdk", branch = "fix/rewards-market" } +# sc-client-api = { git = "https://github.com//gasp-xyz/polkadot-sdk", branch = "fix/rewards-market" } +# sc-sysinfo = { git = "https://github.com//gasp-xyz/polkadot-sdk", branch = "fix/rewards-market" } +# sc-executor = { git = "https://github.com//gasp-xyz/polkadot-sdk", branch = "fix/rewards-market" } +# sc-executor-wasmtime = { git = "https://github.com//gasp-xyz/polkadot-sdk", branch = "fix/rewards-market" } +# sc-executor-common = { git = "https://github.com//gasp-xyz/polkadot-sdk", branch = "fix/rewards-market" } +# sc-executor-polkavm = { git = "https://github.com//gasp-xyz/polkadot-sdk", branch = "fix/rewards-market" } +# sc-telemetry = { git = "https://github.com//gasp-xyz/polkadot-sdk", branch = "fix/rewards-market" } +# sc-offchain = { git = "https://github.com//gasp-xyz/polkadot-sdk", branch = "fix/rewards-market" } +# sc-service = { git = "https://github.com//gasp-xyz/polkadot-sdk", branch = "fix/rewards-market" } +# sc-rpc = { git = "https://github.com//gasp-xyz/polkadot-sdk", branch = "fix/rewards-market" } +# sc-rpc-api = { git = "https://github.com//gasp-xyz/polkadot-sdk", branch = "fix/rewards-market" } + +# # patch generated by './scripts/dev_manifest.sh ../polkadot-sdk/' +# [patch."https://github.com/gasp-xyz/polkadot-sdk"] +# substrate-wasm-builder = { path = "../polkadot-sdk/substrate/utils/wasm-builder" } +# frame-benchmarking-cli = { path = "../polkadot-sdk/substrate/utils/frame/benchmarking-cli" } +# substrate-bip39 = { path = "../polkadot-sdk/substrate/utils/substrate-bip39" } +# substrate-prometheus-endpoint = { path = "../polkadot-sdk/substrate/utils/prometheus" } +# fork-tree = { path = "../polkadot-sdk/substrate/utils/fork-tree" } +# substrate-build-script-utils = { path = "../polkadot-sdk/substrate/utils/build-script-utils" } +# substrate-test-client = { path = "../polkadot-sdk/substrate/test-utils/client" } +# substrate-test-runtime = { path = "../polkadot-sdk/substrate/test-utils/runtime" } +# substrate-test-runtime-client = { path = "../polkadot-sdk/substrate/test-utils/runtime/client" } +# frame-try-runtime = { path = "../polkadot-sdk/substrate/frame/try-runtime" } +# pallet-timestamp = { path = "../polkadot-sdk/substrate/frame/timestamp" } +# pallet-membership = { path = "../polkadot-sdk/substrate/frame/membership" } +# pallet-sudo-mangata = { path = "../polkadot-sdk/substrate/frame/sudo-mangata" } +# pallet-treasury = { path = "../polkadot-sdk/substrate/frame/treasury" } +# pallet-collective-mangata = { path = "../polkadot-sdk/substrate/frame/collective-mangata" } +# frame-system = { path = "../polkadot-sdk/substrate/frame/system" } +# frame-system-benchmarking = { path = "../polkadot-sdk/substrate/frame/system/benchmarking" } +# frame-system-rpc-runtime-api = { path = "../polkadot-sdk/substrate/frame/system/rpc/runtime-api" } +# mangata-support = { path = "../polkadot-sdk/substrate/frame/mangata-support" } +# pallet-balances = { path = "../polkadot-sdk/substrate/frame/balances" } +# pallet-aura = { path = "../polkadot-sdk/substrate/frame/aura" } +# pallet-vesting-mangata = { path = "../polkadot-sdk/substrate/frame/vesting-mangata" } +# pallet-session = { path = "../polkadot-sdk/substrate/frame/session" } +# pallet-utility-mangata = { path = "../polkadot-sdk/substrate/frame/utility-mangata" } +# pallet-identity = { path = "../polkadot-sdk/substrate/frame/identity" } +# pallet-utility = { path = "../polkadot-sdk/substrate/frame/utility" } +# pallet-authorship = { path = "../polkadot-sdk/substrate/frame/authorship" } +# frame-executive = { path = "../polkadot-sdk/substrate/frame/executive" } +# pallet-babe = { path = "../polkadot-sdk/substrate/frame/babe" } +# pallet-transaction-payment = { path = "../polkadot-sdk/substrate/frame/transaction-payment" } +# pallet-transaction-payment-rpc = { path = "../polkadot-sdk/substrate/frame/transaction-payment/rpc" } +# pallet-transaction-payment-rpc-runtime-api = { path = "../polkadot-sdk/substrate/frame/transaction-payment/rpc/runtime-api" } +# pallet-proxy = { path = "../polkadot-sdk/substrate/frame/proxy" } +# pallet-grandpa = { path = "../polkadot-sdk/substrate/frame/grandpa" } +# frame-support = { path = "../polkadot-sdk/substrate/frame/support" } +# frame-support-procedural = { path = "../polkadot-sdk/substrate/frame/support/procedural" } +# frame-support-procedural-tools = { path = "../polkadot-sdk/substrate/frame/support/procedural/tools" } +# frame-support-procedural-tools-derive = { path = "../polkadot-sdk/substrate/frame/support/procedural/tools/derive" } +# frame-benchmarking = { path = "../polkadot-sdk/substrate/frame/benchmarking" } +# sp-application-crypto = { path = "../polkadot-sdk/substrate/primitives/application-crypto" } +# sp-timestamp = { path = "../polkadot-sdk/substrate/primitives/timestamp" } +# sp-crypto-hashing = { path = "../polkadot-sdk/substrate/primitives/crypto/hashing" } +# sp-crypto-hashing-proc-macro = { path = "../polkadot-sdk/substrate/primitives/crypto/hashing/proc-macro" } +# sp-metadata-ir = { path = "../polkadot-sdk/substrate/primitives/metadata-ir" } +# sp-state-machine = { path = "../polkadot-sdk/substrate/primitives/state-machine" } +# sp-tracing = { path = "../polkadot-sdk/substrate/primitives/tracing" } +# sp-mixnet = { path = "../polkadot-sdk/substrate/primitives/mixnet" } +# sp-wasm-interface = { path = "../polkadot-sdk/substrate/primitives/wasm-interface" } +# sp-ver = { path = "../polkadot-sdk/substrate/primitives/ver" } +# sp-blockchain = { path = "../polkadot-sdk/substrate/primitives/blockchain" } +# sp-block-builder = { path = "../polkadot-sdk/substrate/primitives/block-builder" } +# sp-storage = { path = "../polkadot-sdk/substrate/primitives/storage" } +# sp-session = { path = "../polkadot-sdk/substrate/primitives/session" } +# sp-arithmetic = { path = "../polkadot-sdk/substrate/primitives/arithmetic" } +# sp-consensus = { path = "../polkadot-sdk/substrate/primitives/consensus/common" } +# sp-consensus-aura = { path = "../polkadot-sdk/substrate/primitives/consensus/aura" } +# sp-consensus-babe = { path = "../polkadot-sdk/substrate/primitives/consensus/babe" } +# sp-consensus-slots = { path = "../polkadot-sdk/substrate/primitives/consensus/slots" } +# sp-consensus-grandpa = { path = "../polkadot-sdk/substrate/primitives/consensus/grandpa" } +# sp-runtime-interface = { path = "../polkadot-sdk/substrate/primitives/runtime-interface" } +# sp-runtime-interface-proc-macro = { path = "../polkadot-sdk/substrate/primitives/runtime-interface/proc-macro" } +# ver-api = { path = "../polkadot-sdk/substrate/primitives/ver-api" } +# sp-keystore = { path = "../polkadot-sdk/substrate/primitives/keystore" } +# mangata-types = { path = "../polkadot-sdk/substrate/primitives/mangata-types" } +# sp-trie = { path = "../polkadot-sdk/substrate/primitives/trie" } +# sp-transaction-pool = { path = "../polkadot-sdk/substrate/primitives/transaction-pool" } +# sp-api = { path = "../polkadot-sdk/substrate/primitives/api" } +# sp-api-proc-macro = { path = "../polkadot-sdk/substrate/primitives/api/proc-macro" } +# sp-std = { path = "../polkadot-sdk/substrate/primitives/std" } +# sp-keyring = { path = "../polkadot-sdk/substrate/primitives/keyring" } +# sp-version = { path = "../polkadot-sdk/substrate/primitives/version" } +# sp-version-proc-macro = { path = "../polkadot-sdk/substrate/primitives/version/proc-macro" } +# sp-weights = { path = "../polkadot-sdk/substrate/primitives/weights" } +# sp-core = { path = "../polkadot-sdk/substrate/primitives/core" } +# sp-maybe-compressed-blob = { path = "../polkadot-sdk/substrate/primitives/maybe-compressed-blob" } +# extrinsic-shuffler = { path = "../polkadot-sdk/substrate/primitives/shuffler" } +# sp-panic-handler = { path = "../polkadot-sdk/substrate/primitives/panic-handler" } +# sp-inherents = { path = "../polkadot-sdk/substrate/primitives/inherents" } +# sp-transaction-storage-proof = { path = "../polkadot-sdk/substrate/primitives/transaction-storage-proof" } +# sp-runtime = { path = "../polkadot-sdk/substrate/primitives/runtime" } +# sp-io = { path = "../polkadot-sdk/substrate/primitives/io" } +# sp-database = { path = "../polkadot-sdk/substrate/primitives/database" } +# sp-debug-derive = { path = "../polkadot-sdk/substrate/primitives/debug-derive" } +# sp-staking = { path = "../polkadot-sdk/substrate/primitives/staking" } +# sp-rpc = { path = "../polkadot-sdk/substrate/primitives/rpc" } +# sp-offchain = { path = "../polkadot-sdk/substrate/primitives/offchain" } +# sp-statement-store = { path = "../polkadot-sdk/substrate/primitives/statement-store" } +# sp-externalities = { path = "../polkadot-sdk/substrate/primitives/externalities" } +# sp-genesis-builder = { path = "../polkadot-sdk/substrate/primitives/genesis-builder" } +# sc-allocator = { path = "../polkadot-sdk/substrate/client/allocator" } +# sc-rpc-spec-v2 = { path = "../polkadot-sdk/substrate/client/rpc-spec-v2" } +# sc-utils = { path = "../polkadot-sdk/substrate/client/utils" } +# sc-informant = { path = "../polkadot-sdk/substrate/client/informant" } +# sc-basic-authorship-ver = { path = "../polkadot-sdk/substrate/client/basic-authorship-ver" } +# sc-tracing = { path = "../polkadot-sdk/substrate/client/tracing" } +# sc-tracing-proc-macro = { path = "../polkadot-sdk/substrate/client/tracing/proc-macro" } +# sc-mixnet = { path = "../polkadot-sdk/substrate/client/mixnet" } +# sc-state-db = { path = "../polkadot-sdk/substrate/client/state-db" } +# sc-executor = { path = "../polkadot-sdk/substrate/client/executor" } +# sc-executor-polkavm = { path = "../polkadot-sdk/substrate/client/executor/polkavm" } +# sc-executor-wasmtime = { path = "../polkadot-sdk/substrate/client/executor/wasmtime" } +# sc-executor-common = { path = "../polkadot-sdk/substrate/client/executor/common" } +# sc-block-builder = { path = "../polkadot-sdk/substrate/client/block-builder" } +# sc-telemetry = { path = "../polkadot-sdk/substrate/client/telemetry" } +# sc-consensus = { path = "../polkadot-sdk/substrate/client/consensus/common" } +# sc-consensus-aura = { path = "../polkadot-sdk/substrate/client/consensus/aura" } +# sc-consensus-slots = { path = "../polkadot-sdk/substrate/client/consensus/slots" } +# sc-consensus-grandpa = { path = "../polkadot-sdk/substrate/client/consensus/grandpa" } +# sc-keystore = { path = "../polkadot-sdk/substrate/client/keystore" } +# sc-block-builder-ver = { path = "../polkadot-sdk/substrate/client/block-builder-ver" } +# sc-proposer-metrics = { path = "../polkadot-sdk/substrate/client/proposer-metrics" } +# sc-transaction-pool = { path = "../polkadot-sdk/substrate/client/transaction-pool" } +# sc-transaction-pool-api = { path = "../polkadot-sdk/substrate/client/transaction-pool/api" } +# sc-network = { path = "../polkadot-sdk/substrate/client/network" } +# sc-network-common = { path = "../polkadot-sdk/substrate/client/network/common" } +# sc-network-bitswap = { path = "../polkadot-sdk/substrate/client/network/bitswap" } +# sc-network-sync = { path = "../polkadot-sdk/substrate/client/network/sync" } +# sc-network-light = { path = "../polkadot-sdk/substrate/client/network/light" } +# sc-network-transactions = { path = "../polkadot-sdk/substrate/client/network/transactions" } +# sc-client-api = { path = "../polkadot-sdk/substrate/client/api" } +# sc-network-gossip = { path = "../polkadot-sdk/substrate/client/network-gossip" } +# sc-chain-spec = { path = "../polkadot-sdk/substrate/client/chain-spec" } +# sc-chain-spec-derive = { path = "../polkadot-sdk/substrate/client/chain-spec/derive" } +# sc-rpc-api = { path = "../polkadot-sdk/substrate/client/rpc-api" } +# sc-cli = { path = "../polkadot-sdk/substrate/client/cli" } +# sc-service = { path = "../polkadot-sdk/substrate/client/service" } +# sc-rpc-server = { path = "../polkadot-sdk/substrate/client/rpc-servers" } +# sc-sysinfo = { path = "../polkadot-sdk/substrate/client/sysinfo" } +# sc-client-db = { path = "../polkadot-sdk/substrate/client/db" } +# sc-rpc = { path = "../polkadot-sdk/substrate/client/rpc" } +# sc-offchain = { path = "../polkadot-sdk/substrate/client/offchain" } + +# # patch generated by './scripts/dev_manifest.sh ../open-runtime-module-library/' +# [patch."https://github.com/gasp-xyz/open-runtime-module-library"] +# orml-tokens = { path = "../open-runtime-module-library/tokens" } +# orml-asset-registry = { path = "../open-runtime-module-library/asset-registry" } +# orml-traits = { path = "../open-runtime-module-library/traits" } +# orml-utilities = { path = "../open-runtime-module-library/utilities" } + +# # patch generated by './scripts/dev_manifest.sh ../open-runtime-module-library/' +# [patch."https://github.com/gasp-xyz/open-runtime-module-library"] +# orml-tokens = { git = "https://github.com//gasp-xyz/open-runtime-module-library", branch = "feature/transfer-allowlist" } +# orml-asset-registry = { git = "https://github.com//gasp-xyz/open-runtime-module-library", branch = "feature/transfer-allowlist" } +# orml-traits = { git = "https://github.com//gasp-xyz/open-runtime-module-library", branch = "feature/transfer-allowlist" } +# orml-utilities = { git = "https://github.com//gasp-xyz/open-runtime-module-library", branch = "feature/transfer-allowlist" } diff --git a/gasp-node/Dockerfile b/gasp-node/Dockerfile new file mode 100644 index 000000000..3fddf3642 --- /dev/null +++ b/gasp-node/Dockerfile @@ -0,0 +1,35 @@ +FROM mangatasolutions/node-builder:multi-1.77-nightly-2024-01-20 AS builder + +WORKDIR /app + +COPY . . + +ARG ENABLE_FAST_RUNTIME=false +ARG ENABLE_UNLOCKED_RUNTIME=false + +# Set RUSTC_WRAPPER to empty string by default, as it's causing random issues on arm64 image builds in CI +# To enable sccache set RUSTC_WRAPPER build arg to "sccache" in your image build command with `--build-arg RUSTC_WRAPPER=sccache` +ARG RUSTC_WRAPPER="" + +RUN --mount=type=cache,target=/usr/local/cargo/registry,sharing=locked \ + --mount=type=cache,target=/usr/local/cargo/git,sharing=locked \ + --mount=type=cache,target=/app/target,sharing=locked \ + export RUSTC_WRAPPER=${RUSTC_WRAPPER} && \ + if [ "$ENABLE_FAST_RUNTIME" = "true" ]; then \ + cargo build --release --no-default-features --features=fast-runtime; \ + elif [ "$ENABLE_UNLOCKED_RUNTIME" = "true" ]; then \ + cargo build --release --no-default-features --features=unlocked; \ + else \ + cargo build --locked --release; \ + fi \ + # copy build artifacts to the root directory to avoid issues with accessing cache mount from 2nd stage + && cp target/release/rollup-node ./node \ + && cp target/release/wbuild/rollup-runtime/rollup_runtime.compact.compressed.wasm ./rollup_runtime.compact.compressed.wasm + +FROM debian:buster AS runner + +WORKDIR /app + +COPY --from=builder /app/node /app/rollup_runtime.compact.compressed.wasm /app/ + +ENTRYPOINT ["/app/node"] diff --git a/gasp-node/LICENSE b/gasp-node/LICENSE new file mode 100644 index 000000000..cf1ab25da --- /dev/null +++ b/gasp-node/LICENSE @@ -0,0 +1,24 @@ +This is free and unencumbered software released into the public domain. + +Anyone is free to copy, modify, publish, use, compile, sell, or +distribute this software, either in source code form or as a compiled +binary, for any purpose, commercial or non-commercial, and by any +means. + +In jurisdictions that recognize copyright laws, the author or authors +of this software dedicate any and all copyright interest in the +software to the public domain. We make this dedication for the benefit +of the public at large and to the detriment of our heirs and +successors. We intend this dedication to be an overt act of +relinquishment in perpetuity of all present and future rights to this +software under copyright law. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR +OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, +ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +OTHER DEALINGS IN THE SOFTWARE. + +For more information, please refer to diff --git a/gasp-node/Mangata.md b/gasp-node/Mangata.md new file mode 100644 index 000000000..b570365f0 --- /dev/null +++ b/gasp-node/Mangata.md @@ -0,0 +1,66 @@ +# Block construction/execution +Mangata implements front-running bots prevention on automated market maker crypto exchange. For that reason it was decided to split block production and block execution into two following blocks. Execution of the transaction included in block N is delayed by 1 block comparing to origin substrate implementation. It is following block producer (N+1) who calculates the order of transactions execution from the previous block. This way none is able to foresee in what order transactions will be performed. + +Affected creates: +- [sc-block-builder](https://docs.rs/sc-block-builder/0.8.0/sc_block_builder/index.html) - execution of extrisnics from previous block on block creation + +# Shuffling +Extrinsics are randomly shuffled but still preservers original order per every account - motivation for such algorithm was the fact that following transactions from a given account may depend on other transactions from the same account. + + +Origin order: +``` +| Alice TX1 | Alice TX2 | Alice TX3 | Bob TX1 | Bob TX2 | +``` +example shuffled order: + +``` +| Alice TX1 | Bob TX1 | Alice TX2 | Bob TX2 | Alice TX3 | +``` + +As shuffling occurs on block execution not creation, every node needs to calculate the same order and agree on storage state afterwards. That is achieved using Fisher-Yates shuffling algorithm with Xoshiro256++ as PRNG initialized with seed stored in blockchain storage. Usage of sr25519 key pair together with VRF capabilities guarantees that block producer cannot impact execution order and it's easy to validate if the seed was properly calculated by validators/following nodes (thanks to VRF verification that only requires block author public key). Seed itself is stored into the storage and is publically accessible through dedicated runtime API [RandomSeedApi](https://github.com/mangata-finance/mangata-node/blob/59b8e6d27c76f89cddbad777ffbeafd1d7f86297/pallets/random-seed/runtime-api/src/lib.rs). + +PRNG initialization seed for block `N-1` is signature of 'input message' signed with block producer private key where 'input message' is defined as: +- Seed from block `N-1` +- babe epoch randomness (changed in every epoch) + +There is no way for block producer to manipulate seed value in order to impact execution order of extrinsics - also block producer is not able to calculate seed for a block he is going to author sooner than N-1 block is produced. + +Seed value is injected into the block as [InherentData](https://docs.rs/sp-inherents/2.0.0/sp_inherents/struct.InherentData.html) by Babe consensus protocol. Whether block producer finds out it's his turn to create a block new seed is being calculated. Seeds value is validated (VRF verify) by other nodes when new block is being imported - in case of seed calculation violation block will be rejected. + + +Affected crates +- [sc-block-builder](https://docs.rs/sc-block-builder/0.8.0/sc_block_builder/index.html) - extracting seed value on inherents creation +- [sc-basic-authorship](https://docs.rs/sc-basic-authorship/0.8.0/sc_basic_authorship/index.html) - shuffling extrinsics before passing them to block builder +- [sc-consensus-babe](https://docs.rs/sc-basic-authorship/0.8.0/sc_basic_authorship/index.html) - calcuating and injecting shuffling seed value into InherentData, shuffling seed verification +- [sc-service](https://docs.rs/sc-service/0.8.0/sc_service/index.html) - fetching seed value and extrinsic shuffling for 'following nodes' +#Tokens + +Mangata uses concept of liquidity tokens. One can create a liquidity pool(together with liquidity pool token) using a pair of tokens and then use the tokens of that pool to stake. New currency trait [`MultiTokenCurrency`](https://github.com/mangata-finance/mangata-node/blob/0846c42a7b7fd29e19fd1b30043ddb3b55a8f250/pallets/tokens/src/multi_token_currency.rs#L14) was introduced and integrated with [staking pallet](https://github.com/mangata-finance/mangata-node/tree/59b8e6d27c76f89cddbad777ffbeafd1d7f86297/pallets/staking). Origin implementation was not sufficient as it only works with single, statically defined currency in our case we require supporting multiple dynamically created currencies. + + +Affected crates +- [orml-tokens](https://docs.rs/orml-tokens/0.3.1/orml_tokens/index.html) - Introduced new `MultiTokenCurrency` trait, using it as `Balances` replacement +- [pallet-staking](https://docs.rs/pallet-staking/2.0.0/pallet_staking/index.html) - Alining with `MultiTokenCurrency` trait. + +#JS API +Because of shuffled delayed transaction execution same applies to events triggered by extrinsics. Origin JS API is not sufficient to map extrinsics to triggered events. Link between transaction <-> event is done based on transaction/events order. In Mangata order of extrinsics inside the block differs from their actual execution order. For that reason dedicated async API methods were introduced to provide missing functionality. When transaction is sent: +1. API waits for notification that it has been included in block N +2. API fetches block N and N+1 +3. API stores information about list of events produced in block N+1 +3. API reads seed from block N+1 and calculate execution order of extrinsics from block N +4. API maps events to shuffled extrinsics list + +from API client perspective it's as easy as +``` + signAndWaitTx( + api.tx.tokens.transfer(to, asset_id, amount), + from, + nonce + ).then( (events) => { + events.forEach((e) => { + console.log(e.toHuman()) + }); + }) + +``` diff --git a/gasp-node/README.md b/gasp-node/README.md new file mode 100644 index 000000000..70ffb3bf2 --- /dev/null +++ b/gasp-node/README.md @@ -0,0 +1,81 @@ +

+ + Mangata brand +

+ +

Mangata Node

+ +

+ Omnichain zk-rollup for L1-grade native liquidity. Implementation includes MEV solution, Proof-of-Liquidity, gas-free swaps, algorithmic buy & burn, weight voting & liquidity gauges, time-incentivized liquidity provision, 3rd party incentives, and more. +

+ +![Themis](https://blog.mangata.finance/assets/posts/themis-cover.png) + +![Issues](https://img.shields.io/github/issues/mangata-finance/mangata-node) +![Pull Request](https://img.shields.io/github/issues-pr/mangata-finance/mangata-node) +![GitHub last commit](https://img.shields.io/github/last-commit/mangata-finance/mangata-node) +![Build Status](https://img.shields.io/endpoint.svg?url=https%3A%2F%2Factions-badge.atrox.dev%2Fmangata-finance%2Fmangata-node%2Fbadge%3Fref%3Ddevelop&style=flat) +![Language](https://img.shields.io/github/languages/top/mangata-finance/mangata-node) + +## Description + +Mangata operates as a cross-chain liquidity protocol, facilitating seamless transactions between Ethereum and various other blockchains through a omnichain zk-rollup Infrastructure. We leverage the power of ZK-rollup, a second-layer (L2) solution, to ensure universal connectivity with first-layer (L1) blockchains. Additionally, our decentralized exchange platform is designed to provide robust protection against Miner Extractable Value (MEV) and frontrunning attempts, thereby safeguarding the interests of our users. + +## API + +[Mangata API Docs](https://mangata-finance.notion.site/Mangata-API-Docs-06f68bc6ba004416ae5c6686163b0468) + +## Build rollup-node locally +- Install [docker](https://docs.docker.com/engine/install/ubuntu/) + +### Compile rollup-node binary and wasms artifacts +- use docker wrapper for cargo to build `rollup-node` + +``` +./docker-cargo.sh build --release -p rollup-node +``` + +build artifacts will be placed in `/docker-cargo/release` + +### Run tests and generate code coverage report +Run unit tests only: +```bash +cargo test +``` +Run unit tests and generate code coverage report in html format: +```bash +cargo install cargo-tarpaulin +cargo tarpaulin --timeout 120 --workspace -e rollup-runtime-integration-test rollup-node --exclude-files **/mock.rs **/weights.rs **/weights/* --out Html +``` + +### Building Docker image and running local network + +Because of number of parameters is quite troublesome thats why we came up with dedicated dockerized environment. + +Use next commands to operate local dockerized environment using Docker Compose: +```bash +# To build Docker image +docker compose build + +# To start local 2-nodes network +docker compose up --build -d + +# To stop local 2-nodes network +docker compose down +``` + +Once started, you can access nodes using port forwards +- [127.0.0.1:9944](https://polkadot.js.org/apps/?rpc=ws%3A%2F%2F127.0.0.1%3A9944#/explorer) - `node-alice` node +- [127.0.0.1:9946](https://polkadot.js.org/apps/?rpc=ws%3A%2F%2F127.0.0.1%3A9946#/explorer) - `node-bob` node + +### Sudo access +`Alice` is set as sudo account for docker setup + +## Mangata node configuration + +There is number of chain configurations available for both development and production environements: + +| chainspec (`--chain`) | Sudo | Description | +|-------------------------------|----------------|---------------------------------------------------------------| +| `rollup-local` | ******* | development rollup local testnet | +| `rollup-local-seq` | Alice | development rollup local testnet with collators as sequencers | diff --git a/gasp-node/compose.yml b/gasp-node/compose.yml new file mode 100644 index 000000000..0da6ad7c6 --- /dev/null +++ b/gasp-node/compose.yml @@ -0,0 +1,44 @@ +# To get `gaspxyz/rollup-node:local` image run next command from the root of the directory: +# docker build -t gaspxyz/rollup-node:local . +# +# Note: You can also skip the step and let the docker-compose to build the image for you, +# but for some reason it keeps loosing cache, so next rebuilds would take longer, than they should. +name: gasp-node + +services: + node-alice: + build: . + image: ${NODE_IMAGE:-gaspxyz/rollup-node:local} + command: + - --alith + - --chain=rollup-local-seq + - --base-path=/data + - --rpc-cors=all + - --unsafe-rpc-external + - --node-key=0000000000000000000000000000000000000000000000000000000000000001 + - --rpc-methods=unsafe + ports: + - 9944:9944 + - 30333:30333 + volumes: + - data-alith:/data + + node-bob: + build: . + image: ${NODE_IMAGE:-gaspxyz/rollup-node:local} + command: + - --baltathar + - --chain=rollup-local-seq + - --base-path=/data + - --rpc-cors=all + - --unsafe-rpc-external + - --bootnodes=/dns/node-alice/tcp/30333/p2p/12D3KooWEyoppNCUx8Yx66oV9fJnriXwCcXwDDUA2kj6vnc6iDEp + - --rpc-methods=unsafe + ports: + - 9946:9944 + volumes: + - data-baltathar:/data + +volumes: + data-alith: + data-baltathar: diff --git a/gasp-node/docker-cargo.sh b/gasp-node/docker-cargo.sh new file mode 100755 index 000000000..c40ce9f13 --- /dev/null +++ b/gasp-node/docker-cargo.sh @@ -0,0 +1,87 @@ +#!/bin/bash -x +REPO_ROOT=$(readlink -f $(dirname $(readlink -f $0))) +CODE_ROOT=${CODE_ROOT:-${REPO_ROOT}} +OUTPUT_DIR=docker-cargo/ +CARGO_HOME=${CARGO_HOME:-$HOME/.cargo} + +DOCKER_BUILDER_IMAGE=${DOCKER_BUILDER_IMAGE:-mangatasolutions/node-builder:multi-1.77-nightly-2024-01-20} +DOCKER_USER="$(id -u):$(id -g)" +DOCKER_JOB_NAME=cargo-wrapper +if [ -n "${DISABLE_TTY}" ]; then + ALLOCATE_TTY_OR_NOT="-i" +else + ALLOCATE_TTY_OR_NOT="-it" +fi + +CARGO_COMMAND=$1 + +CARGO_ARGS=${@:2} + +if [ "$CARGO_COMMAND" == "kill" ]; then + docker kill ${DOCKER_JOB_NAME} + exit 0 +fi + +if ! which docker >/dev/null; then + echo "docker not installed" >&2 + exit 1 +fi + +if docker inspect ${DOCKER_BUILDER_IMAGE} >/dev/null; then + echo "building using docker image ${DOCKER_BUILDER_IMAGE}" +else + echo "docker image ${DOCKER_BUILDER_IMAGE} not found - pulling" >&2 + docker pull ${DOCKER_BUILDER_IMAGE} +fi + +if [ -n "${REUSE_LOCAL_CACHE}" ]; then + if [ -e ${CARGO_HOME} ]; then + CARGO_CACHE_GIT=${CARGO_HOME}/git + CARGO_CACHE_REGISTRY=${CARGO_HOME}/registry + + if [ ! -e ${CARGO_CACHE_GIT} ]; then + mkdir -p ${CARGO_CACHE_GIT} + fi + + if [ ! -e ${CARGO_CACHE_REGISTRY} ]; then + mkdir -p ${CARGO_CACHE_REGISTRY} + fi + else + echo "CARGO_HOME not set" >&2 + exit 1 + fi + if ! [ -e ${SCCACHE_DIR} ]; then + echo "SCCACHE_DIR not set" >&2 + exit 1 + fi +else + CARGO_CACHE_GIT=${REPO_ROOT}/${OUTPUT_DIR}/cache/cargo/git + CARGO_CACHE_REGISTRY=${REPO_ROOT}/${OUTPUT_DIR}/cache/cargo/registry + SCCACHE_DIR=${REPO_ROOT}/${OUTPUT_DIR}/cache/sccache + if [ ! -e ${CARGO_CACHE_REGISTRY} ]; then + mkdir -p ${CARGO_CACHE_REGISTRY} + fi + if [ ! -e ${CARGO_CACHE_GIT} ]; then + mkdir -p ${CARGO_CACHE_GIT} + fi + if [ ! -e ${SCCACHE_DIR} ]; then + mkdir -p ${SCCACHE_DIR} + fi +fi + +if [ -n "${DISABLE_CARGO_CACHE}" ]; then + DOCKER_MOUNT_CACHE_VOLUMES="" +else + DOCKER_MOUNT_CACHE_VOLUMES="-v ${CARGO_CACHE_GIT}:/usr/local/cargo/git -v ${CARGO_CACHE_REGISTRY}:/usr/local/cargo/registry -v ${SCCACHE_DIR}:/.cache/sccache" +fi + +docker run \ + --rm \ + --name=${DOCKER_JOB_NAME} \ + --user $DOCKER_USER \ + -v ${CODE_ROOT}:/code \ + ${DOCKER_MOUNT_CACHE_VOLUMES} \ + ${DOCKER_RUN_EXTRA_ARGS} \ + -e CARGO_TARGET_DIR="/code/${OUTPUT_DIR}" \ + ${ALLOCATE_TTY_OR_NOT} ${DOCKER_BUILDER_IMAGE} \ + /bin/bash -c "cargo ${CARGO_COMMAND} --manifest-path=/code/Cargo.toml ${CARGO_ARGS} && sccache --show-stats" diff --git a/gasp-node/pallets/bootstrap/Cargo.toml b/gasp-node/pallets/bootstrap/Cargo.toml new file mode 100644 index 000000000..4e3bf7e88 --- /dev/null +++ b/gasp-node/pallets/bootstrap/Cargo.toml @@ -0,0 +1,73 @@ +[package] +authors = ["Mangata Team"] +edition = "2018" +license = "Unlicense" +name = "pallet-bootstrap" +version = "0.1.0" + +[package.metadata.docs.rs] +targets = ["x86_64-unknown-linux-gnu"] + +[dependencies] +codec = { workspace = true, default-features = false } +log = { workspace = true, default-features = false } +scale-info = { workspace = true, features = ["derive"], default-features = false } +serde = { workspace = true, optional = true } + +frame-benchmarking = { workspace = true, default-features = false } +frame-executive = { workspace = true, default-features = false } +frame-support = { workspace = true, default-features = false } +frame-system = { workspace = true, default-features = false } +frame-try-runtime = { workspace = true, optional = true, default-features = false } +mangata-support = { workspace = true, default-features = false } +mangata-types = { workspace = true, default-features = false } +pallet-vesting-mangata = { workspace = true, default-features = false } +sp-arithmetic = { workspace = true, default-features = false } +sp-core = { workspace = true, default-features = false } +sp-io = { workspace = true, default-features = false } +sp-runtime = { workspace = true, default-features = false } +sp-std = { workspace = true, default-features = false } + +orml-tokens = { workspace = true, default-features = false } + +[dev-dependencies] +lazy_static.workspace = true +mockall.workspace = true +serial_test.workspace = true +test-case.workspace = true + +pallet-issuance = { path = "../issuance", default-features = false } +pallet-proof-of-stake = { path = "../proof-of-stake" } +pallet-xyk = { path = "../xyk" } + +mangata-support = { workspace = true, default-features = false } +sp-core = { workspace = true, default-features = false } +sp-io = { workspace = true, default-features = false } + +orml-traits = { workspace = true, default-features = false } + +[features] +default = ["std"] +runtime-benchmarks = ["frame-benchmarking/runtime-benchmarks"] +std = [ + "codec/std", + "frame-benchmarking/std", + "frame-support/std", + "frame-system/std", + "mangata-support/std", + "orml-tokens/std", + "pallet-vesting-mangata/std", + "serde", + "sp-io/std", + "sp-runtime/std", + "sp-std/std", +] +try-runtime = [ + "frame-executive/try-runtime", + "frame-support/try-runtime", + "frame-system/try-runtime", + "frame-try-runtime", + "orml-tokens/try-runtime", + "pallet-vesting-mangata/try-runtime", + "sp-runtime/try-runtime", +] diff --git a/gasp-node/pallets/bootstrap/src/benchmarking.rs b/gasp-node/pallets/bootstrap/src/benchmarking.rs new file mode 100644 index 000000000..5a49f5c81 --- /dev/null +++ b/gasp-node/pallets/bootstrap/src/benchmarking.rs @@ -0,0 +1,183 @@ +// This file is part of Substrate. + +// Copyright (C) 2020-2022 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. + +//! Balances pallet benchmarking. + +#![cfg(feature = "runtime-benchmarks")] + +use super::*; + +use frame_benchmarking::{benchmarks, whitelisted_caller}; +use frame_support::assert_ok; +use frame_system::RawOrigin; +use orml_tokens::MultiTokenCurrencyExtended; + +use crate::Pallet as BootstrapPallet; + +const MILION: u128 = 1_000__000_000__000_000; +const DEFAULT_RATIO: (u32, u32) = (1_u32, 10_000_u32); + +fn default_ratio() -> (BalanceOf, BalanceOf) { + (DEFAULT_RATIO.0.into(), DEFAULT_RATIO.1.into()) +} + +benchmarks! { + + schedule_bootstrap { + frame_system::Pallet::::set_block_number(1_u32.into()); + assert!(crate::BootstrapSchedule::::get().is_none()); + let caller: T::AccountId = whitelisted_caller(); + let first_token_id = ::Currency::create(&caller, MILION.try_into().ok().expect("should fit")).expect("Token creation failed"); + let second_token_id = ::Currency::create(&caller, MILION.try_into().ok().expect("should fit")).expect("Token creation failed"); + }: schedule_bootstrap(RawOrigin::Root, first_token_id, second_token_id, 123_456_789_u32.into(), Some(100_000_u32), 100_000_u32, Some(default_ratio::()), true) + verify { + assert!(crate::BootstrapSchedule::::get().is_some()); + } + + provision { + frame_system::Pallet::::set_block_number(1_u32.into()); + let caller: T::AccountId = whitelisted_caller(); + let first_token_id = ::Currency::create(&caller, MILION.try_into().ok().expect("should fit")).expect("Token creation failed"); + let second_token_id = ::Currency::create(&caller, MILION.try_into().ok().expect("should fit")).expect("Token creation failed"); + let ksm_provision_amount = 100_000_u32; + let mga_provision_amount = ksm_provision_amount * DEFAULT_RATIO.1 / DEFAULT_RATIO.0; + + BootstrapPallet::::schedule_bootstrap(RawOrigin::Root.into(), first_token_id, second_token_id, 10_u32.into(), Some(10_u32), 10_u32, Some(default_ratio::()), false).unwrap(); + // jump to public phase + BootstrapPallet::::on_initialize(20_u32.into()); + BootstrapPallet::::provision(RawOrigin::Signed(caller.clone().into()).into(), second_token_id, mga_provision_amount.into()).unwrap(); + + }: provision(RawOrigin::Signed(caller.clone().into()), first_token_id, ksm_provision_amount.into()) + verify { + assert_eq!(BootstrapPallet::::provisions(caller, first_token_id), ksm_provision_amount.into()); + } + + // provision_vested { + // frame_system::Pallet::::set_block_number(1_u32.into()); + // let caller: T::AccountId = whitelisted_caller(); + // let first_token_id = ::Currency::create(&caller, MILION.try_into().unwrap()).expect("Token creation failed").into(); + // let second_token_id = ::Currency::create(&caller, MILION.try_into().unwrap()).expect("Token creation failed").into(); + + // let ksm_provision_amount = 100_000_u128; + // let mga_provision_amount = ksm_provision_amount * DEFAULT_RATIO.1 / DEFAULT_RATIO.0; + + // let lock = 100_000_000_u128; + + // frame_system::Pallet::::set_block_number(1_u32.into()); + // ::VestingProvider::lock_tokens(&caller, first_token_id.into(), (ksm_provision_amount*2).into(), None, lock.into()).unwrap(); + // frame_system::Pallet::::set_block_number(2_u32.into()); + + // BootstrapPallet::::schedule_bootstrap(RawOrigin::Root.into(), first_token_id, second_token_id, Some(10_u32.into()), 10_u32, 10_u32, Some(DEFAULT_RATIO), false).unwrap(); + // // jump to public phase + // BootstrapPallet::::on_initialize(20_u32.into()); + // BootstrapPallet::::provision(RawOrigin::Signed(caller.clone().into()).into(), second_token_id, mga_provision_amount).unwrap(); + + // assert_eq!(BootstrapPallet::::vested_provisions(caller.clone(), first_token_id), (0, 0, 0)); + // }: provision_vested(RawOrigin::Signed(caller.clone().into()), first_token_id, ksm_provision_amount) + // verify { + // assert_eq!(BootstrapPallet::::vested_provisions(caller, first_token_id).0, ksm_provision_amount); + // } + + claim_and_activate_liquidity_tokens { + frame_system::Pallet::::set_block_number(1_u32.into()); + let caller: T::AccountId = whitelisted_caller(); + let first_token_id = ::Currency::create(&caller, MILION.try_into().ok().expect("should fit")).expect("Token creation failed"); + let second_token_id = ::Currency::create(&caller, MILION.try_into().ok().expect("should fit")).expect("Token creation failed"); + let liquidity_asset_id = second_token_id + 1_u32.into(); + + let ksm_provision_amount = 100_000_u32; + // let ksm_vested_provision_amount = 300_000_u128; + let ksm_vested_provision_amount = 0_u32; + let mga_provision_amount = ksm_provision_amount * DEFAULT_RATIO.1 / DEFAULT_RATIO.0; + let mga_vested_provision_amount = ksm_vested_provision_amount * DEFAULT_RATIO.1 / DEFAULT_RATIO.0; + let total_ksm_provision = (ksm_provision_amount + ksm_vested_provision_amount).into(); + let total_mga_provision = (mga_provision_amount + mga_vested_provision_amount).into(); + let total_provision = total_ksm_provision + total_mga_provision; + let lock = 150_u32; + + ::VestingProvider::lock_tokens(&caller, first_token_id, (ksm_provision_amount + ksm_vested_provision_amount).into(), None, lock.into()).unwrap(); + ::VestingProvider::lock_tokens(&caller, second_token_id, (mga_provision_amount + mga_vested_provision_amount).into(), None, lock.into()).unwrap(); + + BootstrapPallet::::schedule_bootstrap(RawOrigin::Root.into(), first_token_id, second_token_id, 10_u32.into(), Some(10_u32), 10_u32, Some(default_ratio::()), false).unwrap(); + BootstrapPallet::::on_initialize(20_u32.into()); + BootstrapPallet::::provision(RawOrigin::Signed(caller.clone().into()).into(), second_token_id, mga_provision_amount.into()).unwrap(); + BootstrapPallet::::provision(RawOrigin::Signed(caller.clone().into()).into(), first_token_id, ksm_provision_amount.into()).unwrap(); + // BootstrapPallet::::provision_vested(RawOrigin::Signed(caller.clone().into()).into(), second_token_id, mga_vested_provision_amount).unwrap(); + // BootstrapPallet::::provision_vested(RawOrigin::Signed(caller.clone().into()).into(), first_token_id, ksm_vested_provision_amount).unwrap(); + BootstrapPallet::::on_initialize(30_u32.into()); + + assert_eq!(BootstrapPallet::::phase(), BootstrapPhase::Finished); + assert_eq!(BootstrapPallet::::claimed_rewards(caller.clone(), first_token_id), 0_u32.into()); + assert_eq!(BootstrapPallet::::claimed_rewards(caller.clone(), second_token_id), 0_u32.into()); + assert_eq!(BootstrapPallet::::valuations(), (total_mga_provision, total_ksm_provision)); + assert_eq!(BootstrapPallet::::provisions(caller.clone(), first_token_id), (ksm_provision_amount.into())); + assert_eq!(BootstrapPallet::::provisions(caller.clone(), second_token_id), (mga_provision_amount.into())); + // assert_eq!(BootstrapPallet::::vested_provisions(caller.clone(), first_token_id), (ksm_vested_provision_amount, 1, lock + 1)); + // assert_eq!(BootstrapPallet::::vested_provisions(caller.clone(), second_token_id), (mga_vested_provision_amount, 1, lock + 1)); + + // promote pool + T::RewardsApi::enable(liquidity_asset_id, 1_u8); + }: claim_and_activate_liquidity_tokens(RawOrigin::Signed(caller.clone().into())) + verify { + let (total_mga_provision, total_ksm_provision) = BootstrapPallet::::valuations(); + let ksm_non_vested_rewards = total_provision / 4_u32.into() * ksm_provision_amount.into() / total_ksm_provision; + let ksm_vested_rewards = total_provision / 4_u32.into() * ksm_vested_provision_amount.into() / total_ksm_provision; + let mga_non_vested_rewards = total_provision / 4_u32.into() * mga_provision_amount.into() / total_mga_provision; + let mga_vested_rewards = total_provision / 4_u32.into() * mga_vested_provision_amount.into() / total_mga_provision; + + assert_eq!(BootstrapPallet::::claimed_rewards(caller.clone(), first_token_id), ksm_vested_rewards + ksm_non_vested_rewards); + assert_eq!(BootstrapPallet::::claimed_rewards(caller.clone(), second_token_id), mga_vested_rewards + mga_non_vested_rewards); + } + + finalize { + frame_system::Pallet::::set_block_number(1_u32.into()); + let caller: T::AccountId = whitelisted_caller(); + let first_token_id = ::Currency::create(&caller, MILION.try_into().ok().expect("should fit")).expect("Token creation failed"); + let second_token_id = ::Currency::create(&caller, MILION.try_into().ok().expect("should fit")).expect("Token creation failed"); + + let ksm_provision_amount = 100_000_u32; + // let ksm_vested_provision_amount = 300_000_u128; + let ksm_vested_provision_amount = 0_u32; + let mga_provision_amount = ksm_provision_amount * DEFAULT_RATIO.1 / DEFAULT_RATIO.0; + let mga_vested_provision_amount = ksm_vested_provision_amount * DEFAULT_RATIO.1 / DEFAULT_RATIO.0; + let total_ksm_provision = ksm_provision_amount + ksm_vested_provision_amount; + let total_mga_provision = mga_provision_amount + mga_vested_provision_amount; + let total_provision = total_ksm_provision + total_mga_provision; + let lock = 150_u32; + + ::VestingProvider::lock_tokens(&caller, first_token_id, (ksm_provision_amount + ksm_vested_provision_amount).into(), None, lock.into()).unwrap(); + ::VestingProvider::lock_tokens(&caller, second_token_id, (mga_provision_amount + mga_vested_provision_amount).into(), None, lock.into()).unwrap(); + + BootstrapPallet::::schedule_bootstrap(RawOrigin::Root.into(), first_token_id, second_token_id, 10_u32.into(), Some(10_u32), 10_u32, Some(default_ratio::()), false).unwrap(); + BootstrapPallet::::on_initialize(20_u32.into()); + BootstrapPallet::::provision(RawOrigin::Signed(caller.clone().into()).into(), second_token_id, mga_provision_amount.into()).unwrap(); + BootstrapPallet::::provision(RawOrigin::Signed(caller.clone().into()).into(), first_token_id, ksm_provision_amount.into()).unwrap(); + // BootstrapPallet::::provision_vested(RawOrigin::Signed(caller.clone().into()).into(), second_token_id, mga_vested_provision_amount).unwrap(); + // BootstrapPallet::::provision_vested(RawOrigin::Signed(caller.clone().into()).into(), first_token_id, ksm_vested_provision_amount).unwrap(); + BootstrapPallet::::on_initialize(30_u32.into()); + + BootstrapPallet::::claim_liquidity_tokens(RawOrigin::Signed(caller.clone().into()).into()).unwrap(); + assert_eq!(BootstrapPallet::::phase(), BootstrapPhase::Finished); + + assert_ok!(BootstrapPallet::::pre_finalize(RawOrigin::Signed(caller.clone().into()).into())); + }: finalize(RawOrigin::Signed(caller.clone().into())) + verify { + assert_eq!(BootstrapPallet::::phase(), BootstrapPhase::BeforeStart); + } + + impl_benchmark_test_suite!(BootstrapPallet, crate::mock::new_test_ext(), crate::mock::Test) +} diff --git a/gasp-node/pallets/bootstrap/src/lib.rs b/gasp-node/pallets/bootstrap/src/lib.rs new file mode 100644 index 000000000..fdddfcd9d --- /dev/null +++ b/gasp-node/pallets/bootstrap/src/lib.rs @@ -0,0 +1,1254 @@ +// Copyright (C) 2020 Mangata team + +#![cfg_attr(not(feature = "std"), no_std)] + +//! # Bootstrap Module +//! +//! The Bootstrap module provides price discovery mechanism between two tokens. When bootstrap +//! is finished all provisioned tokens are collected and used for new liquidity token(pool) creation. +//! From that moment people that participated in bootstrap can claim their liquidity tokens share using +//! dedicated. Also since that moment its possible to exchange/trade tokens that were bootstraped using `Xyk `pallet. +//! +//! +//! ### Features +//! +//! * Bootstrap pallet is reusable** - after bootstrap between tokens `X` and `Y` is finished the following one can be scheduled (with different pair of tokens). +//! * After bootstrap is finished new liquidity token (`Z`) is created and [`pallet_xyk`] can be used to: +//! * exchange/trade `X` and `Y` tokens +//! * mint/burn `Z` tokens +//! +//! * Bootstrap state transition from [`BeforeStart`] -> [`Finished`] happens automatically thanks to substrate framework +//! hooks. **Only transition from `Finished` -> `BeforeStart` needs to be triggered manually because +//! cleaning up storage is complex operation and might not fit in a single block.(as it needs to +//! remove a lot of keys/value pair from the runtime storage)** +//! +//! # How to bootstrap +//! 1. Entity with sudo privileges needs to use [`Pallet::schedule_bootstrap`] to initiate new bootstrap +//! +//! 1.1 [**optional**] depending on fact if [`BootstrapPhase::Whitelist`] is enabled entity +//! with sudo privileges can whitelist particular users using [`Pallet::whitelist_accounts`] +//! +//! 1.2 [**optional**] [`Pallet::update_promote_bootstrap_pool`] can be used to enable or disable +//! automatic pool promotion of liquidity pool. +//! +//! 1.3 [**optional**] [`Pallet::cancel_bootstrap`] can be used to cancel bootstrap event +//! +//! 2. When blockchain reaches block that is scheduled as start od the bootstrap participation is +//! automatically enabled: +//! * in [`BootstrapPhase::Whitelist`] phase only whitelisted accounts [`Pallet::whitelist_accounts`] +//! can participate +//! * in [`BootstrapPhase::Public`] phase everyone can participate +//! +//! 3. When blockchain reaches block: +//! ```ignore +//! current_block_nr > bootstrap_start_block + whitelist_phase_length + public_phase_length +//! ``` +//! +//! Bootstrap is automatically finished and following participations will not be accepted. Also new +//! liquidity pool is created from all the tokens gathered during bootstrap (see [`Valuations`]). `TokenId` +//! of newly created liquidity token as well as amount of minted tokens is persisted into [`MintedLiquidity`] +//! storage item. All the liquidity token minted as a result of pool creation are now stored in +//! bootstrap pallet account. +//! +//! 4. Accounts that participated in bootstrap can claim their liquidity pool share. Share is +//! calculated proportionally based on provisioned amount. One can use one of below extrinsics to +//! claim rewards: +//! * [`Pallet::claim_liquidity_tokens`] +//! * [`Pallet::claim_and_activate_liquidity_tokens`] +//! +//! 5. When every participant of the bootstrap has claimed their liquidity tokens entity with sudo +//! rights can [`Pallet::finalize`] whole bootstrap event. If there are some accounts that still +//! hasnt claim their tokens [`Pallet::claim_liquidity_tokens_for_account`] can be used to do +//! that in behalf of these accounts. When [`Pallet::finalize`] results with [`Event::BootstrapFinalized`] +//! Bootstrap is finalized and another bootstrap can be scheduled (as described in 1st point). +//! +//! Bootstrap has specific lifecycle as presented below: +//! ```plantuml +//! @startuml +//! [*] --> BeforeStart +//! BeforeStart --> Whitelist +//! BeforeStart --> Public +//! Whitelist --> Public +//! Public --> Finished +//! Finished --> BeforeStart +//! @enduml +//! ``` +//! +//! # API +//! +//! ## Runtime Storage Entries +//! +//! - [`Provisions`] - stores information about who provisioned what (non vested tokens) +//! +//! - [`VestedProvisions`] - stores information about who provisioned what (vested tokens) +//! +//! - [`WhitelistedAccount`] - list of accounts allowed to participate in [`BootstrapPhase::Whitelist`] +//! +//! - [`Phase`] - current state of bootstrap +//! +//! - [`Valuations`] - sum of all provisions in active bootstrap +//! +//! - [`BootstrapSchedule`] - parameters of active bootstrap stored as for more details check [`Pallet::schedule_bootstrap`] +//! +//! ```ignore +//! [ +//! block_nr: T::BlockNumber, +//! first_token_id: u32, +//! second_token_id: u32, +//! [ +//! ratio_numerator:u128, +//! ratio_denominator:u128 +//! ] +//! ] +//! ``` +//! +//! - [`ClaimedRewards`] - how many liquidity tokens has user already **after** bootstrap +//! [`BootstrapPhase::Public`] period has finished. +//! +//! - [`ProvisionAccounts`] - list of participants that hasnt claim their tokens yet +//! +//! - [`ActivePair`] - bootstraped pair of tokens +//! +//! ## Extrinsics +//! +//! * [`Pallet::schedule_bootstrap`] +//! * [`Pallet::whitelist_accounts`] +//! * [`Pallet::update_promote_bootstrap_pool`] +//! * [`Pallet::cancel_bootstrap`] +//! * [`Pallet::provision`] +//! * [`Pallet::claim_liquidity_tokens`] +//! * [`Pallet::claim_liquidity_tokens_for_account`] +//! * [`Pallet::claim_and_activate_liquidity_tokens`] +//! * [`Pallet::finalize`] +//! +//! for more details see [click](#how-to-bootstrap) +//! +//! +use frame_support::pallet_prelude::*; + +use codec::{Decode, Encode}; +use frame_support::{ + traits::{ + Contains, ExistenceRequirement, Get, MultiTokenCurrency, MultiTokenVestingLocks, + StorageVersion, + }, + transactional, PalletId, +}; +use frame_system::{ensure_root, ensure_signed, pallet_prelude::*}; + +use mangata_support::traits::{ + AssetRegistryApi, GetMaintenanceStatusTrait, PoolCreateApi, ProofOfStakeRewardsApi, +}; +use mangata_types::multipurpose_liquidity::ActivateKind; + +use orml_tokens::{MultiTokenCurrencyExtended, MultiTokenReservableCurrency}; +use scale_info::TypeInfo; +use sp_arithmetic::{helpers_128bit::multiply_by_rational_with_rounding, per_things::Rounding}; +use sp_core::U256; +use sp_io::KillStorageResult; +use sp_runtime::traits::{ + AccountIdConversion, Bounded, CheckedAdd, One, SaturatedConversion, Saturating, Zero, +}; +use sp_std::{convert::TryInto, prelude::*}; + +#[cfg(test)] +mod mock; + +mod benchmarking; + +#[cfg(test)] +mod tests; + +pub mod weights; +pub use weights::WeightInfo; + +pub use pallet::*; +const PALLET_ID: PalletId = PalletId(*b"bootstrp"); + +#[macro_export] +macro_rules! log { + ($level:tt, $patter:expr $(, $values:expr)* $(,)?) => { + log::$level!( + target: "bootstrap", + concat!("[{:?}] 💸 ", $patter), >::block_number() $(, $values)* + ) + }; +} + +type BalanceOf = <::Currency as MultiTokenCurrency< + ::AccountId, +>>::Balance; + +type CurrencyIdOf = <::Currency as MultiTokenCurrency< + ::AccountId, +>>::CurrencyId; + +type BlockNrAsBalance = BalanceOf; + +#[frame_support::pallet] +pub mod pallet { + use super::*; + + const STORAGE_VERSION: StorageVersion = StorageVersion::new(2); + + #[pallet::pallet] + #[pallet::without_storage_info] + #[pallet::storage_version(STORAGE_VERSION)] + pub struct Pallet(PhantomData); + + #[pallet::hooks] + impl Hooks> for Pallet { + fn on_initialize(n: BlockNumberFor) -> Weight { + let phase = Phase::::get(); // R:1 + if phase == BootstrapPhase::Finished { + return T::DbWeight::get().reads(1) + } + + if let Some((start, whitelist_length, public_length, _)) = BootstrapSchedule::::get() + { + // R:1 + // NOTE: arythmetics protected by invariant check in Bootstrap::start_ido + let whitelist_start = start; + let public_start = start + whitelist_length.into(); + let finished = start + whitelist_length.into() + public_length.into(); + + if n >= finished { + Phase::::put(BootstrapPhase::Finished); // 1 WRINTE + log!(info, "bootstrap event finished"); + let (second_token_valuation, first_token_valuation) = Valuations::::get(); + + // one updated takes R:2, W:2; and multiply for two assets + if !T::AssetRegistryApi::enable_pool_creation(( + Self::first_token_id(), + Self::second_token_id(), + )) { + log!(error, "cannot modify asset registry!"); + } + // XykFunctionsTrait R: 11 W:12 + // PoolCreateApi::pool_create R:2 + + // --------------------------------- + // R: 13 W 12 + if let Some((liq_asset_id, issuance)) = T::PoolCreateApi::pool_create( + Self::vault_address(), + Self::first_token_id(), + first_token_valuation, + Self::second_token_id(), + second_token_valuation, + ) { + MintedLiquidity::::put((liq_asset_id, issuance)); // W:1 + if PromoteBootstrapPool::::get() { + T::RewardsApi::enable( + liq_asset_id, + T::DefaultBootstrapPromotedPoolWeight::get(), + ); + } + } else { + log!(error, "cannot create pool!"); + } + // TODO: include cost of pool_create call + T::DbWeight::get().reads_writes(21, 18) + } else if n >= public_start { + if phase != BootstrapPhase::Public { + Phase::::put(BootstrapPhase::Public); + log!(info, "starting public phase"); + T::DbWeight::get().reads_writes(2, 1) + } else { + T::DbWeight::get().reads(2) + } + } else if n >= whitelist_start { + if phase != BootstrapPhase::Whitelist { + log!(info, "starting whitelist phase"); + Phase::::put(BootstrapPhase::Whitelist); + T::DbWeight::get().reads_writes(2, 1) + } else { + T::DbWeight::get().reads(2) + } + } else { + T::DbWeight::get().reads(2) + } + } else { + T::DbWeight::get().reads(2) + } + } + } + + #[cfg(feature = "runtime-benchmarks")] + pub trait BootstrapBenchmarkingConfig {} + + #[cfg(not(feature = "runtime-benchmarks"))] + pub trait BootstrapBenchmarkingConfig {} + + /// Configure the pallet by specifying the parameters and types on which it depends. + #[pallet::config] + pub trait Config: frame_system::Config + BootstrapBenchmarkingConfig { + /// Because this pallet emits events, it depends on the runtime's definition of an event. + type RuntimeEvent: From> + IsType<::RuntimeEvent>; + + type MaintenanceStatusProvider: GetMaintenanceStatusTrait; + + /// tokens + type Currency: MultiTokenCurrencyExtended + + MultiTokenReservableCurrency; + + type PoolCreateApi: PoolCreateApi, CurrencyIdOf>; + + #[pallet::constant] + type DefaultBootstrapPromotedPoolWeight: Get; + + #[pallet::constant] + type BootstrapUpdateBuffer: Get>; + + #[pallet::constant] + type TreasuryPalletId: Get; + + type VestingProvider: MultiTokenVestingLocks< + Self::AccountId, + Currency = Self::Currency, + Moment = BlockNumberFor, + >; + + type ClearStorageLimit: Get; + + type WeightInfo: WeightInfo; + + type RewardsApi: ProofOfStakeRewardsApi< + Self::AccountId, + BalanceOf, + CurrencyIdOf, + >; + + type AssetRegistryApi: AssetRegistryApi>; + } + + /// maps ([`frame_system::Config::AccountId`], [`CurrencyId`]) -> [`Balance`] - identifies how much tokens did account provisioned in active bootstrap + #[pallet::storage] + #[pallet::getter(fn provisions)] + pub type Provisions = StorageDoubleMap< + _, + Twox64Concat, + T::AccountId, + Twox64Concat, + CurrencyIdOf, + BalanceOf, + ValueQuery, + >; + + /// maps ([`frame_system::Config::AccountId`], [`CurrencyId`]) -> [`Balance`] - identifies how much vested tokens did account provisioned in active bootstrap + #[pallet::storage] + #[pallet::getter(fn vested_provisions)] + pub type VestedProvisions = StorageDoubleMap< + _, + Twox64Concat, + T::AccountId, + Twox64Concat, + CurrencyIdOf, + (BalanceOf, BlockNrAsBalance, BlockNrAsBalance), + ValueQuery, + >; + + /// list ([`Vec`]) of whitelisted accounts allowed to participate in [`BootstrapPhase::Whitelist`] phase + #[pallet::storage] + #[pallet::getter(fn whitelisted_accounts)] + pub type WhitelistedAccount = + StorageMap<_, Twox64Concat, T::AccountId, (), ValueQuery>; + + /// Current state of bootstrap as [`BootstrapPhase`] + #[pallet::storage] + #[pallet::getter(fn phase)] + pub type Phase = StorageValue<_, BootstrapPhase, ValueQuery>; + + /// Total sum of provisions of `first` and `second` token in active bootstrap + #[pallet::storage] + #[pallet::getter(fn valuations)] + pub type Valuations = StorageValue<_, (BalanceOf, BalanceOf), ValueQuery>; + + /// Active bootstrap parameters + #[pallet::storage] + #[pallet::getter(fn config)] + pub type BootstrapSchedule = + StorageValue<_, (BlockNumberFor, u32, u32, (BalanceOf, BalanceOf)), OptionQuery>; + + #[pallet::storage] + #[pallet::getter(fn minted_liquidity)] + pub type MintedLiquidity = + StorageValue<_, (CurrencyIdOf, BalanceOf), ValueQuery>; + + /// Maps ([`frame_system::Config::AccountId`], [`CurrencyId`] ) -> [`Balance`] - where [`CurrencyId`] is id of the token that user participated with. This storage item is used to identify how much liquidity tokens has been claim by the user. If user participated with 2 tokens there are two entries associated with given account (`Address`, `first_token_id`) and (`Address`, `second_token_id`) + #[pallet::storage] + #[pallet::getter(fn claimed_rewards)] + pub type ClaimedRewards = StorageDoubleMap< + _, + Twox64Concat, + T::AccountId, + Twox64Concat, + CurrencyIdOf, + BalanceOf, + ValueQuery, + >; + + /// List of accounts that provisioned funds to bootstrap and has not claimed liquidity tokens yet + #[pallet::storage] + #[pallet::getter(fn provision_accounts)] + pub type ProvisionAccounts = + StorageMap<_, Twox64Concat, T::AccountId, (), OptionQuery>; + + /// Currently bootstraped pair of tokens representaed as [ `first_token_id`, `second_token_id`] + #[pallet::storage] + #[pallet::getter(fn pair)] + pub type ActivePair = + StorageValue<_, (CurrencyIdOf, CurrencyIdOf), OptionQuery>; + + /// Wheter to automatically promote the pool after [`BootstrapPhase::PublicPhase`] or not. + #[pallet::storage] + #[pallet::getter(fn get_promote_bootstrap_pool)] + pub type PromoteBootstrapPool = StorageValue<_, bool, ValueQuery>; + + #[pallet::storage] + #[pallet::getter(fn archived)] + pub type ArchivedBootstrap = StorageValue< + _, + Vec<(BlockNumberFor, u32, u32, (BalanceOf, BalanceOf))>, + ValueQuery, + >; + + #[pallet::call] + impl Pallet { + // /// provisions vested/locked tokens into the boostrstrap + // #[pallet::weight(<::WeightInfo>::provision_vested())] + // #[transactional] + // pub fn provision_vested( + // origin: OriginFor, + // token_id: TokenId, + // amount: Balance, + // ) -> DispatchResult { + // let sender = ensure_signed(origin)?; + // + // ensure!(!T::MaintenanceStatusProvider::is_maintenance(), Error::::ProvisioningBlockedByMaintenanceMode); + // + // let (vesting_starting_block, vesting_ending_block_as_balance) = + // <::VestingProvider>::unlock_tokens( + // &sender, + // token_id.into(), + // amount.into(), + // ) + // .map_err(|_| Error::::NotEnoughVestedAssets)?; + // Self::do_provision( + // &sender, + // token_id, + // amount, + // ProvisionKind::Vested( + // vesting_starting_block.saturated_into::(), + // vesting_ending_block_as_balance.into(), + // ), + // )?; + // ProvisionAccounts::::insert(&sender, ()); + // Self::deposit_event(Event::Provisioned(token_id, amount)); + // Ok(().into()) + // } + + /// Allows for provisioning one of the tokens from currently bootstrapped pair. Can only be called during: + /// - [`BootstrapPhase::Whitelist`] + /// - [`BootstrapPhase::Public`] + /// + /// phases. + /// + /// # Args: + /// - `token_id` - id of the token to provision (should be one of the currently bootstraped pair([`ActivePair`])) + /// - `amount` - amount of the token to provision + #[pallet::call_index(0)] + #[pallet::weight(<::WeightInfo>::provision())] + #[transactional] + pub fn provision( + origin: OriginFor, + token_id: CurrencyIdOf, + amount: BalanceOf, + ) -> DispatchResult { + let sender = ensure_signed(origin)?; + + ensure!( + !T::MaintenanceStatusProvider::is_maintenance(), + Error::::ProvisioningBlockedByMaintenanceMode + ); + + Self::do_provision(&sender, token_id, amount)?; + ProvisionAccounts::::insert(&sender, ()); + Self::deposit_event(Event::Provisioned(token_id, amount)); + Ok(()) + } + + /// Allows for whitelisting accounts, so they can participate in during whitelist phase. The list of + /// account is extended with every subsequent call + #[pallet::call_index(1)] + #[pallet::weight(T::DbWeight::get().writes(1) * (accounts.len() as u64))] + #[transactional] + pub fn whitelist_accounts( + origin: OriginFor, + accounts: Vec, + ) -> DispatchResult { + ensure_root(origin)?; + for account in accounts { + WhitelistedAccount::::insert(&account, ()); + } + Self::deposit_event(Event::AccountsWhitelisted); + Ok(()) + } + + /// Used for starting/scheduling new bootstrap + /// + /// # Args: + /// - `first_token_id` - first token of the tokens pair + /// - `second_token_id`: second token of the tokens pair + /// - `ido_start` - number of block when bootstrap will be started (people will be allowed to participate) + /// - `whitelist_phase_length`: - length of whitelist phase + /// - `public_phase_lenght`- length of public phase + /// - `promote_bootstrap_pool`- whether liquidity pool created by bootstrap should be promoted + /// - `max_first_to_second_ratio` - represented as (numerator,denominator) - Ratio may be used to limit participations of second token id. Ratio between first and second token needs to be held during whole bootstrap. Whenever user tries to participate (using [`Pallet::provision`] extrinsic) the following conditions is check. + /// ```ignore + /// all previous first participations + first token participations ratio numerator + /// ----------------------------------------------------------------------- <= ------------------ + /// all previous second token participations + second token participations ratio denominator + /// ``` + /// and if it evaluates to `false` extrinsic will fail. + /// + /// **Because of above equation only participations with first token of a bootstrap pair are limited!** + /// + /// # Examples + /// Consider: + /// + /// - user willing to participate 1000 of first token, when: + /// - ratio set during bootstrap schedule is is set to (1/2) + /// - sum of first token participations - 10_000 + /// - sum of second token participations - 20_000 + /// + /// participation extrinsic will **fail** because ratio condition **is not met** + /// ```ignore + /// 10_000 + 10_000 1 + /// --------------- <= --- + /// 20_000 2 + /// ``` + /// + /// - user willing to participate 1000 of first token, when: + /// - ratio set during bootstrap schedule is is set to (1/2) + /// - sum of first token participations - 10_000 + /// - sum of second token participations - 40_000 + /// + /// participation extrinsic will **succeed** because ratio condition **is met** + /// ```ignore + /// 10_000 + 10_000 1 + /// --------------- <= --- + /// 40_000 2 + /// ``` + /// + /// + /// **If one doesn't want to limit participations in any way, ratio should be set to (u128::MAX,0) - then ratio requirements are always met** + /// + /// ```ignore + /// all previous first participations + first token participations u128::MAX + /// ----------------------------------------------------------------------- <= ------------------ + /// all previous second token participations + second token participations 1 + /// ``` + #[pallet::call_index(2)] + #[pallet::weight(<::WeightInfo>::schedule_bootstrap())] + #[transactional] + pub fn schedule_bootstrap( + origin: OriginFor, + first_token_id: CurrencyIdOf, + second_token_id: CurrencyIdOf, + ido_start: BlockNumberFor, + whitelist_phase_length: Option, + public_phase_length: u32, + max_first_to_second_ratio: Option<(BalanceOf, BalanceOf)>, + promote_bootstrap_pool: bool, + ) -> DispatchResult { + ensure_root(origin)?; + + ensure!(Phase::::get() == BootstrapPhase::BeforeStart, Error::::AlreadyStarted); + + if let Some((scheduled_ido_start, _, _, _)) = BootstrapSchedule::::get() { + let now = >::block_number(); + ensure!( + now.saturating_add(T::BootstrapUpdateBuffer::get()) < scheduled_ido_start, + Error::::TooLateToUpdateBootstrap + ); + } + + ensure!(first_token_id != second_token_id, Error::::SameToken); + + ensure!(T::Currency::exists(first_token_id), Error::::TokenIdDoesNotExists); + ensure!(T::Currency::exists(second_token_id), Error::::TokenIdDoesNotExists); + + ensure!( + ido_start > frame_system::Pallet::::block_number(), + Error::::BootstrapStartInThePast + ); + + let whitelist_phase_length = whitelist_phase_length.unwrap_or_default(); + let max_first_to_second_ratio = max_first_to_second_ratio + .unwrap_or((BalanceOf::::max_value(), BalanceOf::::one())); + + ensure!(max_first_to_second_ratio.0 != BalanceOf::::zero(), Error::::WrongRatio); + + ensure!(max_first_to_second_ratio.1 != BalanceOf::::zero(), Error::::WrongRatio); + + ensure!(public_phase_length > 0, Error::::PhaseLengthCannotBeZero); + + ensure!( + ido_start + .checked_add(&whitelist_phase_length.into()) + .and_then(|whiteslist_start| whiteslist_start + .checked_add(&public_phase_length.into())) + .is_some(), + Error::::MathOverflow + ); + + ensure!( + ido_start.checked_add(&whitelist_phase_length.into()).is_some(), + Error::::MathOverflow + ); + + ensure!( + !T::PoolCreateApi::pool_exists(first_token_id, second_token_id), + Error::::PoolAlreadyExists + ); + + ActivePair::::put((first_token_id, second_token_id)); + BootstrapSchedule::::put(( + ido_start, + whitelist_phase_length, + public_phase_length, + max_first_to_second_ratio, + )); + + PromoteBootstrapPool::::put(promote_bootstrap_pool); + + Ok(()) + } + + /// Used to cancel active bootstrap. Can only be called before bootstrap is actually started + #[pallet::call_index(3)] + #[pallet::weight(T::DbWeight::get().reads_writes(3, 4).saturating_add(Weight::from_parts(1_000_000, 0)))] + #[transactional] + pub fn cancel_bootstrap(origin: OriginFor) -> DispatchResult { + ensure_root(origin)?; + + // BootstrapSchedule should exist but not after BootstrapUpdateBuffer blocks before start + + let now = >::block_number(); + let (ido_start, _, _, _) = + BootstrapSchedule::::get().ok_or(Error::::BootstrapNotSchduled)?; + ensure!(Phase::::get() == BootstrapPhase::BeforeStart, Error::::AlreadyStarted); + + ensure!( + now.saturating_add(T::BootstrapUpdateBuffer::get()) < ido_start, + Error::::TooLateToUpdateBootstrap + ); + + ActivePair::::kill(); + BootstrapSchedule::::kill(); + PromoteBootstrapPool::::kill(); + // Unnecessary + Phase::::put(BootstrapPhase::BeforeStart); + + Ok(()) + } + + #[pallet::call_index(4)] + #[pallet::weight(T::DbWeight::get().reads_writes(2, 1).saturating_add(Weight::from_parts(1_000_000, 0)))] + #[transactional] + // can be used to enable or disable automatic pool promotion of liquidity pool. Updates [`PromoteBootstrapPool`] + pub fn update_promote_bootstrap_pool( + origin: OriginFor, + promote_bootstrap_pool: bool, + ) -> DispatchResult { + ensure_root(origin)?; + + // BootstrapSchedule should exist but not finalized + // we allow this to go thru if the BootstrapSchedule exists and the phase is before finalized + + ensure!(BootstrapSchedule::::get().is_some(), Error::::BootstrapNotSchduled); + ensure!(Phase::::get() != BootstrapPhase::Finished, Error::::BootstrapFinished); + + PromoteBootstrapPool::::put(promote_bootstrap_pool); + + Ok(()) + } + + /// When bootstrap is in [`BootstrapPhase::Finished`] state user can claim his part of liquidity tokens. + #[pallet::call_index(5)] + #[pallet::weight(<::WeightInfo>::claim_and_activate_liquidity_tokens())] + #[transactional] + pub fn claim_liquidity_tokens(origin: OriginFor) -> DispatchResult { + let sender = ensure_signed(origin)?; + Self::do_claim_liquidity_tokens(&sender, false) + } + + /// When bootstrap is in [`BootstrapPhase::Finished`] state user can claim his part of liquidity tokens comparing to `claim_liquidity_tokens` when calling `claim_and_activate_liquidity_tokens` tokens will be automatically activated. + #[pallet::call_index(6)] + #[pallet::weight(<::WeightInfo>::claim_and_activate_liquidity_tokens())] + #[transactional] + pub fn claim_and_activate_liquidity_tokens(origin: OriginFor) -> DispatchResult { + let sender = ensure_signed(origin)?; + Self::do_claim_liquidity_tokens(&sender, true) + } + + /// Used to reset Bootstrap state of large storages and prepare it for running another bootstrap. + /// It should be called multiple times until it produces [`Event::BootstrapReadyToBeFinalized`] event. + /// + /// **!!! Cleaning up storage is complex operation and pruning all storage items related to particular + /// bootstrap might not fit in a single block. As a result tx can be rejected !!!** + #[pallet::call_index(7)] + #[pallet::weight(Weight::from_parts(40_000_000, 0) + .saturating_add(T::DbWeight::get().reads_writes(6, 0) + .saturating_add(T::DbWeight::get().reads_writes(1, 1).saturating_mul(Into::::into(T::ClearStorageLimit::get())))))] + #[transactional] + pub fn pre_finalize(origin: OriginFor) -> DispatchResult { + let _ = ensure_signed(origin)?; + + ensure!(Self::phase() == BootstrapPhase::Finished, Error::::NotFinishedYet); + + ensure!( + ProvisionAccounts::::iter_keys().next().is_none(), + Error::::BootstrapNotReadyToBeFinished + ); + + let mut limit = T::ClearStorageLimit::get(); + + match VestedProvisions::::clear(limit, None).into() { + KillStorageResult::AllRemoved(num_iter) => limit = limit.saturating_sub(num_iter), + KillStorageResult::SomeRemaining(_) => { + Self::deposit_event(Event::BootstrapParitallyPreFinalized); + return Ok(()) + }, + } + + match WhitelistedAccount::::clear(limit, None).into() { + KillStorageResult::AllRemoved(num_iter) => limit = limit.saturating_sub(num_iter), + KillStorageResult::SomeRemaining(_) => { + Self::deposit_event(Event::BootstrapParitallyPreFinalized); + return Ok(()) + }, + } + + match ClaimedRewards::::clear(limit, None).into() { + KillStorageResult::AllRemoved(num_iter) => limit = limit.saturating_sub(num_iter), + KillStorageResult::SomeRemaining(_) => { + Self::deposit_event(Event::BootstrapParitallyPreFinalized); + return Ok(()) + }, + } + + match Provisions::::clear(limit, None).into() { + KillStorageResult::AllRemoved(num_iter) => limit = limit.saturating_sub(num_iter), + KillStorageResult::SomeRemaining(_) => { + Self::deposit_event(Event::BootstrapParitallyPreFinalized); + return Ok(()) + }, + } + + Self::deposit_event(Event::BootstrapReadyToBeFinalized); + + Ok(()) + } + + /// Used to complete resetting Bootstrap state and prepare it for running another bootstrap. + /// It should be called after pre_finalize has produced the [`Event::BootstrapReadyToBeFinalized`] event. + #[pallet::call_index(8)] + #[pallet::weight(<::WeightInfo>::finalize())] + #[transactional] + pub fn finalize(origin: OriginFor) -> DispatchResult { + let _ = ensure_signed(origin)?; + + ensure!(Self::phase() == BootstrapPhase::Finished, Error::::NotFinishedYet); + + ensure!( + ProvisionAccounts::::iter_keys().next().is_none(), + Error::::BootstrapNotReadyToBeFinished + ); + + ensure!( + VestedProvisions::::iter_keys().next().is_none(), + Error::::BootstrapMustBePreFinalized + ); + + ensure!( + WhitelistedAccount::::iter_keys().next().is_none(), + Error::::BootstrapMustBePreFinalized + ); + + ensure!( + ClaimedRewards::::iter_keys().next().is_none(), + Error::::BootstrapMustBePreFinalized + ); + + ensure!( + Provisions::::iter_keys().next().is_none(), + Error::::BootstrapMustBePreFinalized + ); + + Phase::::put(BootstrapPhase::BeforeStart); + let (liq_token_id, _) = MintedLiquidity::::take(); + let balance = T::Currency::free_balance(liq_token_id.into(), &Self::vault_address()); + if balance > 0_u32.into() { + T::Currency::transfer( + liq_token_id.into(), + &Self::vault_address(), + &T::TreasuryPalletId::get().into_account_truncating(), + balance, + ExistenceRequirement::AllowDeath, + )?; + } + Valuations::::kill(); + ActivePair::::kill(); + PromoteBootstrapPool::::kill(); + + if let Some(bootstrap) = BootstrapSchedule::::take() { + ArchivedBootstrap::::mutate(|v| { + v.push(bootstrap); + }); + } + + Self::deposit_event(Event::BootstrapFinalized); + + Ok(()) + } + + /// Allows claiming rewards for some account that haven't done that yet. The only difference between + /// calling [`Pallet::claim_liquidity_tokens_for_account`] by some other account and calling [`Pallet::claim_liquidity_tokens`] directly by that account is account that will be charged for transaction fee. + /// # Args: + /// - `other` - account in behalf of which liquidity tokens should be claimed + #[pallet::call_index(9)] + #[pallet::weight(<::WeightInfo>::claim_and_activate_liquidity_tokens())] + #[transactional] + pub fn claim_liquidity_tokens_for_account( + origin: OriginFor, + account: T::AccountId, + activate_rewards: bool, + ) -> DispatchResult { + let _ = ensure_signed(origin)?; + Self::do_claim_liquidity_tokens(&account, activate_rewards) + } + } + + #[pallet::error] + /// Errors + pub enum Error { + /// Only scheduled token pair can be used for provisions + UnsupportedTokenId, + /// Not enough funds for provision + NotEnoughAssets, + /// Not enough funds for provision (vested) + NotEnoughVestedAssets, + /// Math problem + MathOverflow, + /// User cannot participate at this moment + Unauthorized, + /// Bootstrap cant be scheduled in past + BootstrapStartInThePast, + /// Bootstarap phases cannot lasts 0 blocks + PhaseLengthCannotBeZero, + /// Bootstrate event already started + AlreadyStarted, + /// Valuation ratio exceeded + ValuationRatio, + /// First provision must be in non restricted token + FirstProvisionInSecondTokenId, + /// Bootstraped pool already exists + PoolAlreadyExists, + /// Cannot claim rewards before bootstrap finish + NotFinishedYet, + /// no rewards to claim + NothingToClaim, + /// wrong ratio + WrongRatio, + /// no rewards to claim + BootstrapNotReadyToBeFinished, + /// Tokens used in bootstrap cannot be the same + SameToken, + /// Token does not exists + TokenIdDoesNotExists, + /// Token activations failed + TokensActivationFailed, + /// Bootstrap not scheduled + BootstrapNotSchduled, + /// Bootstrap already Finished + BootstrapFinished, + /// Bootstrap can only be updated or cancelled + /// BootstrapUpdateBuffer blocks or more before bootstrap start + TooLateToUpdateBootstrap, + /// Bootstrap provisioning blocked by maintenance mode + ProvisioningBlockedByMaintenanceMode, + /// Bootstrap must be pre finalized before it can be finalized + BootstrapMustBePreFinalized, + } + + #[pallet::event] + #[pallet::generate_deposit(pub(super) fn deposit_event)] + pub enum Event { + /// Funds provisioned + Provisioned(CurrencyIdOf, BalanceOf), + /// Funds provisioned using vested tokens + VestedProvisioned(CurrencyIdOf, BalanceOf), + /// The activation of the rewards liquidity tokens failed + RewardsLiquidityAcitvationFailed(T::AccountId, CurrencyIdOf, BalanceOf), + /// Rewards claimed + RewardsClaimed(CurrencyIdOf, BalanceOf), + /// account whitelisted + AccountsWhitelisted, + /// bootstrap pre finalization has completed partially + BootstrapParitallyPreFinalized, + /// bootstrap pre finalization has completed, and the bootstrap can now be finalized + BootstrapReadyToBeFinalized, + /// finalization process finished + BootstrapFinalized, + } +} + +#[derive(Eq, PartialEq, Encode, Decode, TypeInfo, Debug)] +pub enum BootstrapPhase { + /// Waiting for another bootstrap to be scheduled using [`Pallet::schedule_bootstrap`] + BeforeStart, + // Phase where only whitelisted accounts (see [`Bootstrap::whitelist_accounts`]) can participate with both tokens as long as particular accounts are whitelisted and ratio after participation is below enforced ratio. + Whitelist, + /// Anyone can participate as long as ratio after participation is below enforced ratio + Public, + /// Bootstrap has finished. At this phase users that participated in bootstrap during previous phases can claim their share of minted `liquidity tokens`. `Bootstrap::finalize` can be call to reset pallet state schedule following bootstrap again. + Finished, +} + +impl Default for BootstrapPhase { + fn default() -> Self { + BootstrapPhase::BeforeStart + } +} + +impl Pallet { + fn is_whitelisted(account: &T::AccountId) -> bool { + WhitelistedAccount::::try_get(account).is_ok() + } + + fn vault_address() -> T::AccountId { + PALLET_ID.into_account_truncating() + } + + fn claim_liquidity_tokens_from_single_currency( + who: &T::AccountId, + provision_token_id: &CurrencyIdOf, + rewards: BalanceOf, + rewards_vested: BalanceOf, + lock: (BlockNrAsBalance, BlockNrAsBalance), + ) -> DispatchResult { + let (liq_token_id, _) = Self::minted_liquidity(); + let total_rewards = rewards.checked_add(&rewards_vested).ok_or(Error::::MathOverflow)?; + if total_rewards == BalanceOf::::zero() { + return Ok(()) + } + + T::Currency::transfer( + liq_token_id.into(), + &Self::vault_address(), + who, + total_rewards, + ExistenceRequirement::KeepAlive, + )?; + + ClaimedRewards::::try_mutate(who, provision_token_id, |rewards| { + if let Some(val) = rewards.checked_add(&total_rewards) { + *rewards = val; + Ok(()) + } else { + Err(Error::::MathOverflow) + } + })?; + + if rewards_vested > BalanceOf::::zero() { + T::VestingProvider::lock_tokens( + who, + liq_token_id, + rewards_vested, + Some(lock.0.into().saturated_into()), + lock.1, + )?; + } + + Ok(()) + } + + /// + /// assures that + /// + /// actual_nominator expected_nominator + /// -------------------- <= ------------------ + /// actual_denominator expected_denominator + /// + /// actual_nominator * expected_denominator expected_nominator * actual_denominator + /// ---------------------------------------- <= ---------------------------------------- + /// actual_denominator * expected_denominator expected_denominator * actual_nominator + fn is_ratio_kept(ratio_nominator: BalanceOf, ratio_denominator: BalanceOf) -> bool { + let (second_token_valuation, first_token_valuation) = Valuations::::get(); + let left = U256::from(first_token_valuation.into()) * U256::from(ratio_denominator.into()); + let right = U256::from(ratio_nominator.into()) * U256::from(second_token_valuation.into()); + left <= right + } + + pub fn do_provision( + sender: &T::AccountId, + token_id: CurrencyIdOf, + amount: BalanceOf, + // is_vested: ProvisionKind, + ) -> DispatchResult { + let is_first_token = token_id == Self::first_token_id(); + let is_second_token = token_id == Self::second_token_id(); + let is_public_phase = Phase::::get() == BootstrapPhase::Public; + let is_whitelist_phase = Phase::::get() == BootstrapPhase::Whitelist; + let am_i_whitelisted = Self::is_whitelisted(sender); + + ensure!(is_first_token || is_second_token, Error::::UnsupportedTokenId); + + ensure!( + is_public_phase || (is_whitelist_phase && (am_i_whitelisted || is_second_token)), + Error::::Unauthorized + ); + + let schedule = BootstrapSchedule::::get(); + ensure!(schedule.is_some(), Error::::Unauthorized); + let (_, _, _, (ratio_nominator, ratio_denominator)) = schedule.unwrap(); + + ::Currency::transfer( + token_id.into(), + sender, + &Self::vault_address(), + amount, + ExistenceRequirement::KeepAlive, + ) + .or(Err(Error::::NotEnoughAssets))?; + + // match is_vested { + // ProvisionKind::Regular => { + ensure!( + Provisions::::try_mutate(sender, token_id, |provision| { + if let Some(val) = provision.checked_add(&amount) { + *provision = val; + Ok(()) + } else { + Err(()) + } + }) + .is_ok(), + Error::::MathOverflow + ); + /* + }, + ProvisionKind::Vested(provision_start_block, provision_end_block) => { + ensure!( + VestedProvisions::::try_mutate( + sender, + token_id, + |(provision, start_block, end_block)| { + if let Some(val) = provision.checked_add(amount) { + *provision = val; + *start_block = (*start_block).max(provision_start_block); + *end_block = (*end_block).max(provision_end_block); + Ok(()) + } else { + Err(()) + } + } + ) + .is_ok(), + Error::::MathOverflow + ); + }, + } + */ + let (pre_second_token_valuation, _) = Valuations::::get(); + ensure!( + token_id != Self::first_token_id() || + pre_second_token_valuation != BalanceOf::::zero(), + Error::::FirstProvisionInSecondTokenId + ); + + ensure!( + Valuations::::try_mutate( + |(second_token_valuation, first_token_valuation)| -> Result<(), ()> { + if token_id == Self::second_token_id() { + *second_token_valuation = + second_token_valuation.checked_add(&amount).ok_or(())?; + } + if token_id == Self::first_token_id() { + *first_token_valuation = + first_token_valuation.checked_add(&amount).ok_or(())?; + } + Ok(()) + } + ) + .is_ok(), + Error::::MathOverflow + ); + + if token_id == Self::first_token_id() { + ensure!( + Self::is_ratio_kept(ratio_nominator, ratio_denominator), + Error::::ValuationRatio + ); + } + Ok(()) + } + + fn get_valuation(token_id: &CurrencyIdOf) -> BalanceOf { + if *token_id == Self::first_token_id() { + Self::valuations().1 + } else if *token_id == Self::second_token_id() { + Self::valuations().0 + } else { + BalanceOf::::zero() + } + } + + fn calculate_rewards( + who: &T::AccountId, + token_id: &CurrencyIdOf, + ) -> Result<(BalanceOf, BalanceOf, (BlockNrAsBalance, BlockNrAsBalance)), Error> + { + let valuation = Self::get_valuation(token_id); + let provision = Self::provisions(who, token_id); + let (vested_provision, lock_start, lock_end) = Self::vested_provisions(who, token_id); + let (_, liquidity) = Self::minted_liquidity(); + let rewards = multiply_by_rational_with_rounding( + liquidity.into() / 2, + provision.into(), + valuation.into(), + Rounding::Down, + ) + .ok_or(Error::::MathOverflow)? + .try_into() + .map_err(|_| Error::::MathOverflow)?; + + let vested_rewards = multiply_by_rational_with_rounding( + liquidity.into() / 2, + vested_provision.into(), + valuation.into(), + Rounding::Down, + ) + .ok_or(Error::::MathOverflow)? + .try_into() + .map_err(|_| Error::::MathOverflow)?; + + Ok((rewards, vested_rewards, (lock_start, lock_end))) + } + + fn do_claim_liquidity_tokens(who: &T::AccountId, activate_rewards: bool) -> DispatchResult { + ensure!(Self::phase() == BootstrapPhase::Finished, Error::::NotFinishedYet); + + let (liq_token_id, _) = Self::minted_liquidity(); + + // for backward compatibility + if !Self::archived().is_empty() { + ensure!(ProvisionAccounts::::get(who).is_some(), Error::::NothingToClaim); + } else { + ensure!( + !ClaimedRewards::::contains_key(&who, &Self::first_token_id()), + Error::::NothingToClaim + ); + ensure!( + !ClaimedRewards::::contains_key(&who, &Self::second_token_id()), + Error::::NothingToClaim + ); + } + + let (first_token_rewards, first_token_rewards_vested, first_token_lock) = + Self::calculate_rewards(who, &Self::first_token_id())?; + let (second_token_rewards, second_token_rewards_vested, second_token_lock) = + Self::calculate_rewards(who, &Self::second_token_id())?; + + let total_rewards_claimed = second_token_rewards + .checked_add(&second_token_rewards_vested) + .ok_or(Error::::MathOverflow)? + .checked_add(&first_token_rewards) + .ok_or(Error::::MathOverflow)? + .checked_add(&first_token_rewards_vested) + .ok_or(Error::::MathOverflow)?; + + Self::claim_liquidity_tokens_from_single_currency( + who, + &Self::second_token_id(), + second_token_rewards, + second_token_rewards_vested, + second_token_lock, + )?; + log!( + info, + "Second token rewards (non-vested, vested) = ({:?}, {:?})", + second_token_rewards, + second_token_rewards_vested, + ); + + Self::claim_liquidity_tokens_from_single_currency( + who, + &Self::first_token_id(), + first_token_rewards, + first_token_rewards_vested, + first_token_lock, + )?; + log!( + info, + "First token rewards (non-vested, vested) = ({:?}, {:?})", + first_token_rewards, + first_token_rewards_vested, + ); + + ProvisionAccounts::::remove(who); + + if activate_rewards && ::RewardsApi::is_enabled(liq_token_id) { + let non_vested_rewards = second_token_rewards + .checked_add(&first_token_rewards) + .ok_or(Error::::MathOverflow)?; + if non_vested_rewards > BalanceOf::::zero() { + let activate_result = ::RewardsApi::activate_liquidity( + who.clone(), + liq_token_id, + non_vested_rewards, + Some(ActivateKind::AvailableBalance), + ); + if let Err(err) = activate_result { + log!( + error, + "Activating liquidity tokens failed upon bootstrap claim rewards = ({:?}, {:?}, {:?}, {:?})", + who, + liq_token_id, + non_vested_rewards, + err + ); + + Self::deposit_event(Event::RewardsLiquidityAcitvationFailed( + who.clone(), + liq_token_id, + non_vested_rewards, + )); + }; + } + } + + Self::deposit_event(Event::RewardsClaimed(liq_token_id, total_rewards_claimed)); + + Ok(()) + } + + fn first_token_id() -> CurrencyIdOf { + ActivePair::::get().map(|(first, _)| first).unwrap_or(0_u32.into()) + } + + fn second_token_id() -> CurrencyIdOf { + ActivePair::::get().map(|(_, second)| second).unwrap_or(0_u32.into()) + } +} + +impl Contains<(CurrencyIdOf, CurrencyIdOf)> for Pallet { + fn contains(pair: &(CurrencyIdOf, CurrencyIdOf)) -> bool { + if BootstrapSchedule::::get().is_some() { + pair == &(Self::first_token_id(), Self::second_token_id()) || + pair == &(Self::second_token_id(), Self::first_token_id()) + } else { + false + } + } +} diff --git a/gasp-node/pallets/bootstrap/src/mock.rs b/gasp-node/pallets/bootstrap/src/mock.rs new file mode 100644 index 000000000..99c7c72df --- /dev/null +++ b/gasp-node/pallets/bootstrap/src/mock.rs @@ -0,0 +1,478 @@ +// This file is part of Acala. + +// Copyright (C) 2020-2021 Acala Foundation. +// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 + +// This program 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. + +// This program 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 this program. If not, see . + +use super::*; +use crate as pallet_bootstrap; +use frame_support::{ + construct_runtime, derive_impl, parameter_types, + traits::{ + tokens::currency::MultiTokenCurrency, ConstU128, ConstU32, Contains, Everything, Nothing, + WithdrawReasons, + }, +}; +use mangata_support::{ + pools::{PoolInfo, Valuate, ValuateFor}, + traits::ActivationReservesProviderTrait, +}; +use mangata_types::multipurpose_liquidity::ActivateKind; +use orml_tokens::MultiTokenCurrencyAdapter; +use orml_traits::parameter_type_with_key; +use pallet_xyk::AssetMetadataMutationTrait; +use sp_runtime::{BuildStorage, Perbill, Percent}; +use sp_std::convert::TryFrom; +use std::sync::Mutex; + +pub(crate) type AccountId = u64; +pub(crate) type Balance = u128; +pub(crate) type TokenId = u32; +pub(crate) type Amount = i128; + +#[derive_impl(frame_system::config_preludes::TestDefaultConfig as frame_system::DefaultConfig)] +impl frame_system::Config for Test { + type Block = Block; +} + +parameter_types!( + pub const MGAId: TokenId = 0; + pub const KSMId: TokenId = 1; + pub const MaxLocks: u32 = 50; +); + +pub struct DustRemovalWhitelist; +impl Contains for DustRemovalWhitelist { + fn contains(_a: &AccountId) -> bool { + true + } +} + +parameter_type_with_key! { + pub ExistentialDeposits: |_currency_id: TokenId| -> Balance { + 0 + }; +} + +impl orml_tokens::Config for Test { + type RuntimeEvent = RuntimeEvent; + type Balance = Balance; + type Amount = Amount; + type CurrencyId = TokenId; + type WeightInfo = (); + type ExistentialDeposits = ExistentialDeposits; + type MaxLocks = MaxLocks; + type DustRemovalWhitelist = DustRemovalWhitelist; + type MaxReserves = (); + type ReserveIdentifier = [u8; 8]; + type CurrencyHooks = (); + type NontransferableTokens = Nothing; + type NontransferableTokensAllowList = Nothing; +} + +parameter_types! { + pub const NativeCurrencyId: u32 = 0; + pub const TreasuryPalletId: PalletId = PalletId(*b"py/trsry"); + pub const BnbTreasurySubAccDerive: [u8; 4] = *b"bnbt"; + pub const LiquidityMiningIssuanceVaultId: PalletId = PalletId(*b"py/lqmiv"); + pub FakeLiquidityMiningIssuanceVault: AccountId = LiquidityMiningIssuanceVaultId::get().into_account_truncating(); +} + +parameter_types! { + pub const MinVestedTransfer: Balance = 0; + pub UnvestedFundsAllowedWithdrawReasons: WithdrawReasons = + WithdrawReasons::except(WithdrawReasons::TRANSFER | WithdrawReasons::RESERVE); +} + +impl pallet_vesting_mangata::Config for Test { + type RuntimeEvent = RuntimeEvent; + type Tokens = MultiTokenCurrencyAdapter; + type BlockNumberToBalance = sp_runtime::traits::ConvertInto; + type MinVestedTransfer = MinVestedTransfer; + type WeightInfo = pallet_vesting_mangata::weights::SubstrateWeight; + type UnvestedFundsAllowedWithdrawReasons = UnvestedFundsAllowedWithdrawReasons; + type BlockNumberProvider = System; + // `VestingInfo` encode length is 36bytes. 28 schedules gets encoded as 1009 bytes, which is the + // highest number of schedules that encodes less than 2^10. + const MAX_VESTING_SCHEDULES: u32 = 28; +} + +impl pallet_xyk::XykBenchmarkingConfig for Test {} + +pub struct AssetMetadataMutation; +impl AssetMetadataMutationTrait for AssetMetadataMutation { + fn set_asset_info( + _asset: TokenId, + _name: Vec, + _symbol: Vec, + _decimals: u32, + ) -> DispatchResult { + Ok(()) + } +} + +impl pallet_xyk::Config for Test { + type RuntimeEvent = RuntimeEvent; + type MaintenanceStatusProvider = MockMaintenanceStatusProvider; + type ActivationReservesProvider = TokensActivationPassthrough; + type Currency = MultiTokenCurrencyAdapter; + type NativeCurrencyId = NativeCurrencyId; + type TreasuryPalletId = TreasuryPalletId; + type BnbTreasurySubAccDerive = BnbTreasurySubAccDerive; + type LiquidityMiningRewards = ProofOfStake; + type PoolFeePercentage = ConstU128<20>; + type TreasuryFeePercentage = ConstU128<5>; + type BuyAndBurnFeePercentage = ConstU128<5>; + type WeightInfo = (); + type DisallowedPools = Bootstrap; + type DisabledTokens = Nothing; + type VestingProvider = Vesting; + type AssetMetadataMutation = AssetMetadataMutation; + type FeeLockWeight = (); +} + +mockall::mock! { + pub ValuationApi {} + + impl Valuate for ValuationApi { + type CurrencyId = TokenId; + type Balance = Balance; + + fn find_paired_pool(base_id: TokenId, asset_id: TokenId) -> Result, DispatchError>; + + fn check_can_valuate(base_id: TokenId, pool_id: TokenId) -> Result<(), DispatchError>; + + fn check_pool_exist(pool_id: TokenId) -> Result<(), DispatchError>; + + fn get_reserve_and_lp_supply(base_id: TokenId, pool_id: TokenId) -> Option<(Balance, Balance)>; + + fn get_valuation_for_paired(base_id: TokenId, pool_id: TokenId, amount: Balance) -> Balance; + + fn find_valuation(base_id: TokenId, asset_id: TokenId, amount: Balance) -> Result; + } +} +impl ValuateFor for MockValuationApi {} + +impl pallet_proof_of_stake::Config for Test { + type RuntimeEvent = RuntimeEvent; + type ActivationReservesProvider = TokensActivationPassthrough; + type NativeCurrencyId = NativeCurrencyId; + type Currency = MultiTokenCurrencyAdapter; + type LiquidityMiningIssuanceVault = FakeLiquidityMiningIssuanceVault; + type RewardsDistributionPeriod = ConstU32<10000>; + type WeightInfo = (); + type RewardsSchedulesLimit = ConstU32<10>; + type Min3rdPartyRewardValutationPerSession = ConstU128<10>; + type Min3rdPartyRewardVolume = ConstU128<10>; + type ValuationApi = MockValuationApi; + type SchedulesPerBlock = ConstU32<5>; + type NontransferableTokens = Nothing; +} + +impl BootstrapBenchmarkingConfig for Test {} + +pub struct TokensActivationPassthrough(PhantomData); +impl ActivationReservesProviderTrait + for TokensActivationPassthrough +where + T: frame_system::Config, + T::Currency: MultiTokenCurrency, +{ + fn get_max_instant_unreserve_amount(token_id: TokenId, account_id: &AccountId) -> Balance { + ProofOfStake::get_rewards_info(account_id.clone(), token_id).activated_amount + } + + fn can_activate( + token_id: TokenId, + account_id: &AccountId, + amount: Balance, + _use_balance_from: Option, + ) -> bool { + ::Currency::can_reserve(token_id.into(), account_id, amount) + } + + fn activate( + token_id: TokenId, + account_id: &AccountId, + amount: Balance, + _use_balance_from: Option, + ) -> DispatchResult { + ::Currency::reserve(token_id.into(), account_id, amount) + } + + fn deactivate(token_id: TokenId, account_id: &AccountId, amount: Balance) -> Balance { + ::Currency::unreserve(token_id.into(), account_id, amount) + } +} + +parameter_types! { + pub LiquidityMiningIssuanceVault: AccountId = LiquidityMiningIssuanceVaultId::get().into_account_truncating(); + pub const StakingIssuanceVaultId: PalletId = PalletId(*b"py/stkiv"); + pub StakingIssuanceVault: AccountId = StakingIssuanceVaultId::get().into_account_truncating(); + pub const SequencerIssuanceVaultId: PalletId = PalletId(*b"py/seqiv"); + pub SequencerIssuanceVault: AccountId = SequencerIssuanceVaultId::get().into_account_truncating(); + pub const MgaTokenId: TokenId = 0u32; + + + pub const TotalCrowdloanAllocation: Balance = 200_000_000; + pub const LinearIssuanceAmount: Balance = 4_000_000_000; + pub const LinearIssuanceBlocks: u32 = 22_222u32; + pub const LiquidityMiningSplit: Perbill = Perbill::from_parts(555555556); + pub const StakingSplit: Perbill = Perbill::from_parts(344444444); + pub const SequencerSplit: Perbill = Perbill::from_parts(100000000); + pub const ImmediateTGEReleasePercent: Percent = Percent::from_percent(20); + pub const TGEReleasePeriod: u32 = 100u32; // 2 years + pub const TGEReleaseBegin: u32 = 10u32; // Two weeks into chain start + pub const BlocksPerRound: u32 = 5u32; + pub const HistoryLimit: u32 = 10u32; +} + +impl pallet_issuance::Config for Test { + type RuntimeEvent = RuntimeEvent; + type NativeCurrencyId = MgaTokenId; + type Tokens = orml_tokens::MultiTokenCurrencyAdapter; + type BlocksPerRound = BlocksPerRound; + type HistoryLimit = HistoryLimit; + type LiquidityMiningIssuanceVault = LiquidityMiningIssuanceVault; + type StakingIssuanceVault = StakingIssuanceVault; + type TotalCrowdloanAllocation = TotalCrowdloanAllocation; + type LinearIssuanceAmount = LinearIssuanceAmount; + type LinearIssuanceBlocks = LinearIssuanceBlocks; + type LiquidityMiningSplit = LiquidityMiningSplit; + type StakingSplit = StakingSplit; + type ImmediateTGEReleasePercent = ImmediateTGEReleasePercent; + type TGEReleasePeriod = TGEReleasePeriod; + type TGEReleaseBegin = TGEReleaseBegin; + type VestingProvider = Vesting; + type WeightInfo = (); + type LiquidityMiningApi = ProofOfStake; + type SequencersIssuanceVault = SequencerIssuanceVault; + type SequencersSplit = SequencerSplit; +} + +mockall::mock! { + pub PoolCreateApi {} + + impl PoolCreateApi for PoolCreateApi { + fn pool_exists(first: TokenId, second: TokenId) -> bool; + fn pool_create(account: u64, first: TokenId, first_amount: Balance, second: TokenId, second_amount: Balance) -> Option<(TokenId, Balance)>; + } +} + +mockall::mock! { + pub RewardsApi {} + + impl ProofOfStakeRewardsApi for RewardsApi { + + fn enable(liquidity_token_id: TokenId, weight: u8); + + fn disable(liquidity_token_id: TokenId); + + fn is_enabled( + liquidity_token_id: TokenId, + ) -> bool; + + fn claim_rewards_all( + sender: AccountId, + liquidity_token_id: TokenId, + ) -> Result; + + // Activation & deactivation should happen in PoS + fn activate_liquidity( + sender: AccountId, + liquidity_token_id: TokenId, + amount: Balance, + use_balance_from: Option, + ) -> DispatchResult; + + // Activation & deactivation should happen in PoS + fn deactivate_liquidity( + sender: AccountId, + liquidity_token_id: TokenId, + amount: Balance, + ) -> DispatchResult; + + fn calculate_rewards_amount( + user: AccountId, + liquidity_asset_id: TokenId, + ) -> Result; + + fn rewards_period() -> u32; + } +} + +mockall::mock! { + pub AssetRegistryApi {} + + impl AssetRegistryApi for AssetRegistryApi { + fn enable_pool_creation(assets: (TokenId, TokenId)) -> bool; + } +} + +pub struct AssetRegistry; +impl AssetRegistryApi for AssetRegistry { + fn enable_pool_creation(_assets: (TokenId, TokenId)) -> bool { + true + } +} + +pub struct MockMaintenanceStatusProvider; + +lazy_static::lazy_static! { + static ref MAINTENANCE_STATUS: Mutex = { + let m: bool = false; + Mutex::new(m) + }; +} + +#[cfg(test)] +impl MockMaintenanceStatusProvider { + pub fn instance() -> &'static Mutex { + &MAINTENANCE_STATUS + } +} + +impl MockMaintenanceStatusProvider { + pub fn set_maintenance(value: bool) { + let mut mutex = Self::instance().lock().unwrap(); + *mutex = value; + } +} + +impl GetMaintenanceStatusTrait for MockMaintenanceStatusProvider { + fn is_maintenance() -> bool { + let mutex = Self::instance().lock().unwrap(); + *mutex + } + + fn is_upgradable() -> bool { + unimplemented!() + } +} + +parameter_types! { + pub const BootstrapUpdateBuffer: BlockNumberFor = 10; + pub const DefaultBootstrapPromotedPoolWeight: u8 = 1u8; + pub const ClearStorageLimit: u32 = 10u32; +} + +#[cfg(not(feature = "runtime-benchmarks"))] +impl pallet_bootstrap::Config for Test { + type RuntimeEvent = RuntimeEvent; + type MaintenanceStatusProvider = MockMaintenanceStatusProvider; + type PoolCreateApi = MockPoolCreateApi; + type DefaultBootstrapPromotedPoolWeight = DefaultBootstrapPromotedPoolWeight; + type BootstrapUpdateBuffer = BootstrapUpdateBuffer; + type TreasuryPalletId = TreasuryPalletId; + type Currency = orml_tokens::MultiTokenCurrencyAdapter; + type VestingProvider = Vesting; + type RewardsApi = MockRewardsApi; + type ClearStorageLimit = ClearStorageLimit; + type WeightInfo = (); + type AssetRegistryApi = MockAssetRegistryApi; +} + +#[cfg(feature = "runtime-benchmarks")] +impl pallet_bootstrap::Config for Test { + type RuntimeEvent = RuntimeEvent; + type MaintenanceStatusProvider = MockMaintenanceStatusProvider; + type PoolCreateApi = Xyk; + type DefaultBootstrapPromotedPoolWeight = DefaultBootstrapPromotedPoolWeight; + type BootstrapUpdateBuffer = BootstrapUpdateBuffer; + type TreasuryPalletId = TreasuryPalletId; + type Currency = orml_tokens::MultiTokenCurrencyAdapter; + type VestingProvider = Vesting; + type RewardsApi = ProofOfStake; + type ClearStorageLimit = ClearStorageLimit; + type WeightInfo = (); + type AssetRegistryApi = AssetRegistry; +} + +parameter_types! { + pub const MinLengthName: usize = 1; + pub const MaxLengthName: usize = 255; + pub const MinLengthSymbol: usize = 1; + pub const MaxLengthSymbol: usize = 255; + pub const MinLengthDescription: usize = 1; + pub const MaxLengthDescription: usize = 255; + pub const MaxDecimals: u32 = 255; +} + +type Block = frame_system::mocking::MockBlock; + +construct_runtime!( + pub enum Test { + System: frame_system, + Tokens: orml_tokens, + Xyk: pallet_xyk, + Bootstrap: pallet_bootstrap, + Vesting: pallet_vesting_mangata, + ProofOfStake: pallet_proof_of_stake, + Issuance: pallet_issuance, + } +); + +impl Pallet +where + T::Currency: MultiTokenCurrencyExtended, +{ + pub fn balance(id: TokenId, who: AccountId) -> Balance { + Tokens::accounts(who.clone(), id).free - Tokens::accounts(who, id).frozen + } + + pub fn reserved_balance(id: TokenId, who: AccountId) -> Balance { + Tokens::accounts(who, id).reserved + } + + pub fn locked_balance(id: TokenId, who: AccountId) -> Balance { + Tokens::accounts(who, id).frozen + } + + pub fn total_supply(id: TokenId) -> Balance { + ::Currency::total_issuance(id.into()).into() + } + pub fn transfer( + currency_id: TokenId, + source: AccountId, + dest: AccountId, + value: Balance, + ) -> DispatchResult { + ::Currency::transfer( + currency_id, + &source, + &dest, + value, + frame_support::traits::ExistenceRequirement::KeepAlive, + ) + } + pub fn create_new_token(who: &AccountId, amount: Balance) -> TokenId { + ::Currency::create(who, amount).expect("Token creation failed") + } +} + +// This function basically just builds a genesis storage key/value store according to +// our desired mockup. +pub fn new_test_ext() -> sp_io::TestExternalities { + let t = frame_system::GenesisConfig::::default() + .build_storage() + .expect("Frame system builds valid default genesis config"); + + let mut ext = sp_io::TestExternalities::new(t); + ext.execute_with(|| { + System::set_block_number(1); + MockMaintenanceStatusProvider::set_maintenance(false); + }); + ext +} diff --git a/gasp-node/pallets/bootstrap/src/tests.rs b/gasp-node/pallets/bootstrap/src/tests.rs new file mode 100644 index 000000000..56e395b74 --- /dev/null +++ b/gasp-node/pallets/bootstrap/src/tests.rs @@ -0,0 +1,2444 @@ +#![cfg(not(feature = "runtime-benchmarks"))] +use super::*; +use mock::*; + +use frame_support::{assert_err, assert_ok}; +use serial_test::serial; +use sp_runtime::traits::BadOrigin; +use test_case::test_case; + +const USER_ID: u64 = 0; +const PROVISION_USER1_ID: u64 = 200; +const PROVISION_USER2_ID: u64 = 201; +const ANOTHER_USER_ID: u64 = 100; +const YET_ANOTHER_USER_ID: u64 = 900; +const INITIAL_AMOUNT: u128 = 1_000_000; +const DUMMY_ID: u32 = 2; +const LIQ_TOKEN_ID: TokenId = 10_u32; +const LIQ_TOKEN_AMOUNT: Balance = 1_000_000_u128; +const DEFAULT_RATIO: (u128, u128) = (1_u128, 10_000_u128); +const POOL_CREATE_DUMMY_RETURN_VALUE: Option<(TokenId, Balance)> = + Some((LIQ_TOKEN_ID, LIQ_TOKEN_AMOUNT)); + +fn set_up() { + // for backwards compatibility + ArchivedBootstrap::::mutate(|v| { + v.push(Default::default()); + }); + let mga_id = Bootstrap::create_new_token(&USER_ID, INITIAL_AMOUNT); + let ksm_id = Bootstrap::create_new_token(&USER_ID, INITIAL_AMOUNT); + let dummy_id = Bootstrap::create_new_token(&USER_ID, INITIAL_AMOUNT); + assert_eq!(mga_id, MGAId::get()); + assert_eq!(ksm_id, KSMId::get()); + assert_eq!(dummy_id, DUMMY_ID); + assert_eq!(INITIAL_AMOUNT, Bootstrap::balance(KSMId::get(), USER_ID)); + assert_eq!(INITIAL_AMOUNT, Bootstrap::balance(MGAId::get(), USER_ID)); +} + +fn jump_to_whitelist_phase() { + let pool_exists_mock = MockPoolCreateApi::pool_exists_context(); + pool_exists_mock.expect().return_const(false); + Bootstrap::schedule_bootstrap( + RuntimeOrigin::root(), + KSMId::get(), + MGAId::get(), + 10_u32.into(), + Some(10), + 10, + Some(DEFAULT_RATIO), + false, + ) + .unwrap(); + Bootstrap::on_initialize(15_u32.into()); + assert_eq!(BootstrapPhase::Whitelist, Phase::::get()); +} + +fn jump_to_public_phase() { + let pool_exists_mock = MockPoolCreateApi::pool_exists_context(); + pool_exists_mock.expect().return_const(false); + + Bootstrap::schedule_bootstrap( + RuntimeOrigin::root(), + KSMId::get(), + MGAId::get(), + 10_u32.into(), + Some(10), + 10, + Some(DEFAULT_RATIO), + false, + ) + .unwrap(); + Bootstrap::on_initialize(25_u32.into()); + assert_eq!(BootstrapPhase::Public, Phase::::get()); +} + +#[test] +#[serial] +fn do_not_allow_for_provision_in_unsupported_currency() { + new_test_ext().execute_with(|| { + set_up(); + assert_err!( + Bootstrap::provision(RuntimeOrigin::signed(USER_ID), DUMMY_ID, 1000), + Error::::UnsupportedTokenId + ) + }); +} + +#[test] +#[serial] +fn test_first_provision_with_ksm_fails() { + new_test_ext().execute_with(|| { + set_up(); + jump_to_public_phase(); + + assert_err!( + Bootstrap::provision(RuntimeOrigin::signed(USER_ID), KSMId::get(), 1), + Error::::FirstProvisionInSecondTokenId + ); + }); +} + +#[test] +#[serial] +fn test_event_is_published_after_successful_provision() { + new_test_ext().execute_with(|| { + set_up(); + jump_to_public_phase(); + Bootstrap::provision(RuntimeOrigin::signed(USER_ID), MGAId::get(), 1).unwrap(); + + let event = crate::mock::RuntimeEvent::Bootstrap(crate::Event::::Provisioned( + MGAId::get(), + 1, + )); + + assert!(System::events().iter().any(|record| record.event == event)); + }); +} + +#[test] +#[serial] +fn test_dont_allow_for_ksm_donation_before_minimal_valuation_fro_mga_is_provided() { + new_test_ext().execute_with(|| { + set_up(); + + jump_to_public_phase(); + + Bootstrap::provision(RuntimeOrigin::signed(USER_ID), MGAId::get(), 1).unwrap(); + assert_eq!(1, Bootstrap::provisions(USER_ID, MGAId::get())); + assert_eq!((1, 0), Bootstrap::valuations()); + + assert_err!( + Bootstrap::provision(RuntimeOrigin::signed(USER_ID), KSMId::get(), 1), + Error::::ValuationRatio + ); + + Bootstrap::provision(RuntimeOrigin::signed(USER_ID), MGAId::get(), 9999).unwrap(); + assert_eq!(10000, Bootstrap::provisions(USER_ID, MGAId::get())); + assert_eq!((10000, 0), Bootstrap::valuations()); + + Bootstrap::provision(RuntimeOrigin::signed(USER_ID), KSMId::get(), 1).unwrap(); + assert_eq!(1, Bootstrap::provisions(USER_ID, KSMId::get())); + assert_eq!((10000, 1), Bootstrap::valuations()); + + assert_err!( + Bootstrap::provision(RuntimeOrigin::signed(USER_ID), KSMId::get(), 1), + Error::::ValuationRatio + ); + }); +} + +#[test] +#[serial] +fn test_donation_in_supported_tokens() { + new_test_ext().execute_with(|| { + set_up(); + jump_to_public_phase(); + + Bootstrap::provision(RuntimeOrigin::signed(USER_ID), MGAId::get(), 10000).unwrap(); + assert_eq!(10000, Bootstrap::provisions(USER_ID, MGAId::get())); + + Bootstrap::provision(RuntimeOrigin::signed(USER_ID), KSMId::get(), 1).unwrap(); + assert_eq!(1, Bootstrap::provisions(USER_ID, KSMId::get())); + + Bootstrap::provision(RuntimeOrigin::signed(USER_ID), MGAId::get(), 20000).unwrap(); + assert_eq!(30000, Bootstrap::provisions(USER_ID, MGAId::get())); + + Bootstrap::provision(RuntimeOrigin::signed(USER_ID), KSMId::get(), 2).unwrap(); + assert_eq!(3, Bootstrap::provisions(USER_ID, KSMId::get())); + }); +} + +#[test] +#[serial] +fn test_donation_with_more_tokens_than_available() { + new_test_ext().execute_with(|| { + set_up(); + + jump_to_public_phase(); + + assert_err!( + Bootstrap::provision(RuntimeOrigin::signed(USER_ID), KSMId::get(), INITIAL_AMOUNT * 2), + Error::::NotEnoughAssets + ); + + assert_err!( + Bootstrap::provision(RuntimeOrigin::signed(USER_ID), MGAId::get(), INITIAL_AMOUNT * 2), + Error::::NotEnoughAssets + ); + }); +} + +#[test] +#[serial] +fn test_prevent_provisions_in_before_start_phase() { + new_test_ext().execute_with(|| { + set_up(); + + let pool_exists_mock = MockPoolCreateApi::pool_exists_context(); + pool_exists_mock.expect().return_const(false); + + Bootstrap::schedule_bootstrap( + RuntimeOrigin::root(), + KSMId::get(), + MGAId::get(), + 100_u32.into(), + Some(10), + 20, + Some(DEFAULT_RATIO), + false, + ) + .unwrap(); + + assert_err!( + Bootstrap::provision(RuntimeOrigin::signed(USER_ID), KSMId::get(), INITIAL_AMOUNT * 2), + Error::::Unauthorized + ); + }); +} + +#[test] +#[serial] +fn test_fail_scheudle_bootstrap_with_same_token() { + new_test_ext().execute_with(|| { + set_up(); + + let pool_exists_mock = MockPoolCreateApi::pool_exists_context(); + pool_exists_mock.expect().return_const(false); + + assert_err!( + Bootstrap::schedule_bootstrap( + RuntimeOrigin::root(), + 100, + 100, + 100_u32.into(), + Some(10), + 20, + Some(DEFAULT_RATIO), + false, + ), + Error::::SameToken + ); + }); +} + +#[test] +#[serial] +fn test_prevent_schedule_bootstrap_with_pair_that_does_not_exists() { + new_test_ext().execute_with(|| { + set_up(); + + let pool_exists_mock = MockPoolCreateApi::pool_exists_context(); + pool_exists_mock.expect().return_const(false); + + assert_err!( + Bootstrap::schedule_bootstrap( + RuntimeOrigin::root(), + 100, + 101, + 100_u32.into(), + Some(10), + 20, + Some(DEFAULT_RATIO), + false, + ), + Error::::TokenIdDoesNotExists + ); + }); +} + +#[test] +#[serial] +fn test_prevent_provisions_in_finished_phase() { + new_test_ext().execute_with(|| { + set_up(); + + let pool_exists_mock = MockPoolCreateApi::pool_exists_context(); + pool_exists_mock.expect().return_const(false); + + Bootstrap::schedule_bootstrap( + RuntimeOrigin::root(), + KSMId::get(), + MGAId::get(), + 100_u32.into(), + Some(10), + 20, + Some(DEFAULT_RATIO), + false, + ) + .unwrap(); + + Phase::::put(BootstrapPhase::Finished); + + assert_err!( + Bootstrap::provision(RuntimeOrigin::signed(USER_ID), KSMId::get(), INITIAL_AMOUNT * 2), + Error::::Unauthorized + ); + }); +} + +#[test] +#[serial] +fn test_prevent_non_whitelited_account_to_provision_in_whitelisted_phase() { + new_test_ext().execute_with(|| { + set_up(); + + jump_to_whitelist_phase(); + + assert!(!Bootstrap::is_whitelisted(&USER_ID)); + assert_err!( + Bootstrap::provision(RuntimeOrigin::signed(USER_ID), KSMId::get(), 1000), + Error::::Unauthorized + ); + }); +} + +#[test] +#[serial] +fn test_allow_non_whitelited_account_to_provision_in_whitelisted_phase_with_mga() { + new_test_ext().execute_with(|| { + set_up(); + + jump_to_whitelist_phase(); + + assert!(!Bootstrap::is_whitelisted(&USER_ID)); + Bootstrap::provision(RuntimeOrigin::signed(USER_ID), MGAId::get(), 1000).unwrap(); + }); +} + +#[test] +#[serial] +fn test_whitelist_account_deposit_event() { + new_test_ext().execute_with(|| { + set_up(); + Bootstrap::whitelist_accounts(RuntimeOrigin::root(), vec![USER_ID]).unwrap(); + + assert!(System::events().iter().any(|record| record.event == + crate::mock::RuntimeEvent::Bootstrap(crate::Event::::AccountsWhitelisted))); + }); +} + +#[test] +#[serial] +fn test_incremental_whitliested_donation() { + new_test_ext().execute_with(|| { + set_up(); + + jump_to_whitelist_phase(); + + Bootstrap::whitelist_accounts(RuntimeOrigin::root(), vec![USER_ID]).unwrap(); + Bootstrap::provision(RuntimeOrigin::signed(USER_ID), MGAId::get(), 1000).unwrap(); + + Bootstrap::transfer(MGAId::get(), USER_ID, ANOTHER_USER_ID, 10_000).unwrap(); + Bootstrap::whitelist_accounts(RuntimeOrigin::root(), vec![ANOTHER_USER_ID]).unwrap(); + + Bootstrap::provision(RuntimeOrigin::signed(USER_ID), MGAId::get(), 1000).unwrap(); + Bootstrap::provision(RuntimeOrigin::signed(ANOTHER_USER_ID), MGAId::get(), 1000).unwrap(); + assert_ne!(USER_ID, ANOTHER_USER_ID); + }); +} + +#[test] +#[serial] +fn test_bootstrap_promotion_can_be_updated() { + new_test_ext().execute_with(|| { + let enable_pool_creation_mock = MockAssetRegistryApi::enable_pool_creation_context(); + enable_pool_creation_mock.expect().return_const(true); + + let pool_exists_mock = MockPoolCreateApi::pool_exists_context(); + pool_exists_mock.expect().return_const(false); + + let pool_create_mock = MockPoolCreateApi::pool_create_context(); + pool_create_mock.expect().times(1).return_const(POOL_CREATE_DUMMY_RETURN_VALUE); + + let mock = MockRewardsApi::enable_context(); + mock.expect().times(1).return_const(()); + + set_up(); + assert_ok!(Bootstrap::schedule_bootstrap( + RuntimeOrigin::root(), + KSMId::get(), + MGAId::get(), + 100_u32.into(), + Some(1), + 9, + Some(DEFAULT_RATIO), + false, + )); + + System::set_block_number(109); + Bootstrap::on_initialize(109_u32.into()); + + assert_ok!(Bootstrap::update_promote_bootstrap_pool(RuntimeOrigin::root(), true)); + + System::set_block_number(111); + Bootstrap::on_initialize(111_u32.into()); + + assert_err!( + Bootstrap::update_promote_bootstrap_pool(RuntimeOrigin::root(), false), + Error::::BootstrapFinished + ); + }); +} + +#[test] +#[serial] +fn test_scheduled_bootstrap_can_be_updated() { + new_test_ext().execute_with(|| { + let pool_exists_mock = MockPoolCreateApi::pool_exists_context(); + pool_exists_mock.expect().return_const(false); + + set_up(); + assert_ok!(Bootstrap::schedule_bootstrap( + RuntimeOrigin::root(), + KSMId::get(), + MGAId::get(), + 100_u32.into(), + Some(1), + 9, + Some(DEFAULT_RATIO), + false, + )); + + System::set_block_number(80); + Bootstrap::on_initialize(80_u32.into()); + + assert_ok!(Bootstrap::schedule_bootstrap( + RuntimeOrigin::root(), + KSMId::get(), + MGAId::get(), + 100_u32.into(), + Some(1), + 9, + Some((100, 10)), + false, + )); + + System::set_block_number(95); + Bootstrap::on_initialize(95_u32.into()); + + assert_err!( + Bootstrap::schedule_bootstrap( + RuntimeOrigin::root(), + KSMId::get(), + MGAId::get(), + 100_u32.into(), + Some(1), + 9, + Some((1000, 1)), + false, + ), + Error::::TooLateToUpdateBootstrap + ); + }); +} + +#[test] +#[serial] +fn test_scheduled_bootstrap_can_be_cancelled() { + new_test_ext().execute_with(|| { + let pool_exists_mock = MockPoolCreateApi::pool_exists_context(); + pool_exists_mock.expect().return_const(false); + + set_up(); + assert_ok!(Bootstrap::schedule_bootstrap( + RuntimeOrigin::root(), + KSMId::get(), + MGAId::get(), + 100_u32.into(), + Some(1), + 9, + Some(DEFAULT_RATIO), + false, + )); + + System::set_block_number(95); + Bootstrap::on_initialize(95_u32.into()); + + assert_err!( + Bootstrap::cancel_bootstrap(RuntimeOrigin::root()), + Error::::TooLateToUpdateBootstrap + ); + + System::set_block_number(80); + Bootstrap::on_initialize(80_u32.into()); + + assert_ok!(Bootstrap::cancel_bootstrap(RuntimeOrigin::root())); + }); +} + +#[test] +#[serial] +fn test_non_root_user_can_not_schedule_bootstrap() { + new_test_ext().execute_with(|| { + set_up(); + assert_err!( + Bootstrap::schedule_bootstrap( + RuntimeOrigin::signed(USER_ID), + KSMId::get(), + MGAId::get(), + 0_u32.into(), + Some(1), + 1, + Some(DEFAULT_RATIO), + false, + ), + BadOrigin + ); + }); +} + +#[test] +#[serial] +fn test_non_root_user_can_not_whitelist_accounts() { + new_test_ext().execute_with(|| { + set_up(); + assert_err!( + Bootstrap::whitelist_accounts(RuntimeOrigin::signed(USER_ID), vec![],), + BadOrigin + ); + }); +} + +#[test] +#[serial] +fn test_only_root_can_whitelist_accounts() { + new_test_ext().execute_with(|| { + set_up(); + Bootstrap::whitelist_accounts(RuntimeOrigin::root(), vec![]).unwrap(); + }); +} + +#[test] +#[serial] +fn test_ido_start_cannot_happen_in_the_past() { + new_test_ext().execute_with(|| { + set_up(); + System::set_block_number(1000); + assert_err!( + Bootstrap::schedule_bootstrap( + RuntimeOrigin::root(), + KSMId::get(), + MGAId::get(), + 999_u32.into(), + Some(1), + 1, + Some(DEFAULT_RATIO), + false, + ), + Error::::BootstrapStartInThePast + ); + }); +} + +#[test] +#[serial] +fn test_ido_start_can_not_be_initialize_with_0_ratio() { + new_test_ext().execute_with(|| { + set_up(); + System::set_block_number(10); + assert_err!( + Bootstrap::schedule_bootstrap( + RuntimeOrigin::root(), + KSMId::get(), + MGAId::get(), + 999_u32.into(), + Some(1), + 1, + Some((1, 0)), + false, + ), + Error::::WrongRatio + ); + assert_err!( + Bootstrap::schedule_bootstrap( + RuntimeOrigin::root(), + KSMId::get(), + MGAId::get(), + 999_u32.into(), + Some(1), + 1, + Some((0, 1)), + false, + ), + Error::::WrongRatio + ); + }); +} + +#[test] +#[serial] +fn test_can_schedule_bootstrap_with_whitelist_phase_length_equal_zero() { + new_test_ext().execute_with(|| { + set_up(); + + let pool_exists_mock = MockPoolCreateApi::pool_exists_context(); + pool_exists_mock.expect().return_const(false); + + assert_ok!(Bootstrap::schedule_bootstrap( + RuntimeOrigin::root(), + KSMId::get(), + MGAId::get(), + 100_u32.into(), + Some(0), + 1, + Some(DEFAULT_RATIO), + false, + )); + }); +} + +#[test] +#[serial] +fn test_cannot_schedule_bootstrap_with_public_phase_length_equal_zero() { + new_test_ext().execute_with(|| { + set_up(); + assert_err!( + Bootstrap::schedule_bootstrap( + RuntimeOrigin::root(), + KSMId::get(), + MGAId::get(), + 100_u32.into(), + Some(1), + 0, + Some(DEFAULT_RATIO), + false, + ), + Error::::PhaseLengthCannotBeZero + ); + }); +} + +#[test] +#[serial] +fn test_bootstrap_can_be_modified_only_before_its_started() { + new_test_ext().execute_with(|| { + set_up(); + + let pool_exists_mock = MockPoolCreateApi::pool_exists_context(); + pool_exists_mock.expect().return_const(false); + + Bootstrap::schedule_bootstrap( + RuntimeOrigin::root(), + KSMId::get(), + MGAId::get(), + 50_u32.into(), + Some(10), + 20, + Some(DEFAULT_RATIO), + false, + ) + .unwrap(); + + Bootstrap::schedule_bootstrap( + RuntimeOrigin::root(), + KSMId::get(), + MGAId::get(), + 100_u32.into(), + Some(10), + 20, + Some(DEFAULT_RATIO), + false, + ) + .unwrap(); + + Bootstrap::on_initialize(100_u32.into()); + + assert_err!( + Bootstrap::schedule_bootstrap( + RuntimeOrigin::root(), + KSMId::get(), + MGAId::get(), + 100_u32.into(), + Some(10), + 20, + Some(DEFAULT_RATIO), + false, + ), + Error::::AlreadyStarted + ); + + assert_eq!(Some((100_u32.into(), 10_u32, 20_u32, DEFAULT_RATIO)), Bootstrap::config()); + }); +} + +#[test] +#[serial] +fn test_bootstrap_state_transitions() { + new_test_ext().execute_with(|| { + set_up(); + + let enable_pool_creation_mock = MockAssetRegistryApi::enable_pool_creation_context(); + enable_pool_creation_mock.expect().return_const(true); + + const BOOTSTRAP_WHITELIST_START: u64 = 100; + const BOOTSTRAP_PUBLIC_START: u64 = 110; + const BOOTSTRAP_FINISH: u64 = 130; + + let pool_exists_mock = MockPoolCreateApi::pool_exists_context(); + pool_exists_mock.expect().return_const(false); + + let pool_create_mock = MockPoolCreateApi::pool_create_context(); + pool_create_mock.expect().times(1).return_const(POOL_CREATE_DUMMY_RETURN_VALUE); + + Bootstrap::schedule_bootstrap( + RuntimeOrigin::root(), + KSMId::get(), + MGAId::get(), + BOOTSTRAP_WHITELIST_START, + Some((BOOTSTRAP_PUBLIC_START - BOOTSTRAP_WHITELIST_START).try_into().unwrap()), + (BOOTSTRAP_FINISH - BOOTSTRAP_PUBLIC_START).try_into().unwrap(), + Some(DEFAULT_RATIO), + false, + ) + .unwrap(); + + for i in 1..BOOTSTRAP_WHITELIST_START { + Bootstrap::on_initialize(i); + assert_eq!(Bootstrap::phase(), BootstrapPhase::BeforeStart); + } + + Bootstrap::on_initialize(BOOTSTRAP_WHITELIST_START); + assert_eq!(Bootstrap::phase(), BootstrapPhase::Whitelist); + + for i in BOOTSTRAP_WHITELIST_START..BOOTSTRAP_PUBLIC_START { + Bootstrap::on_initialize(i); + assert_eq!(Bootstrap::phase(), BootstrapPhase::Whitelist); + } + + Bootstrap::on_initialize(BOOTSTRAP_PUBLIC_START); + assert_eq!(Bootstrap::phase(), BootstrapPhase::Public); + + for i in BOOTSTRAP_PUBLIC_START..BOOTSTRAP_FINISH { + Bootstrap::on_initialize(i); + assert_eq!(Bootstrap::phase(), BootstrapPhase::Public); + } + + Bootstrap::on_initialize(BOOTSTRAP_FINISH); + assert_eq!(Bootstrap::phase(), BootstrapPhase::Finished); + }); +} + +#[test] +#[serial] +fn test_bootstrap_state_transitions_when_on_initialized_is_not_called() { + new_test_ext().execute_with(|| { + set_up(); + + let enable_pool_creation_mock = MockAssetRegistryApi::enable_pool_creation_context(); + enable_pool_creation_mock.expect().return_const(true); + + let pool_create_mock = MockPoolCreateApi::pool_create_context(); + pool_create_mock.expect().times(1).return_const(POOL_CREATE_DUMMY_RETURN_VALUE); + + const BOOTSTRAP_WHITELIST_START: u64 = 100; + const BOOTSTRAP_PUBLIC_START: u64 = 110; + const BOOTSTRAP_FINISH: u64 = 130; + let pool_exists_mock = MockPoolCreateApi::pool_exists_context(); + pool_exists_mock.expect().return_const(false); + + Bootstrap::schedule_bootstrap( + RuntimeOrigin::root(), + KSMId::get(), + MGAId::get(), + BOOTSTRAP_WHITELIST_START, + Some((BOOTSTRAP_PUBLIC_START - BOOTSTRAP_WHITELIST_START).try_into().unwrap()), + (BOOTSTRAP_FINISH - BOOTSTRAP_PUBLIC_START).try_into().unwrap(), + Some(DEFAULT_RATIO), + false, + ) + .unwrap(); + + assert_eq!(Bootstrap::phase(), BootstrapPhase::BeforeStart); + Bootstrap::on_initialize(200); + assert_eq!(Bootstrap::phase(), BootstrapPhase::Finished); + }); +} + +#[test] +#[serial] +fn test_bootstrap_schedule_overflow() { + new_test_ext().execute_with(|| { + set_up(); + + assert_err!( + Bootstrap::schedule_bootstrap( + RuntimeOrigin::root(), + KSMId::get(), + MGAId::get(), + u64::MAX, + Some(u32::MAX), + 1_u32, + Some(DEFAULT_RATIO), + false, + ), + Error::::MathOverflow + ); + + assert_err!( + Bootstrap::schedule_bootstrap( + RuntimeOrigin::root(), + KSMId::get(), + MGAId::get(), + u64::MAX, + Some(1_u32), + u32::MAX, + Some(DEFAULT_RATIO), + false, + ), + Error::::MathOverflow + ); + + assert_err!( + Bootstrap::schedule_bootstrap( + RuntimeOrigin::root(), + KSMId::get(), + MGAId::get(), + u64::MAX, + Some(u32::MAX), + u32::MAX, + Some(DEFAULT_RATIO), + false, + ), + Error::::MathOverflow + ); + }); +} +#[serial] +fn test_do_not_allow_for_creating_starting_bootstrap_for_existing_pool() { + new_test_ext().execute_with(|| { + set_up(); + + let pool_exists_mock = MockPoolCreateApi::pool_exists_context(); + pool_exists_mock.expect().return_const(true); + + assert_err!( + Bootstrap::schedule_bootstrap( + RuntimeOrigin::root(), + KSMId::get(), + MGAId::get(), + 100_u32.into(), + Some(10), + 10, + Some(DEFAULT_RATIO), + false, + ), + Error::::PoolAlreadyExists + ); + }); +} + +#[test] +#[serial] +fn test_crate_pool_is_called_with_proper_arguments_after_bootstrap_finish() { + new_test_ext().execute_with(|| { + set_up(); + + use mockall::predicate::eq; + const KSM_PROVISON: Balance = 30; + const MGA_PROVISON: Balance = 500_000; + + let enable_pool_creation_mock = MockAssetRegistryApi::enable_pool_creation_context(); + enable_pool_creation_mock.expect().return_const(true); + + let pool_exists_mock = MockPoolCreateApi::pool_exists_context(); + pool_exists_mock.expect().return_const(false); + + let pool_create_mock = MockPoolCreateApi::pool_create_context(); + pool_create_mock + .expect() + .with( + eq(Bootstrap::vault_address()), + eq(KSMId::get()), + eq(KSM_PROVISON), + eq(MGAId::get()), + eq(MGA_PROVISON), + ) + .times(1) + .return_const(POOL_CREATE_DUMMY_RETURN_VALUE); + + Bootstrap::schedule_bootstrap( + RuntimeOrigin::root(), + KSMId::get(), + MGAId::get(), + 100_u32.into(), + Some(10), + 10, + Some(DEFAULT_RATIO), + false, + ) + .unwrap(); + + Bootstrap::on_initialize(110_u32.into()); + Bootstrap::provision(RuntimeOrigin::signed(USER_ID), MGAId::get(), MGA_PROVISON).unwrap(); + Bootstrap::provision(RuntimeOrigin::signed(USER_ID), KSMId::get(), KSM_PROVISON).unwrap(); + Bootstrap::on_initialize(120_u32.into()); + }); +} + +#[test] +#[serial] +fn test_cannot_claim_liquidity_tokens_when_bootstrap_is_not_finished() { + new_test_ext().execute_with(|| { + set_up(); + + let pool_exists_mock = MockPoolCreateApi::pool_exists_context(); + pool_exists_mock.expect().return_const(false); + + Bootstrap::schedule_bootstrap( + RuntimeOrigin::root(), + KSMId::get(), + MGAId::get(), + 100_u32.into(), + Some(10), + 10, + Some(DEFAULT_RATIO), + false, + ) + .unwrap(); + + Bootstrap::on_initialize(100_u32.into()); + assert_eq!(BootstrapPhase::Whitelist, Phase::::get()); + + Bootstrap::on_initialize(110_u32.into()); + assert_eq!(BootstrapPhase::Public, Phase::::get()); + + assert_err!( + Bootstrap::claim_liquidity_tokens(RuntimeOrigin::signed(USER_ID)), + Error::::NotFinishedYet + ); + }); +} + +#[test] +#[serial] +fn test_rewards_are_distributed_properly_with_single_user() { + new_test_ext().execute_with(|| { + use std::sync::{Arc, Mutex}; + set_up(); + + const KSM_PROVISON: Balance = 10; + const MGA_PROVISON: Balance = 100_000; + let liq_token_id: Arc> = Arc::new(Mutex::new(0_u32)); + let ref_liq_token_id = liq_token_id.clone(); + + let enable_pool_creation_mock = MockAssetRegistryApi::enable_pool_creation_context(); + enable_pool_creation_mock.expect().return_const(true); + + let pool_exists_mock = MockPoolCreateApi::pool_exists_context(); + pool_exists_mock.expect().return_const(false); + + let pool_create_mock = MockPoolCreateApi::pool_create_context(); + pool_create_mock + .expect() + .times(1) + .returning(move |addr, _, ksm_amount, _, mga_amount| { + let issuance = (ksm_amount + mga_amount) / 2; + let id = Bootstrap::create_new_token(&addr, issuance); + *(ref_liq_token_id.lock().unwrap()) = id; + Some((id, issuance)) + }); + + Bootstrap::schedule_bootstrap( + RuntimeOrigin::root(), + KSMId::get(), + MGAId::get(), + 100_u32.into(), + Some(10), + 10, + Some(DEFAULT_RATIO), + false, + ) + .unwrap(); + + Bootstrap::on_initialize(100_u32.into()); + assert_eq!(BootstrapPhase::Whitelist, Phase::::get()); + + Bootstrap::on_initialize(110_u32.into()); + assert_eq!(BootstrapPhase::Public, Phase::::get()); + + Bootstrap::provision(RuntimeOrigin::signed(USER_ID), MGAId::get(), MGA_PROVISON).unwrap(); + Bootstrap::provision(RuntimeOrigin::signed(USER_ID), KSMId::get(), KSM_PROVISON).unwrap(); + + Bootstrap::on_initialize(120_u32.into()); + assert_eq!(BootstrapPhase::Finished, Phase::::get()); + + let (mga_valuation, ksm_valuation) = Bootstrap::valuations(); + let liquidity_token_id = *(liq_token_id.lock().unwrap()); + let liquidity_token_amount = (mga_valuation + ksm_valuation) / 2; + + assert_eq!( + Bootstrap::balance(liquidity_token_id, Bootstrap::vault_address()), + liquidity_token_amount + ); + assert_eq!(Bootstrap::minted_liquidity(), (liquidity_token_id, liquidity_token_amount)); + + Bootstrap::claim_liquidity_tokens(RuntimeOrigin::signed(USER_ID)).unwrap(); + + assert_eq!(Bootstrap::claimed_rewards(USER_ID, MGAId::get()), liquidity_token_amount / 2); + + assert_eq!(Bootstrap::claimed_rewards(USER_ID, KSMId::get()), liquidity_token_amount / 2); + + assert_eq!( + Bootstrap::balance(liquidity_token_id, USER_ID), + // KSM rewards MGA rewards + (liquidity_token_amount / 2) + (liquidity_token_amount / 2) + ); + }); +} + +#[test] +#[serial] +fn test_rewards_are_distributed_properly_with_multiple_user() { + new_test_ext().execute_with(|| { + use std::sync::{Arc, Mutex}; + set_up(); + + let provisioned_ev = |id, amount| { + crate::mock::RuntimeEvent::Bootstrap(crate::Event::::Provisioned(id, amount)) + }; + + let rewards_claimed_ev = |id, amount| { + crate::mock::RuntimeEvent::Bootstrap(crate::Event::::RewardsClaimed(id, amount)) + }; + + const USER_KSM_PROVISON: Balance = 15; + const USER_MGA_PROVISON: Balance = 400_000; + const ANOTHER_USER_KSM_PROVISON: Balance = 20; + const ANOTHER_USER_MGA_PROVISON: Balance = 100_000; + let liq_token_id: Arc> = Arc::new(Mutex::new(0_u32)); + let ref_liq_token_id = liq_token_id.clone(); + + let enable_pool_creation_mock = MockAssetRegistryApi::enable_pool_creation_context(); + enable_pool_creation_mock.expect().return_const(true); + + let pool_exists_mock = MockPoolCreateApi::pool_exists_context(); + pool_exists_mock.expect().return_const(false); + + let pool_create_mock = MockPoolCreateApi::pool_create_context(); + pool_create_mock + .expect() + .times(1) + .returning(move |addr, _, ksm_amount, _, mga_amount| { + let issuance = (ksm_amount + mga_amount) / 2; + let id = Bootstrap::create_new_token(&addr, issuance); + *(ref_liq_token_id.lock().unwrap()) = id; + Some((id, issuance)) + }); + + Bootstrap::schedule_bootstrap( + RuntimeOrigin::root(), + KSMId::get(), + MGAId::get(), + 100_u32.into(), + Some(10), + 10, + Some(DEFAULT_RATIO), + false, + ) + .unwrap(); + + Bootstrap::on_initialize(100_u32.into()); + assert_eq!(BootstrapPhase::Whitelist, Phase::::get()); + + Bootstrap::on_initialize(110_u32.into()); + assert_eq!(BootstrapPhase::Public, Phase::::get()); + + Bootstrap::transfer(MGAId::get(), USER_ID, ANOTHER_USER_ID, 500_000).unwrap(); + Bootstrap::transfer(KSMId::get(), USER_ID, ANOTHER_USER_ID, 500_000).unwrap(); + + Bootstrap::provision(RuntimeOrigin::signed(USER_ID), MGAId::get(), USER_MGA_PROVISON) + .unwrap(); + Bootstrap::provision( + RuntimeOrigin::signed(ANOTHER_USER_ID), + MGAId::get(), + ANOTHER_USER_MGA_PROVISON, + ) + .unwrap(); + + Bootstrap::provision(RuntimeOrigin::signed(USER_ID), KSMId::get(), USER_KSM_PROVISON) + .unwrap(); + Bootstrap::provision( + RuntimeOrigin::signed(ANOTHER_USER_ID), + KSMId::get(), + ANOTHER_USER_KSM_PROVISON, + ) + .unwrap(); + + assert!(System::events() + .iter() + .any(|record| record.event == provisioned_ev(MGAId::get(), USER_MGA_PROVISON))); + assert!(System::events() + .iter() + .any(|record| record.event == provisioned_ev(KSMId::get(), USER_KSM_PROVISON))); + assert!(System::events() + .iter() + .any(|record| record.event == provisioned_ev(MGAId::get(), ANOTHER_USER_MGA_PROVISON))); + assert!(System::events() + .iter() + .any(|record| record.event == provisioned_ev(KSMId::get(), ANOTHER_USER_KSM_PROVISON))); + + Bootstrap::on_initialize(120_u32.into()); + assert_eq!(BootstrapPhase::Finished, Phase::::get()); + + let (mga_valuation, ksm_valuation) = Bootstrap::valuations(); + assert_eq!(mga_valuation, 500_000); + assert_eq!(ksm_valuation, 35); + let liquidity_token_id = *(liq_token_id.lock().unwrap()); + let liquidity_token_amount = (mga_valuation + ksm_valuation) / 2; + + assert_eq!( + Bootstrap::balance(liquidity_token_id, Bootstrap::vault_address()), + liquidity_token_amount + ); + assert_eq!(Bootstrap::minted_liquidity(), (liquidity_token_id, liquidity_token_amount)); + + assert_eq!(Bootstrap::claimed_rewards(ANOTHER_USER_ID, MGAId::get()), 0); + assert_eq!(Bootstrap::claimed_rewards(ANOTHER_USER_ID, KSMId::get()), 0); + assert_eq!(Bootstrap::claimed_rewards(USER_ID, MGAId::get()), 0); + assert_eq!(Bootstrap::claimed_rewards(USER_ID, KSMId::get()), 0); + + let user_expected_ksm_rewards = + liquidity_token_amount / 2 * USER_KSM_PROVISON / ksm_valuation; + let user_expected_mga_rewards = + liquidity_token_amount / 2 * USER_MGA_PROVISON / mga_valuation; + let user_expected_liq_amount = user_expected_ksm_rewards + user_expected_mga_rewards; + + let user2_expected_ksm_rewards = + liquidity_token_amount / 2 * ANOTHER_USER_KSM_PROVISON / ksm_valuation; + let user2_expected_mga_rewards = + liquidity_token_amount / 2 * ANOTHER_USER_MGA_PROVISON / mga_valuation; + let user2_expected_liq_amount = user2_expected_ksm_rewards + user2_expected_mga_rewards; + + Bootstrap::claim_liquidity_tokens(RuntimeOrigin::signed(USER_ID)).unwrap(); + Bootstrap::claim_liquidity_tokens(RuntimeOrigin::signed(ANOTHER_USER_ID)).unwrap(); + + assert_eq!(Bootstrap::claimed_rewards(USER_ID, MGAId::get()), user_expected_mga_rewards); + assert_eq!(Bootstrap::claimed_rewards(USER_ID, KSMId::get()), user_expected_ksm_rewards); + assert_eq!( + Bootstrap::claimed_rewards(ANOTHER_USER_ID, MGAId::get()), + user2_expected_mga_rewards + ); + assert_eq!( + Bootstrap::claimed_rewards(ANOTHER_USER_ID, KSMId::get()), + user2_expected_ksm_rewards + ); + + assert_err!( + Bootstrap::claim_liquidity_tokens(RuntimeOrigin::signed(USER_ID)), + Error::::NothingToClaim + ); + assert_err!( + Bootstrap::claim_liquidity_tokens(RuntimeOrigin::signed(ANOTHER_USER_ID)), + Error::::NothingToClaim + ); + + assert_eq!(Bootstrap::balance(liquidity_token_id, USER_ID), user_expected_liq_amount); + assert_eq!( + Bootstrap::balance(liquidity_token_id, ANOTHER_USER_ID), + user2_expected_liq_amount + ); + + assert!(System::events().iter().any(|record| record.event == + rewards_claimed_ev(liquidity_token_id, user_expected_liq_amount))); + assert!(System::events().iter().any(|record| record.event == + rewards_claimed_ev(liquidity_token_id, user2_expected_liq_amount))); + }); +} + +#[test] +#[serial] +fn dont_allow_for_provision_in_vested_tokens_without_dedicated_extrinsic() { + new_test_ext().execute_with(|| { + set_up(); + jump_to_public_phase(); + + let mga_amount = Bootstrap::balance(MGAId::get(), USER_ID); + ::VestingProvider::lock_tokens( + &USER_ID, + MGAId::get(), + mga_amount, + None, + 100_u32.into(), + ) + .unwrap(); + + assert_err!( + Bootstrap::provision(RuntimeOrigin::signed(USER_ID), MGAId::get(), 1), + Error::::NotEnoughAssets + ); + }); +} + +// #[test] +// #[serial] +// fn fail_vested_token_provision_if_user_doesnt_have_vested_tokens() { +// new_test_ext().execute_with(|| { +// set_up(); +// jump_to_public_phase(); + +// assert_err!( +// Bootstrap::provision_vested(RuntimeOrigin::signed(USER_ID), MGAId::get(), 1), +// Error::::NotEnoughVestedAssets +// ); +// }); +// } + +// #[test] +// #[serial] +// fn successful_vested_provision_using_vested_tokens_only_when_user_has_both_vested_and_non_vested_tokens( +// ) { +// new_test_ext().execute_with(|| { +// set_up(); +// jump_to_public_phase(); + +// let provision_amount = 10_000; +// let lock_start: u128 = 1; +// let lock_end: u128 = 150; + +// ::VestingProvider::lock_tokens( +// &USER_ID, +// MGAId::get(), +// provision_amount, +// Some(lock_start.saturated_into()), +// lock_end.into(), +// ) +// .unwrap(); + +// let non_vested_initial_amount = Bootstrap::balance(MGAId::get(), USER_ID); + +// Bootstrap::provision_vested(RuntimeOrigin::signed(USER_ID), MGAId::get(), provision_amount) +// .unwrap(); + +// assert_eq!(non_vested_initial_amount, Bootstrap::balance(MGAId::get(), USER_ID)); + +// assert_eq!(0, Bootstrap::locked_balance(MGAId::get(), USER_ID)); + +// assert_eq!( +// (provision_amount, lock_start, lock_end + 1), +// Bootstrap::vested_provisions(USER_ID, MGAId::get()) +// ); +// }); +// } + +// #[test] +// #[serial] +// fn successful_vested_provision_is_stored_properly_in_storage() { +// new_test_ext().execute_with(|| { +// set_up(); +// jump_to_public_phase(); + +// let mga_amount = Bootstrap::balance(MGAId::get(), USER_ID); +// let lock_start: u128 = 1; +// let lock_end: u128 = 150; + +// ::VestingProvider::lock_tokens( +// &USER_ID, +// MGAId::get(), +// mga_amount, +// Some(lock_start.saturated_into()), +// lock_end.into(), +// ) +// .unwrap(); +// Bootstrap::provision_vested(RuntimeOrigin::signed(USER_ID), MGAId::get(), mga_amount).unwrap(); + +// assert_eq!( +// (mga_amount, lock_start, lock_end + 1), +// Bootstrap::vested_provisions(USER_ID, MGAId::get()) +// ); +// }); +// } + +// #[test] +// #[serial] +// fn successful_merged_vested_provision_is_stored_properly_in_storage() { +// new_test_ext().execute_with(|| { +// set_up(); +// jump_to_public_phase(); + +// let mga_amount = Bootstrap::balance(MGAId::get(), USER_ID); +// let first_lock_start: u128 = 1; +// let first_lock_end: u128 = 150; +// let first_lock_amount = mga_amount / 2; +// let second_lock_start: u128 = 2; +// let second_lock_end: u128 = 300; +// let second_lock_amount = mga_amount - first_lock_amount; + +// ::VestingProvider::lock_tokens( +// &USER_ID, +// MGAId::get(), +// first_lock_amount, +// Some(first_lock_start.saturated_into()), +// first_lock_end.into(), +// ) +// .unwrap(); +// ::VestingProvider::lock_tokens( +// &USER_ID, +// MGAId::get(), +// second_lock_amount, +// Some(second_lock_start.saturated_into()), +// second_lock_end.into(), +// ) +// .unwrap(); + +// Bootstrap::provision_vested(RuntimeOrigin::signed(USER_ID), MGAId::get(), first_lock_amount) +// .unwrap(); +// assert_eq!( +// (first_lock_amount, first_lock_start, first_lock_end + 1), +// Bootstrap::vested_provisions(USER_ID, MGAId::get()) +// ); + +// Bootstrap::provision_vested(RuntimeOrigin::signed(USER_ID), MGAId::get(), second_lock_amount) +// .unwrap(); +// assert_eq!( +// (mga_amount, second_lock_start, second_lock_end + 1), +// Bootstrap::vested_provisions(USER_ID, MGAId::get()) +// ); +// }); +// } + +#[macro_export] +macro_rules! init_mocks { + () => { + let pool_exists_mock = MockPoolCreateApi::pool_exists_context(); + pool_exists_mock.expect().return_const(false); + + let pool_create_mock = MockPoolCreateApi::pool_create_context(); + pool_create_mock + .expect() + .times(1) + .returning(move |addr, _, ksm_amount, _, mga_amount| { + let issuance = (ksm_amount + mga_amount) / 2; + let id = Bootstrap::create_new_token(&addr, issuance); + Some((id, issuance)) + }); + }; +} + +fn provisions(provisions: Vec<(::AccountId, TokenId, Balance)>) { + for (user_id, token_id, amount) in provisions { + ::Currency::transfer( + token_id, + &USER_ID, + &user_id, + amount, + frame_support::traits::ExistenceRequirement::KeepAlive, + ) + .unwrap(); + + // if let ProvisionKind::Vested(begin, end) = lock { + // ::VestingProvider::lock_tokens( + // &user_id, + // token_id, + // amount, + // Some(begin.saturated_into()), + // end.into(), + // ) + // .unwrap(); + // Bootstrap::provision_vested(RuntimeOrigin::signed(user_id), token_id, amount).unwrap(); + // } else { + Bootstrap::provision(RuntimeOrigin::signed(user_id), token_id, amount).unwrap(); + // } + } +} + +// #[test] +// #[serial] +// fn vested_provision_included_in_valuation() { +// new_test_ext().execute_with(|| { +// // ARRANGE - USER provides vested MGA tokens, ANOTHER_USER provides KSM tokens +// set_up(); +// jump_to_public_phase(); +// init_mocks!(); +// let liq_token_id = Tokens::next_asset_id(); + +// // ACT +// provisions(vec![ +// (PROVISION_USER1_ID, MGAId::get(), 1_000_000, ProvisionKind::Vested(1, 150)), +// (PROVISION_USER2_ID, KSMId::get(), 100, ProvisionKind::Regular), +// ]); +// let (mga_valuation, ksm_valuation) = Bootstrap::valuations(); +// let liq_token_minted = (mga_valuation + ksm_valuation) / 2; +// assert_eq!(mga_valuation, 1_000_000); +// assert_eq!(ksm_valuation, 100); + +// Bootstrap::on_initialize(100_u32.into()); +// assert_eq!(BootstrapPhase::Finished, Phase::::get()); +// Bootstrap::claim_liquidity_tokens(RuntimeOrigin::signed(PROVISION_USER1_ID)).unwrap(); +// Bootstrap::claim_liquidity_tokens(RuntimeOrigin::signed(PROVISION_USER2_ID)).unwrap(); + +// // ASSERT +// assert_eq!( +// liq_token_minted / 2, +// Bootstrap::locked_balance(liq_token_id, PROVISION_USER1_ID) +// ); +// assert_eq!(0, Bootstrap::balance(liq_token_id, PROVISION_USER1_ID)); + +// assert_eq!(liq_token_minted / 2, Bootstrap::balance(liq_token_id, PROVISION_USER2_ID)); +// assert_eq!(0, Bootstrap::locked_balance(liq_token_id, PROVISION_USER2_ID)); +// }); +// } + +// #[test] +// #[serial] +// fn multi_provisions() { +// new_test_ext().execute_with(|| { +// // ARRANGE - USER provides vested MGA tokens, ANOTHER_USER provides KSM tokens +// set_up(); +// jump_to_public_phase(); +// init_mocks!(); +// let liq_token_id = Tokens::next_asset_id(); + +// // ACT +// provisions(vec![ +// (PROVISION_USER1_ID, MGAId::get(), 100_000, ProvisionKind::Vested(1, 150)), +// (PROVISION_USER1_ID, KSMId::get(), 10, ProvisionKind::Regular), +// (PROVISION_USER2_ID, MGAId::get(), 300_000, ProvisionKind::Vested(1, 150)), +// (PROVISION_USER2_ID, KSMId::get(), 30, ProvisionKind::Regular), +// ]); +// let (mga_valuation, ksm_valuation) = Bootstrap::valuations(); +// let liq_token_minted = (mga_valuation + ksm_valuation) / 2; +// assert_eq!(mga_valuation, 400_000); +// assert_eq!(ksm_valuation, 40); + +// Bootstrap::on_initialize(100_u32.into()); +// assert_eq!(BootstrapPhase::Finished, Phase::::get()); +// Bootstrap::claim_liquidity_tokens(RuntimeOrigin::signed(PROVISION_USER1_ID)).unwrap(); +// Bootstrap::claim_liquidity_tokens(RuntimeOrigin::signed(PROVISION_USER2_ID)).unwrap(); + +// // ASSERT +// assert_eq!( +// liq_token_minted / 2 * 10 / ksm_valuation, +// Bootstrap::locked_balance(liq_token_id, PROVISION_USER1_ID) +// ); +// assert_eq!( +// liq_token_minted / 2 * 100_000 / mga_valuation, +// Bootstrap::balance(liq_token_id, PROVISION_USER1_ID) +// ); + +// assert_eq!( +// liq_token_minted / 2 * 30 / ksm_valuation, +// Bootstrap::locked_balance(liq_token_id, PROVISION_USER2_ID) +// ); +// assert_eq!( +// liq_token_minted / 2 * 300_000 / mga_valuation, +// Bootstrap::balance(liq_token_id, PROVISION_USER2_ID) +// ); +// }); +// } + +#[test] +#[serial] +fn multi_provisions_only_with_non_vested() { + new_test_ext().execute_with(|| { + // ARRANGE - USER provides vested MGA tokens, ANOTHER_USER provides KSM tokens + set_up(); + jump_to_public_phase(); + init_mocks!(); + let liq_token_id = Tokens::next_asset_id(); + + let enable_pool_creation_mock = MockAssetRegistryApi::enable_pool_creation_context(); + enable_pool_creation_mock.expect().return_const(true); + + // ACT + provisions(vec![ + (PROVISION_USER1_ID, MGAId::get(), 100_000), + (PROVISION_USER1_ID, KSMId::get(), 10), + (PROVISION_USER2_ID, MGAId::get(), 300_000), + (PROVISION_USER2_ID, KSMId::get(), 30), + ]); + let (mga_valuation, ksm_valuation) = Bootstrap::valuations(); + let liq_token_minted = (mga_valuation + ksm_valuation) / 2; + assert_eq!(mga_valuation, 400_000); + assert_eq!(ksm_valuation, 40); + + Bootstrap::on_initialize(100_u32.into()); + assert_eq!(BootstrapPhase::Finished, Phase::::get()); + Bootstrap::claim_liquidity_tokens(RuntimeOrigin::signed(PROVISION_USER1_ID)).unwrap(); + Bootstrap::claim_liquidity_tokens(RuntimeOrigin::signed(PROVISION_USER2_ID)).unwrap(); + + // ASSERT + assert_eq!( + liq_token_minted / 2 * 100_000 / mga_valuation * 2, + Bootstrap::balance(liq_token_id, PROVISION_USER1_ID) + ); + + assert_eq!( + liq_token_minted / 2 * 300_000 / mga_valuation * 2, + Bootstrap::balance(liq_token_id, PROVISION_USER2_ID) + ); + }); +} + +// // formula KSM/MGA for provision calculation +// // (KSM valuation + MGA valuation) User KSM/MGA token provision +// // ------------------------------- * ---------------------------- = liquidity tokens rewards +// // 2 KSM/MGA valuation +// // EX: + +// #[test_case( +// vec![ +// (PROVISION_USER1_ID, MGAId::get(), 100_000, ProvisionKind::Vested(1, 150)), +// (PROVISION_USER1_ID, KSMId::get(), 10, ProvisionKind::Regular), +// (PROVISION_USER2_ID, MGAId::get(), 300_000, ProvisionKind::Vested(1, 150)), +// (PROVISION_USER2_ID, KSMId::get(), 30, ProvisionKind::Regular), +// ], +// (25002, 25002), +// (75007, 75007); +// "two users provision both vested and non vested tokens") +// ] +// #[test_case( +// vec![ +// (PROVISION_USER1_ID, MGAId::get(), 100_000, ProvisionKind::Regular), +// (PROVISION_USER1_ID, KSMId::get(), 10, ProvisionKind::Regular), +// ], +// (50_004, 0), +// (0, 0); +// "non vested provisions from single user") +// ] +// #[test_case( +// vec![ +// (PROVISION_USER1_ID, MGAId::get(), 100_000, ProvisionKind::Vested(1, 150)), +// (PROVISION_USER1_ID, KSMId::get(), 10, ProvisionKind::Vested(1, 150)), +// ], +// (0, 50_004), +// (0, 0); +// "vested provisions from single user") +// ] +// #[test_case( +// vec![ +// (PROVISION_USER1_ID, MGAId::get(), 100_000, ProvisionKind::Vested(1, 150)), +// (PROVISION_USER1_ID, KSMId::get(), 10, ProvisionKind::Vested(1, 150)), +// (PROVISION_USER2_ID, MGAId::get(), 300_000, ProvisionKind::Regular), +// (PROVISION_USER2_ID, KSMId::get(), 30, ProvisionKind::Regular), +// ], +// (0, 50_004), // 400040 / 2 / 2 * 1 / 4 +// (150014, 0); // 400040 / 2 / 2 * 3 / 4 +// "vested provisions from single user & non vested form second one") +// ] +// #[test_case( +// vec![ +// (PROVISION_USER1_ID, MGAId::get(), 10_000, ProvisionKind::Vested(1, 150)), +// (PROVISION_USER2_ID, KSMId::get(), 1, ProvisionKind::Regular), +// (PROVISION_USER1_ID, MGAId::get(), 20_000, ProvisionKind::Regular), +// (PROVISION_USER2_ID, KSMId::get(), 1, ProvisionKind::Regular), +// (PROVISION_USER2_ID, KSMId::get(), 1, ProvisionKind::Regular), +// (PROVISION_USER1_ID, MGAId::get(), 30_000, ProvisionKind::Vested(1, 150)), +// (PROVISION_USER1_ID, KSMId::get(), 1, ProvisionKind::Vested(1, 150)), +// (PROVISION_USER2_ID, KSMId::get(), 1, ProvisionKind::Regular), +// (PROVISION_USER2_ID, KSMId::get(), 1, ProvisionKind::Regular), +// (PROVISION_USER1_ID, MGAId::get(), 40_000, ProvisionKind::Vested(1, 150)), +// (PROVISION_USER2_ID, MGAId::get(), 200_000, ProvisionKind::Regular), +// (PROVISION_USER2_ID, MGAId::get(), 100_000, ProvisionKind::Vested(1, 150)), +// (PROVISION_USER2_ID, KSMId::get(), 10, ProvisionKind::Regular), +// (PROVISION_USER2_ID, KSMId::get(), 15, ProvisionKind::Regular), +// (PROVISION_USER1_ID, KSMId::get(), 4, ProvisionKind::Vested(1, 150)), +// (PROVISION_USER1_ID, KSMId::get(), 5, ProvisionKind::Vested(1, 150)), +// ], +// (5_000, 45_004), +// (125012, 25_002); +// "multiple provisions from multiple accounts mixed") +// ] +// #[serial] +// fn test_multi_provisions( +// provisions_list: Vec<( +// ::AccountId, +// TokenId, +// Balance, +// ProvisionKind, +// )>, +// user1_rewards: (Balance, Balance), +// user2_rewards: (Balance, Balance), +// ) { +// new_test_ext().execute_with(|| { +// // ARRANGE - USER provides vested MGA tokens, ANOTHER_USER provides KSM tokens +// set_up(); +// jump_to_public_phase(); +// init_mocks!(); +// let liq_token_id = Tokens::next_asset_id(); +// let user1_has_provisions = +// provisions_list.iter().any(|(who, _, _, _)| *who == PROVISION_USER1_ID); +// let user2_has_provisions = +// provisions_list.iter().any(|(who, _, _, _)| *who == PROVISION_USER2_ID); +// let total_ksm_provision: u128 = provisions_list +// .iter() +// .filter_map( +// |(_, token_id, amount, _)| { +// if *token_id == KSMId::get() { +// Some(amount) +// } else { +// None +// } +// }, +// ) +// .sum(); +// let total_mga_provision: u128 = provisions_list +// .iter() +// .filter_map( +// |(_, token_id, amount, _)| { +// if *token_id == MGAId::get() { +// Some(amount) +// } else { +// None +// } +// }, +// ) +// .sum(); + +// // ACT +// provisions(provisions_list); + +// Bootstrap::on_initialize(100_u32.into()); +// assert_eq!(BootstrapPhase::Finished, Phase::::get()); + +// if user1_has_provisions { +// Bootstrap::claim_liquidity_tokens(RuntimeOrigin::signed(PROVISION_USER1_ID)).unwrap(); +// } + +// if user2_has_provisions { +// Bootstrap::claim_liquidity_tokens(RuntimeOrigin::signed(PROVISION_USER2_ID)).unwrap(); +// } + +// // ASSERT +// let (mga_valuation, ksm_valuation) = Bootstrap::valuations(); +// assert_eq!(total_ksm_provision, ksm_valuation); +// assert_eq!(total_mga_provision, mga_valuation); + +// assert_eq!(user1_rewards.0, Bootstrap::balance(liq_token_id, PROVISION_USER1_ID)); +// assert_eq!(user1_rewards.1, Bootstrap::locked_balance(liq_token_id, PROVISION_USER1_ID)); + +// assert_eq!(user2_rewards.0, Bootstrap::balance(liq_token_id, PROVISION_USER2_ID)); +// assert_eq!(user2_rewards.1, Bootstrap::locked_balance(liq_token_id, PROVISION_USER2_ID)); +// }) +// } + +// formula KSM/MGA for provision calculation +// (KSM valuation + MGA valuation) User KSM/MGA token provision +// ------------------------------- * ---------------------------- = liquidity tokens rewards +// 2 KSM/MGA valuation +// EX: + +#[test_case( + vec![ + (PROVISION_USER1_ID, MGAId::get(), 100_000), + (PROVISION_USER1_ID, KSMId::get(), 10), + (PROVISION_USER2_ID, MGAId::get(), 300_000), + (PROVISION_USER2_ID, KSMId::get(), 30), + ], + (50_004, 0), + (150014, 0); + "two users provision both vested and non vested tokens") +] +#[test_case( + vec![ + (PROVISION_USER1_ID, MGAId::get(), 100_000), + (PROVISION_USER1_ID, KSMId::get(), 10), + ], + (50_004, 0), + (0, 0); + "non vested provisions from single user") +] +#[test_case( + vec![ + (PROVISION_USER1_ID, MGAId::get(), 100_000), + (PROVISION_USER1_ID, KSMId::get(), 10), + ], + (50_004, 0), + (0, 0); + "vested provisions from single user") +] +#[test_case( + vec![ + (PROVISION_USER1_ID, MGAId::get(), 100_000), + (PROVISION_USER1_ID, KSMId::get(), 10), + (PROVISION_USER2_ID, MGAId::get(), 300_000), + (PROVISION_USER2_ID, KSMId::get(), 30), + ], + (50_004, 0), // 400040 / 2 / 2 * 1 / 4 + (150014, 0); // 400040 / 2 / 2 * 3 / 4 + "vested provisions from single user & non vested form second one") +] +#[test_case( + vec![ + (PROVISION_USER1_ID, MGAId::get(), 10_000), + (PROVISION_USER2_ID, KSMId::get(), 1), + (PROVISION_USER1_ID, MGAId::get(), 20_000), + (PROVISION_USER2_ID, KSMId::get(), 1), + (PROVISION_USER2_ID, KSMId::get(), 1), + (PROVISION_USER1_ID, MGAId::get(), 30_000), + (PROVISION_USER1_ID, KSMId::get(), 1), + (PROVISION_USER2_ID, KSMId::get(), 1), + (PROVISION_USER2_ID, KSMId::get(), 1), + (PROVISION_USER1_ID, MGAId::get(), 40_000), + (PROVISION_USER2_ID, MGAId::get(), 200_000), + (PROVISION_USER2_ID, MGAId::get(), 100_000), + (PROVISION_USER2_ID, KSMId::get(), 10), + (PROVISION_USER2_ID, KSMId::get(), 15), + (PROVISION_USER1_ID, KSMId::get(), 4), + (PROVISION_USER1_ID, KSMId::get(), 5), + ], + (50004, 0), + (150014, 0); + "multiple provisions from multiple accounts mixed") +] +#[serial] +fn test_multi_provisions_only_with_non_vested( + provisions_list: Vec<(::AccountId, TokenId, Balance)>, + user1_rewards: (Balance, Balance), + user2_rewards: (Balance, Balance), +) { + new_test_ext().execute_with(|| { + // ARRANGE - USER provides vested MGA tokens, ANOTHER_USER provides KSM tokens + set_up(); + jump_to_public_phase(); + init_mocks!(); + let enable_pool_creation_mock = MockAssetRegistryApi::enable_pool_creation_context(); + enable_pool_creation_mock.expect().return_const(true); + + let liq_token_id = Tokens::next_asset_id(); + let user1_has_provisions = + provisions_list.iter().any(|(who, _, _)| *who == PROVISION_USER1_ID); + let user2_has_provisions = + provisions_list.iter().any(|(who, _, _)| *who == PROVISION_USER2_ID); + let total_ksm_provision: u128 = provisions_list + .iter() + .filter_map( + |(_, token_id, amount)| { + if *token_id == KSMId::get() { + Some(amount) + } else { + None + } + }, + ) + .sum(); + let total_mga_provision: u128 = provisions_list + .iter() + .filter_map( + |(_, token_id, amount)| { + if *token_id == MGAId::get() { + Some(amount) + } else { + None + } + }, + ) + .sum(); + + // ACT + provisions(provisions_list); + + Bootstrap::on_initialize(100_u32.into()); + assert_eq!(BootstrapPhase::Finished, Phase::::get()); + + if user1_has_provisions { + Bootstrap::claim_liquidity_tokens(RuntimeOrigin::signed(PROVISION_USER1_ID)).unwrap(); + } + + if user2_has_provisions { + Bootstrap::claim_liquidity_tokens(RuntimeOrigin::signed(PROVISION_USER2_ID)).unwrap(); + } + + // ASSERT + let (mga_valuation, ksm_valuation) = Bootstrap::valuations(); + assert_eq!(total_ksm_provision, ksm_valuation); + assert_eq!(total_mga_provision, mga_valuation); + + assert_eq!(user1_rewards.0, Bootstrap::balance(liq_token_id, PROVISION_USER1_ID)); + + assert_eq!(user2_rewards.0, Bootstrap::balance(liq_token_id, PROVISION_USER2_ID)); + }) +} + +#[test] +#[serial] +fn test_restart_bootstrap() { + new_test_ext().execute_with(|| { + set_up(); + + const USER_KSM_PROVISON: Balance = 15; + const USER_MGA_PROVISON: Balance = 400_000; + const ANOTHER_USER_KSM_PROVISON: Balance = 20; + const ANOTHER_USER_MGA_PROVISON: Balance = 100_000; + let liq_token_id = Tokens::next_asset_id(); + + let enable_pool_creation_mock = MockAssetRegistryApi::enable_pool_creation_context(); + enable_pool_creation_mock.expect().return_const(true); + + let pool_exists_mock = MockPoolCreateApi::pool_exists_context(); + pool_exists_mock.expect().return_const(false); + + let pool_create_mock = MockPoolCreateApi::pool_create_context(); + pool_create_mock + .expect() + .times(1) + .returning(move |addr, _, ksm_amount, _, mga_amount| { + let issuance = (ksm_amount + mga_amount) / 2; + let id = Bootstrap::create_new_token(&addr, issuance); + assert_eq!(id, liq_token_id); + Some((id, issuance)) + }); + + Bootstrap::schedule_bootstrap( + RuntimeOrigin::root(), + KSMId::get(), + MGAId::get(), + 100_u32.into(), + Some(10), + 10, + Some(DEFAULT_RATIO), + false, + ) + .unwrap(); + Bootstrap::on_initialize(110_u32.into()); + Bootstrap::transfer(MGAId::get(), USER_ID, ANOTHER_USER_ID, 500_000).unwrap(); + Bootstrap::transfer(KSMId::get(), USER_ID, ANOTHER_USER_ID, 500_000).unwrap(); + Bootstrap::provision(RuntimeOrigin::signed(USER_ID), MGAId::get(), USER_MGA_PROVISON) + .unwrap(); + Bootstrap::provision( + RuntimeOrigin::signed(ANOTHER_USER_ID), + MGAId::get(), + ANOTHER_USER_MGA_PROVISON, + ) + .unwrap(); + + Bootstrap::provision(RuntimeOrigin::signed(USER_ID), KSMId::get(), USER_KSM_PROVISON) + .unwrap(); + Bootstrap::provision( + RuntimeOrigin::signed(ANOTHER_USER_ID), + KSMId::get(), + ANOTHER_USER_KSM_PROVISON, + ) + .unwrap(); + + assert_err!( + Bootstrap::pre_finalize(RuntimeOrigin::signed(YET_ANOTHER_USER_ID)), + Error::::NotFinishedYet + ); + + Bootstrap::on_initialize(120_u32.into()); + + assert_eq!(0, Bootstrap::balance(liq_token_id, USER_ID)); + assert_eq!(0, Bootstrap::balance(liq_token_id, ANOTHER_USER_ID)); + + Bootstrap::claim_liquidity_tokens(RuntimeOrigin::signed(USER_ID)).unwrap(); + + // not all rewards claimed + assert_err!( + Bootstrap::pre_finalize(RuntimeOrigin::signed(YET_ANOTHER_USER_ID)), + Error::::BootstrapNotReadyToBeFinished + ); + + Bootstrap::claim_liquidity_tokens_for_account( + RuntimeOrigin::signed(USER_ID), + ANOTHER_USER_ID, + false, + ) + .unwrap(); + + assert_ne!(0, Bootstrap::balance(liq_token_id, USER_ID)); + assert_ne!(0, Bootstrap::balance(liq_token_id, ANOTHER_USER_ID)); + + Bootstrap::pre_finalize(RuntimeOrigin::signed(YET_ANOTHER_USER_ID)).unwrap(); + Bootstrap::finalize(RuntimeOrigin::signed(YET_ANOTHER_USER_ID)).unwrap(); + assert!(Provisions::::iter_keys().next().is_none()); + assert!(VestedProvisions::::iter_keys().next().is_none()); + assert!(WhitelistedAccount::::iter_keys().next().is_none()); + assert!(ClaimedRewards::::iter_keys().next().is_none()); + assert!(ProvisionAccounts::::iter_keys().next().is_none()); + assert_eq!(Valuations::::get(), (0, 0)); + assert_eq!(Phase::::get(), BootstrapPhase::BeforeStart); + assert_eq!(BootstrapSchedule::::get(), None); + assert_eq!(MintedLiquidity::::get(), (0, 0)); + assert_eq!(ActivePair::::get(), None); + + Bootstrap::schedule_bootstrap( + RuntimeOrigin::root(), + KSMId::get(), + MGAId::get(), + 200_u32.into(), + Some(10), + 10, + Some(DEFAULT_RATIO), + false, + ) + .unwrap(); + }); +} + +#[test] +#[serial] +fn claim_liquidity_tokens_even_if_sum_of_rewards_is_zero_because_of_small_provision() { + new_test_ext().execute_with(|| { + let enable_pool_creation_mock = MockAssetRegistryApi::enable_pool_creation_context(); + enable_pool_creation_mock.expect().return_const(true); + + ArchivedBootstrap::::mutate(|v| { + v.push(Default::default()); + }); + + Bootstrap::create_new_token(&USER_ID, u128::MAX); + Bootstrap::create_new_token(&USER_ID, u128::MAX); + let liq_token_id = Tokens::next_asset_id(); + + let pool_exists_mock = MockPoolCreateApi::pool_exists_context(); + pool_exists_mock.expect().return_const(false); + + let pool_create_mock = MockPoolCreateApi::pool_create_context(); + pool_create_mock + .expect() + .times(1) + .returning(move |addr, _, ksm_amount, _, mga_amount| { + let issuance = (ksm_amount + mga_amount) / 2; + let id = Bootstrap::create_new_token(&addr, issuance); + assert_eq!(id, liq_token_id); + Some((id, issuance)) + }); + + Bootstrap::schedule_bootstrap( + RuntimeOrigin::root(), + KSMId::get(), + MGAId::get(), + 100_u32.into(), + Some(10), + 10, + Some(DEFAULT_RATIO), + false, + ) + .unwrap(); + Bootstrap::on_initialize(110_u32.into()); + Bootstrap::transfer(MGAId::get(), USER_ID, ANOTHER_USER_ID, 1).unwrap(); + Bootstrap::transfer(KSMId::get(), USER_ID, ANOTHER_USER_ID, 1).unwrap(); + + Bootstrap::provision( + RuntimeOrigin::signed(USER_ID), + MGAId::get(), + 1_000_000_000_000_000_000_000_000_000_000_000_u128, + ) + .unwrap(); + Bootstrap::provision(RuntimeOrigin::signed(ANOTHER_USER_ID), MGAId::get(), 1).unwrap(); + + Bootstrap::provision(RuntimeOrigin::signed(USER_ID), KSMId::get(), 1_000_000_u128).unwrap(); + + Bootstrap::on_initialize(120_u32.into()); + + assert_eq!(0, Bootstrap::balance(liq_token_id, USER_ID)); + assert_eq!(0, Bootstrap::balance(liq_token_id, ANOTHER_USER_ID)); + + Bootstrap::claim_liquidity_tokens(RuntimeOrigin::signed(USER_ID)).unwrap(); + Bootstrap::claim_liquidity_tokens(RuntimeOrigin::signed(ANOTHER_USER_ID)).unwrap(); + + assert_err!( + Bootstrap::claim_liquidity_tokens(RuntimeOrigin::signed(USER_ID)), + Error::::NothingToClaim + ); + + assert_err!( + Bootstrap::claim_liquidity_tokens(RuntimeOrigin::signed(ANOTHER_USER_ID)), + Error::::NothingToClaim + ); + }); +} + +#[test] +#[serial] +fn transfer_dust_to_treasury() { + new_test_ext().execute_with(|| { + Bootstrap::create_new_token(&USER_ID, u128::MAX); + Bootstrap::create_new_token(&USER_ID, u128::MAX); + let liq_token_id = Tokens::next_asset_id(); + + let enable_pool_creation_mock = MockAssetRegistryApi::enable_pool_creation_context(); + enable_pool_creation_mock.expect().return_const(true); + + let pool_exists_mock = MockPoolCreateApi::pool_exists_context(); + pool_exists_mock.expect().return_const(false); + + let pool_create_mock = MockPoolCreateApi::pool_create_context(); + pool_create_mock + .expect() + .times(1) + .returning(move |addr, _, ksm_amount, _, mga_amount| { + let issuance = (ksm_amount + mga_amount) / 2; + let id = Bootstrap::create_new_token(&addr, issuance); + assert_eq!(id, liq_token_id); + Some((id, issuance)) + }); + + Bootstrap::schedule_bootstrap( + RuntimeOrigin::root(), + KSMId::get(), + MGAId::get(), + 100_u32.into(), + Some(10), + 10, + Some(DEFAULT_RATIO), + false, + ) + .unwrap(); + Bootstrap::on_initialize(110_u32.into()); + Bootstrap::transfer(MGAId::get(), USER_ID, ANOTHER_USER_ID, 1).unwrap(); + Bootstrap::transfer(KSMId::get(), USER_ID, ANOTHER_USER_ID, 1).unwrap(); + + Bootstrap::provision(RuntimeOrigin::signed(USER_ID), MGAId::get(), 1_000_000_000_u128) + .unwrap(); + Bootstrap::provision(RuntimeOrigin::signed(ANOTHER_USER_ID), MGAId::get(), 1).unwrap(); + + Bootstrap::provision(RuntimeOrigin::signed(USER_ID), KSMId::get(), 100_u128).unwrap(); + + Bootstrap::on_initialize(120_u32.into()); + + assert_eq!(0, Bootstrap::balance(liq_token_id, USER_ID)); + assert_eq!(0, Bootstrap::balance(liq_token_id, ANOTHER_USER_ID)); + + Bootstrap::claim_liquidity_tokens(RuntimeOrigin::signed(USER_ID)).unwrap(); + Bootstrap::claim_liquidity_tokens(RuntimeOrigin::signed(ANOTHER_USER_ID)).unwrap(); + + let before_finalize = Bootstrap::balance( + liq_token_id, + ::TreasuryPalletId::get().into_account_truncating(), + ); + + Bootstrap::pre_finalize(RuntimeOrigin::signed(YET_ANOTHER_USER_ID)).unwrap(); + Bootstrap::finalize(RuntimeOrigin::signed(YET_ANOTHER_USER_ID)).unwrap(); + + let after_finalize = Bootstrap::balance( + liq_token_id, + ::TreasuryPalletId::get().into_account_truncating(), + ); + assert!(after_finalize > before_finalize); + }); +} + +#[test] +#[serial] +fn archive_previous_bootstrap_schedules() { + new_test_ext().execute_with(|| { + let enable_pool_creation_mock = MockAssetRegistryApi::enable_pool_creation_context(); + enable_pool_creation_mock.expect().return_const(true); + + Bootstrap::create_new_token(&USER_ID, u128::MAX); + Bootstrap::create_new_token(&USER_ID, u128::MAX); + + let pool_exists_mock = MockPoolCreateApi::pool_exists_context(); + pool_exists_mock.expect().return_const(false); + + let pool_create_mock = MockPoolCreateApi::pool_create_context(); + pool_create_mock + .expect() + .times(1) + .returning(move |addr, _, ksm_amount, _, mga_amount| { + let issuance = (ksm_amount + mga_amount) / 2; + let id = Bootstrap::create_new_token(&addr, issuance); + Some((id, issuance)) + }); + + Bootstrap::schedule_bootstrap( + RuntimeOrigin::root(), + KSMId::get(), + MGAId::get(), + 100_u32.into(), + Some(10), + 10, + Some(DEFAULT_RATIO), + false, + ) + .unwrap(); + Bootstrap::on_initialize(110_u32.into()); + Bootstrap::provision(RuntimeOrigin::signed(USER_ID), MGAId::get(), 1_000_000_000_u128) + .unwrap(); + Bootstrap::provision(RuntimeOrigin::signed(USER_ID), KSMId::get(), 100_u128).unwrap(); + Bootstrap::on_initialize(120_u32.into()); + Bootstrap::claim_liquidity_tokens(RuntimeOrigin::signed(USER_ID)).unwrap(); + assert_eq!(0, Bootstrap::archived().len()); + Bootstrap::pre_finalize(RuntimeOrigin::signed(YET_ANOTHER_USER_ID)).unwrap(); + Bootstrap::finalize(RuntimeOrigin::signed(YET_ANOTHER_USER_ID)).unwrap(); + assert_eq!(0, Bootstrap::provisions(USER_ID, KSMId::get())); + + assert_eq!(1, Bootstrap::archived().len()); + }) +} + +#[test] +#[serial] +fn test_activate_liq_tokens_is_called_with_all_liq_tokens_when_pool_is_promoted_and_all_provisions_are_non_vested( +) { + new_test_ext().execute_with(|| { + // ARRANGE - USER provides vested MGA tokens, ANOTHER_USER provides KSM tokens + set_up(); + jump_to_public_phase(); + init_mocks!(); + + let enable_pool_creation_mock = MockAssetRegistryApi::enable_pool_creation_context(); + enable_pool_creation_mock.expect().return_const(true); + + let mga_provision = 1_000_000_u128; + let ksm_provision = 100_u128; + let expected_liq_tokens_amount = (mga_provision + ksm_provision) / 2; + let expected_activated_tokens_amount = expected_liq_tokens_amount / 2; + + let is_enabled_mock = MockRewardsApi::is_enabled_context(); + is_enabled_mock.expect().return_const(true); + + let activate_liquidity = MockRewardsApi::activate_liquidity_context(); + activate_liquidity.expect().returning(move |_, _, activated_amount, _| { + assert_eq!(expected_activated_tokens_amount, activated_amount); + Ok(()) + }); + + // ACT + provisions(vec![ + (PROVISION_USER1_ID, MGAId::get(), mga_provision), + (PROVISION_USER2_ID, KSMId::get(), ksm_provision), + ]); + let (mga_valuation, ksm_valuation) = Bootstrap::valuations(); + assert_eq!(mga_valuation, 1_000_000); + assert_eq!(ksm_valuation, 100); + + Bootstrap::on_initialize(100_u32.into()); + assert_eq!(BootstrapPhase::Finished, Phase::::get()); + + Bootstrap::claim_and_activate_liquidity_tokens(RuntimeOrigin::signed(PROVISION_USER1_ID)) + .unwrap(); + Bootstrap::claim_and_activate_liquidity_tokens(RuntimeOrigin::signed(PROVISION_USER2_ID)) + .unwrap(); + }); +} + +// #[test] +// #[serial] +// fn test_dont_activate_liq_tokens_when_pool_is_promoted_but_all_provisions_are_vested() { +// new_test_ext().execute_with(|| { +// // ARRANGE - USER provides vested MGA tokens, ANOTHER_USER provides KSM tokens +// set_up(); +// jump_to_public_phase(); +// init_mocks!(); + +// let mga_provision = 1_000_000_u128; +// let ksm_provision = 100_u128; + +// let is_enabled_mock = MockRewardsApi::is_enabled_context(); +// is_enabled_mock.expect().return_const(true); + +// let activate_liquidity = MockRewardsApi::activate_liquidity_context(); +// activate_liquidity.expect().times(0); + +// // ACT +// provisions(vec![ +// (PROVISION_USER1_ID, MGAId::get(), mga_provision, ProvisionKind::Regular), +// (PROVISION_USER2_ID, KSMId::get(), ksm_provision, ProvisionKind::Vested(1, 150)), +// ]); + +// Bootstrap::on_initialize(100_u32.into()); +// assert_eq!(BootstrapPhase::Finished, Phase::::get()); + +// Bootstrap::claim_and_activate_liquidity(RuntimeOrigin::signed(PROVISION_USER2_ID)).unwrap(); +// }); +// } + +// #[test] +// #[serial] +// fn test_activate_liquidity_is_called_only_with_non_vested_liq_tokens_when_pool_is_promoted() +// { +// new_test_ext().execute_with(|| { +// // ARRANGE - USER provides vested MGA tokens, ANOTHER_USER provides KSM tokens +// set_up(); +// jump_to_public_phase(); +// init_mocks!(); + +// let mga_provision = 1_000_000_u128; +// let ksm_provision = 100_u128; +// let expected_liq_tokens_amount = (mga_provision + ksm_provision) / 2; +// let expected_liq_tokens_amount_per_user = expected_liq_tokens_amount / 2; +// let expected_non_vested_liq_tokens_amount_per_user = +// expected_liq_tokens_amount_per_user / 2; + +// let is_enabled_mock = MockRewardsApi::is_enabled_context(); +// is_enabled_mock.expect().return_const(true); + +// let activate_liquidity = MockRewardsApi::activate_liquidity_context(); +// activate_liquidity.expect().returning(move |_, _, activated_amount| { +// assert_eq!(expected_non_vested_liq_tokens_amount_per_user, activated_amount); +// Ok(().into()) +// }); + +// // ACT +// provisions(vec![ +// (PROVISION_USER1_ID, MGAId::get(), mga_provision, ProvisionKind::Regular), +// (PROVISION_USER2_ID, KSMId::get(), ksm_provision / 2, ProvisionKind::Regular), +// (PROVISION_USER2_ID, KSMId::get(), ksm_provision / 2, ProvisionKind::Vested(1, 150)), +// ]); + +// Bootstrap::on_initialize(100_u32.into()); +// assert_eq!(BootstrapPhase::Finished, Phase::::get()); + +// Bootstrap::claim_and_activate_liquidity(RuntimeOrigin::signed(PROVISION_USER2_ID)).unwrap(); +// }); +// } + +#[test] +#[serial] +fn test_dont_activate_liquidity_when_pool_is_not_promoted_and_provisions_are_not_vested() { + new_test_ext().execute_with(|| { + // ARRANGE - USER provides vested MGA tokens, ANOTHER_USER provides KSM tokens + set_up(); + jump_to_public_phase(); + init_mocks!(); + + let mga_provision = 1_000_000_u128; + let ksm_provision = 100_u128; + + let enable_pool_creation_mock = MockAssetRegistryApi::enable_pool_creation_context(); + enable_pool_creation_mock.expect().return_const(true); + + let is_enabled_mock = MockRewardsApi::is_enabled_context(); + is_enabled_mock.expect().return_const(false); + + let activate_liquidity = MockRewardsApi::activate_liquidity_context(); + activate_liquidity.expect().times(0); + + // ACT + provisions(vec![ + (PROVISION_USER1_ID, MGAId::get(), mga_provision), + (PROVISION_USER2_ID, KSMId::get(), ksm_provision), + ]); + + Bootstrap::on_initialize(100_u32.into()); + assert_eq!(BootstrapPhase::Finished, Phase::::get()); + + Bootstrap::claim_and_activate_liquidity_tokens(RuntimeOrigin::signed(PROVISION_USER1_ID)) + .unwrap(); + Bootstrap::claim_and_activate_liquidity_tokens(RuntimeOrigin::signed(PROVISION_USER2_ID)) + .unwrap(); + }); +} + +#[test] +#[serial] +fn test_claim_and_activate_doesnt_fail_when_tokens_activations_fails() { + new_test_ext().execute_with(|| { + // ARRANGE - USER provides vested MGA tokens, ANOTHER_USER provides KSM tokens + set_up(); + jump_to_public_phase(); + init_mocks!(); + + let enable_pool_creation_mock = MockAssetRegistryApi::enable_pool_creation_context(); + enable_pool_creation_mock.expect().return_const(true); + + let mga_provision = 1_000_000_u128; + let ksm_provision = 100_u128; + + let is_enabled_mock = MockRewardsApi::is_enabled_context(); + is_enabled_mock.expect().return_const(true); + + let activate_liquidity = MockRewardsApi::activate_liquidity_context(); + activate_liquidity + .expect() + // inject any error + .returning(move |_, _, _, _| Err(Error::::NotEnoughAssets.into())); + + // ACT + provisions(vec![ + (PROVISION_USER1_ID, MGAId::get(), mga_provision), + (PROVISION_USER2_ID, KSMId::get(), ksm_provision), + ]); + Bootstrap::on_initialize(100_u32.into()); + assert_eq!(BootstrapPhase::Finished, Phase::::get()); + + assert_ok!(Bootstrap::claim_and_activate_liquidity_tokens(RuntimeOrigin::signed( + PROVISION_USER1_ID + ))); + }); +} + +#[test] +#[serial] +fn test_pool_is_promoted_if_scheduled_to() { + new_test_ext().execute_with(|| { + use std::sync::{Arc, Mutex}; + set_up(); + + let liq_token_id: Arc> = Arc::new(Mutex::new(0_u32)); + let ref_liq_token_id = liq_token_id; + + let enable_pool_creation_mock = MockAssetRegistryApi::enable_pool_creation_context(); + enable_pool_creation_mock.expect().return_const(true); + + let pool_exists_mock = MockPoolCreateApi::pool_exists_context(); + pool_exists_mock.expect().return_const(false); + + let pool_create_mock = MockPoolCreateApi::pool_create_context(); + pool_create_mock + .expect() + .times(1) + .returning(move |addr, _, ksm_amount, _, mga_amount| { + let issuance = (ksm_amount + mga_amount) / 2; + let id = Bootstrap::create_new_token(&addr, issuance); + *(ref_liq_token_id.lock().unwrap()) = id; + Some((id, issuance)) + }); + + let mock = MockRewardsApi::enable_context(); + mock.expect().times(1).return_const(()); + + Bootstrap::schedule_bootstrap( + RuntimeOrigin::root(), + KSMId::get(), + MGAId::get(), + 100_u32.into(), + Some(10), + 10, + Some(DEFAULT_RATIO), + true, + ) + .unwrap(); + + Bootstrap::on_initialize(100_u32.into()); + assert_eq!(BootstrapPhase::Whitelist, Phase::::get()); + + Bootstrap::on_initialize(110_u32.into()); + assert_eq!(BootstrapPhase::Public, Phase::::get()); + + Bootstrap::on_initialize(120_u32.into()); + assert_eq!(BootstrapPhase::Finished, Phase::::get()); + }); +} + +#[test] +#[serial] +fn test_pool_is_not_promoted_if_not_scheduled_to() { + new_test_ext().execute_with(|| { + use std::sync::{Arc, Mutex}; + set_up(); + + let liq_token_id: Arc> = Arc::new(Mutex::new(0_u32)); + let ref_liq_token_id = liq_token_id; + + let enable_pool_creation_mock = MockAssetRegistryApi::enable_pool_creation_context(); + enable_pool_creation_mock.expect().return_const(true); + + let pool_exists_mock = MockPoolCreateApi::pool_exists_context(); + pool_exists_mock.expect().return_const(false); + + let pool_create_mock = MockPoolCreateApi::pool_create_context(); + pool_create_mock + .expect() + .times(1) + .returning(move |addr, _, ksm_amount, _, mga_amount| { + let issuance = (ksm_amount + mga_amount) / 2; + let id = Bootstrap::create_new_token(&addr, issuance); + *(ref_liq_token_id.lock().unwrap()) = id; + Some((id, issuance)) + }); + + let mock = MockRewardsApi::enable_context(); + mock.expect().times(0).return_const(()); + + Bootstrap::schedule_bootstrap( + RuntimeOrigin::root(), + KSMId::get(), + MGAId::get(), + 100_u32.into(), + Some(10), + 10, + Some(DEFAULT_RATIO), + false, + ) + .unwrap(); + + Bootstrap::on_initialize(100_u32.into()); + assert_eq!(BootstrapPhase::Whitelist, Phase::::get()); + + Bootstrap::on_initialize(110_u32.into()); + assert_eq!(BootstrapPhase::Public, Phase::::get()); + + Bootstrap::on_initialize(120_u32.into()); + assert_eq!(BootstrapPhase::Finished, Phase::::get()); + }); +} + +#[test] +#[serial] +fn test_pool_bootstrap_finalize_continues_if_asset_metadata_update_fails() { + new_test_ext().execute_with(|| { + set_up(); + + let enable_pool_creation_mock = MockAssetRegistryApi::enable_pool_creation_context(); + enable_pool_creation_mock.expect().return_const(false); + + let pool_exists_mock = MockPoolCreateApi::pool_exists_context(); + pool_exists_mock.expect().return_const(false); + + let pool_create_mock = MockPoolCreateApi::pool_create_context(); + pool_create_mock.expect().times(1).return_const(POOL_CREATE_DUMMY_RETURN_VALUE); + + Bootstrap::schedule_bootstrap( + RuntimeOrigin::root(), + KSMId::get(), + MGAId::get(), + 100_u32.into(), + Some(10), + 10, + Some(DEFAULT_RATIO), + false, + ) + .unwrap(); + + Bootstrap::on_initialize(120_u32.into()); + assert_eq!(BootstrapPhase::Finished, Phase::::get()); + }); +} + +#[test] +#[serial] +fn provisions_not_allowed_in_maintenance_mode() { + new_test_ext().execute_with(|| { + // ARRANGE - USER provides vested MGA tokens, ANOTHER_USER provides KSM tokens + set_up(); + jump_to_public_phase(); + + MockMaintenanceStatusProvider::set_maintenance(true); + + assert_err!( + Bootstrap::provision(RuntimeOrigin::signed(0), 0, 10000), + Error::::ProvisioningBlockedByMaintenanceMode + ); + }); +} + +// #[test] +// #[serial] +// fn vested_provisions_not_allowed_in_maintenance_mode() { +// new_test_ext().execute_with(|| { +// // ARRANGE - USER provides vested MGA tokens, ANOTHER_USER provides KSM tokens +// set_up(); +// jump_to_public_phase(); + +// MockMaintenanceStatusProvider::set_maintenance(true); + +// assert_err!( +// Bootstrap::provision_vested(RuntimeOrigin::signed(0), 0, 10000), +// Error::::ProvisioningBlockedByMaintenanceMode +// ); + +// }); +// } diff --git a/gasp-node/pallets/bootstrap/src/weights.rs b/gasp-node/pallets/bootstrap/src/weights.rs new file mode 100644 index 000000000..3f9f5e673 --- /dev/null +++ b/gasp-node/pallets/bootstrap/src/weights.rs @@ -0,0 +1,93 @@ +// This file is part of Mangata. + +// Copyright (C) 2020-2022 Mangata Foundation. +// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 + +// This program 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. + +// This program 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 this program. If not, see . + +//! Autogenerated weights for pallet_bootstrap +//! +//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev +//! DATE: 2022-08-25, STEPS: `50`, REPEAT: 20, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! EXECUTION: Some(Wasm), WASM-EXECUTION: Compiled, CHAIN: Some("dev"), DB CACHE: 1024 + +// Executed Command: +// /home/ubuntu/mangata-node/scripts/..//target/release/mangata-node +// benchmark +// pallet +// --chain +// dev +// --execution +// wasm +// --wasm-execution +// compiled +// --pallet +// pallet-bootstrap +// --extrinsic +// * +// --steps +// 50 +// --repeat +// 20 +// --output +// ./benchmarks/pallet-bootstrap_weights.rs +// --template +// ./templates/module-weight-template.hbs + +#![cfg_attr(rustfmt, rustfmt_skip)] +#![allow(unused_parens)] +#![allow(unused_imports)] +#![allow(clippy::unnecessary_cast)] + +use frame_support::{traits::Get, weights::{Weight, constants::RocksDbWeight}}; +use sp_std::marker::PhantomData; + +/// Weight functions needed for pallet_bootstrap. +pub trait WeightInfo { + fn schedule_bootstrap() -> Weight; + fn provision() -> Weight; + // fn provision_vested() -> Weight; + fn claim_and_activate_liquidity_tokens() -> Weight; + fn finalize() -> Weight; +} + + +// For backwards compatibility and tests +impl WeightInfo for () { + fn schedule_bootstrap() -> Weight { + Weight::from_parts(26_587_000, 0) + .saturating_add(RocksDbWeight::get().reads(4 as u64)) + .saturating_add(RocksDbWeight::get().writes(2 as u64)) + } + fn provision() -> Weight { + Weight::from_parts(89_870_000, 0) + .saturating_add(RocksDbWeight::get().reads(9 as u64)) + .saturating_add(RocksDbWeight::get().writes(6 as u64)) + } + // fn provision_vested() -> Weight { + // Weight::from_parts(183_414_000, 0) + // .saturating_add(RocksDbWeight::get().reads(11 as u64)) + // .saturating_add(RocksDbWeight::get().writes(8 as u64)) + // } + fn claim_and_activate_liquidity_tokens() -> Weight { + Weight::from_parts(432_898_000, 0) + .saturating_add(RocksDbWeight::get().reads(21 as u64)) + .saturating_add(RocksDbWeight::get().writes(12 as u64)) + } + fn finalize() -> Weight { + Weight::from_parts(112_243_000, 0) + .saturating_add(RocksDbWeight::get().reads(6 as u64)) + .saturating_add(RocksDbWeight::get().writes(12 as u64)) + } +} diff --git a/gasp-node/pallets/crowdloan-rewards/Cargo.toml b/gasp-node/pallets/crowdloan-rewards/Cargo.toml new file mode 100644 index 000000000..cd9168ec2 --- /dev/null +++ b/gasp-node/pallets/crowdloan-rewards/Cargo.toml @@ -0,0 +1,65 @@ +[package] +authors = ["PureStake"] +edition = "2021" +name = "pallet-crowdloan-rewards" +version = '0.6.0' +description = "Reward citizens who participated in a crowdloan to acquire a parachain slot o nthe backing relay chain." + +[dependencies] +codec = { workspace = true, default-features = false } +serde = { workspace = true, optional = true, features = ["derive"], default-features = false } +log = { workspace = true, default-features = false } +scale-info = { workspace = true, default-features = false, features = ["derive"] } + +frame-benchmarking = { workspace = true, default-features = false, optional = true } +frame-support = { workspace = true, default-features = false } +frame-system = { workspace = true, default-features = false } +mangata-types = { workspace = true, default-features = false } +pallet-utility = { workspace = true, default-features = false } +pallet-vesting-mangata = { workspace = true, default-features = false } +sp-core = { workspace = true, default-features = false } +sp-io = { workspace = true, default-features = false } +sp-runtime = { workspace = true, default-features = false } +sp-std = { workspace = true, default-features = false } +sp-trie = { workspace = true, default-features = false, optional = true } +sp-application-crypto = { workspace = true, default-features = false } + +orml-tokens = { workspace = true, default-features = false } + +[dev-dependencies] +orml-traits = { workspace = true, default-features = false } +sp-keystore = { workspace = true, default-features = false } + +[features] +default = ["std"] +std = [ + "frame-benchmarking?/std", + "frame-support/std", + "frame-system/std", + "log/std", + "mangata-types/std", + "orml-tokens/std", + "orml-traits/std", + "pallet-utility/std", + "pallet-vesting-mangata/std", + "codec/std", + "scale-info/std", + "serde", + "sp-core/std", + "sp-io/std", + "sp-runtime/std", + "sp-std/std", + "sp-application-crypto/std", +] +runtime-benchmarks = [ + "frame-benchmarking/runtime-benchmarks", + "sp-runtime/runtime-benchmarks", + "sp-trie", +] + +try-runtime = [ + "frame-system/try-runtime", + "frame-support/try-runtime", + "pallet-vesting-mangata/try-runtime", + "sp-runtime/try-runtime", +] diff --git a/gasp-node/pallets/crowdloan-rewards/src/benchmarks.rs b/gasp-node/pallets/crowdloan-rewards/src/benchmarks.rs new file mode 100644 index 000000000..4baa68af7 --- /dev/null +++ b/gasp-node/pallets/crowdloan-rewards/src/benchmarks.rs @@ -0,0 +1,354 @@ +#![cfg(feature = "runtime-benchmarks")] + +use super::*; +use crate::{Call, Config, Pallet, WRAPPED_BYTES_POSTFIX, WRAPPED_BYTES_PREFIX}; +use codec::{alloc::string::String, Encode}; +use frame_benchmarking::{account, benchmarks, impl_benchmark_test_suite}; +use frame_support::{ + assert_ok, + traits::{Get, OnFinalize}, +}; +use frame_system::RawOrigin; +use orml_tokens::MultiTokenCurrencyExtended; +use sp_application_crypto::{ecdsa::Public, RuntimePublic}; +use sp_runtime::{ + account::EthereumSignature, + traits::{BlockNumberProvider, One, Zero}, + AccountId20, +}; +use sp_std::{fmt::Write, vec, vec::Vec}; + +/// Default balance amount is minimum contribution +fn default_balance() -> BalanceOf { + T::MinimumReward::get() +} + +/// Create a funded user. +// fn fund_specific_account(pallet_account: T::AccountId, extra: BalanceOf) { +// let default_balance = default_balance::(); +// let total = default_balance + extra; +// TODO: fix +// T::RewardCurrency::make_free_balance_be(&pallet_account, total); +// T::RewardCurrency::issue(total); +// } + +/// Create a funded user. +fn create_funded_user( + string: &'static str, + n: u32, + extra: BalanceOf, +) -> T::AccountId { + const SEED: u32 = 0; + let user = account(string, n, SEED); + let default_balance = default_balance::(); + let total = default_balance + extra; + while T::Tokens::get_next_currency_id() <= T::NativeTokenId::get() { + T::Tokens::create(&user, 0u32.into()).unwrap(); + } + assert_ok!(T::Tokens::mint(T::NativeTokenId::get(), &user, total)); + user +} + +/// Create contributors. +fn create_contributors( + total_number: u32, + seed_offset: u32, +) -> Vec<(T::RelayChainAccountId, Option, BalanceOf)> { + let mut contribution_vec = Vec::new(); + for i in 0..total_number { + let seed = SEED - seed_offset - i; + let mut seed_20: [u8; 20] = [0; 20]; + seed_20[16..20].copy_from_slice(&seed.to_be_bytes()); + let relay_chain_account: AccountId20 = + <[u8; 20]>::try_from(&seed_20[..]).expect("Right len of slice").into(); + let user = create_funded_user::("user", seed, 0u32.into()); + let contribution: BalanceOf = 100u32.into(); + contribution_vec.push((relay_chain_account.into(), Some(user.clone()), contribution)); + } + contribution_vec +} + +/// Insert contributors. +fn insert_contributors( + contributors: Vec<(T::RelayChainAccountId, Option, BalanceOf)>, +) -> Result<(), &'static str> { + let mut sub_vec = Vec::new(); + let batch = max_batch_contributors::(); + // Due to the MaxInitContributors associated type, we need ton insert them in batches + // When we reach the batch size, we insert them + let amount = contributors + .iter() + .fold(0u32.into(), |acc: BalanceOf, (_, _, amount)| acc + *amount); + Pallet::::set_crowdloan_allocation(RawOrigin::Root.into(), amount)?; + + for i in 0..contributors.len() { + sub_vec.push(contributors[i].clone()); + // If we reached the batch size, we should insert them + if i as u32 % batch == batch - 1 || i == contributors.len() - 1 { + Pallet::::initialize_reward_vec(RawOrigin::Root.into(), sub_vec.clone())?; + sub_vec.clear() + } + } + Ok(()) +} + +/// Create a Contributor. +fn close_initialization( + init_relay: T::VestingBlockNumber, + end_relay: T::VestingBlockNumber, +) -> Result<(), &'static str> { + Pallet::::complete_initialization(RawOrigin::Root.into(), init_relay, end_relay)?; + Ok(()) +} + +fn create_sig(seed: u32, payload: Vec) -> (AccountId20, EthereumSignature) { + let mut buffer = String::new(); + let _ = write!(&mut buffer, "//{seed}"); + let public = Public::generate_pair(sp_core::testing::ECDSA, Some(buffer.into_bytes())); + let sig = public.sign(sp_core::testing::ECDSA, &payload).unwrap(); + let signature: EthereumSignature = sig.into(); + + let account: AccountId20 = public.into(); + (account, signature) +} + +fn max_batch_contributors() -> u32 { + <::MaxInitContributors as Get>::get() +} + +// This is our current number of contributors +const MAX_ALREADY_USERS: u32 = 5799; +const SEED: u32 = 999999999; + +benchmarks! { + set_crowdloan_allocation{ + assert!(Pallet::::get_crowdloan_allocation(0u32).is_zero()); + + }: _(RawOrigin::Root, 10000u32.into()) + verify { + assert_eq!(Pallet::::get_crowdloan_allocation(0u32), 10000u32.into()); + } + + initialize_reward_vec { + let x in 1..max_batch_contributors::(); + let y = MAX_ALREADY_USERS; + + // Create y contributors + let contributors = create_contributors::(y, 0); + + // Insert them + let amount = contributors.iter().fold(0u32.into(), |acc: BalanceOf, (_,_,amount)| acc + *amount); + assert_ok!(Pallet::::set_crowdloan_allocation(RawOrigin::Root.into(), amount)); + insert_contributors::(contributors)?; + + // This X new contributors are the ones we will count + let new_contributors = create_contributors::(x, y); + let new_amount = new_contributors.iter().fold(0u32.into(), |acc: BalanceOf, (_,_,amount)| acc + *amount); + assert_ok!(Pallet::::set_crowdloan_allocation(RawOrigin::Root.into(), amount + new_amount)); + + let verifier = create_funded_user::("user", SEED, 0u32.into()); + + }: _(RawOrigin::Root, new_contributors) + verify { + assert!(Pallet::::accounts_payable(0u32, &verifier).is_some()); + } + + complete_initialization { + // Fund pallet account + let total_pot = 100u32; + assert_ok!(Pallet::::set_crowdloan_allocation(RawOrigin::Root.into(), total_pot.into())); + // 1 contributor is enough + let contributors = create_contributors::(1, 0); + + // Insert them + insert_contributors::(contributors)?; + + // We need to create the first block inherent, to initialize the initRelayBlock + T::VestingBlockProvider::set_block_number(1u32.into()); + as OnFinalize>>::on_finalize(BlockNumberFor::::one()); + + }: _(RawOrigin::Root, 1u32.into(), 10u32.into()) + verify { + assert!(Pallet::::initialized()); + } + + claim { + // Fund pallet account + let total_pot = 100u32; + assert_ok!(Pallet::::set_crowdloan_allocation(RawOrigin::Root.into(), total_pot.into())); + + // The user that will make the call + let caller: T::AccountId = create_funded_user::("user", SEED, 100u32.into()); + + // We verified there is no dependency of the number of contributors already inserted in claim + // Create 1 contributor + let contributors: Vec<(T::RelayChainAccountId, Option, BalanceOf)> = + vec![(AccountId20::from([1u8;20]).into(), Some(caller.clone()), total_pot.into())]; + + // Insert them + insert_contributors::(contributors)?; + + // Close initialization + close_initialization::(1u32.into(), 10u32.into())?; + + // First inherent + T::VestingBlockProvider::set_block_number(1u32.into()); + as OnFinalize>>::on_finalize(BlockNumberFor::::one()); + + // Claimed + let claimed_reward = Pallet::::accounts_payable(0u32, &caller).unwrap().claimed_reward; + + // Create 4th relay block, by now the user should have vested some amount + T::VestingBlockProvider::set_block_number(4u32.into()); + }: _(RawOrigin::Signed(caller.clone()),None) + verify { + assert!(Pallet::::accounts_payable(0u32, &caller).unwrap().claimed_reward > claimed_reward); + } + + update_reward_address { + // Fund pallet account + let total_pot = 100u32; + assert_ok!(Pallet::::set_crowdloan_allocation(RawOrigin::Root.into(), total_pot.into())); + + // The user that will make the call + let caller: T::AccountId = create_funded_user::("user", SEED, 100u32.into()); + + let relay_account: T::RelayChainAccountId = AccountId20::from([1u8;20]).into(); + // We verified there is no dependency of the number of contributors already inserted in update_reward_address + // Create 1 contributor + let contributors: Vec<(T::RelayChainAccountId, Option, BalanceOf)> = + vec![(relay_account.clone(), Some(caller.clone()), total_pot.into())]; + + // Insert them + insert_contributors::(contributors)?; + + // Close initialization + close_initialization::(1u32.into(), 10u32.into())?; + + // First inherent + T::VestingBlockProvider::set_block_number(1u32.into()); + as OnFinalize>>::on_finalize(BlockNumberFor::::one()); + + // Let's advance the relay so that the vested amount get transferred + T::VestingBlockProvider::set_block_number(4u32.into()); + + // The new user + let new_user = create_funded_user::("user", SEED+1, 0u32.into()); + + }: _(RawOrigin::Signed(caller.clone()), new_user.clone(), None) + verify { + assert_eq!(Pallet::::accounts_payable(0u32, &new_user).unwrap().total_reward, (100u32.into())); + assert!(Pallet::::claimed_relay_chain_ids(0u32, &relay_account).is_some()); + } + + associate_native_identity { + // Fund pallet account + let total_pot = 100u32; + // fund_specific_account::(Pallet::::account_id(), total_pot.into()); + + // The caller that will associate the account + let caller: T::AccountId = create_funded_user::("user", SEED, 100u32.into()); + + // Construct payload + let mut payload = WRAPPED_BYTES_PREFIX.to_vec(); + payload.append(&mut T::SignatureNetworkIdentifier::get().to_vec()); + payload.append(&mut caller.clone().encode()); + payload.append(&mut WRAPPED_BYTES_POSTFIX.to_vec()); + + // Create a fake sig for such an account + let (relay_account, signature) = create_sig::(SEED, payload); + + // We verified there is no dependency of the number of contributors already inserted in associate_native_identity + // Create 1 contributor + let contributors: Vec<(T::RelayChainAccountId, Option, BalanceOf)> = + vec![(relay_account.clone().into(), None, total_pot.into())]; + + // Insert them + insert_contributors::(contributors)?; + + // Clonse initialization + close_initialization::(1u32.into(), 10u32.into())?; + + // First inherent + T::VestingBlockProvider::set_block_number(1u32.into()); + as OnFinalize>>::on_finalize(BlockNumberFor::::one()); + + }: _(RawOrigin::Root, caller.clone(), relay_account.into(), signature) + verify { + assert_eq!(Pallet::::accounts_payable(0u32, &caller).unwrap().total_reward, (100u32.into())); + } + + change_association_with_relay_keys { + + // The weight will depend on the number of proofs provided + // We need to parameterize this value + // We leave this as the max batch length + let x in 1..max_batch_contributors::(); + + // Fund pallet account + let total_pot = 100u32*x; + assert_ok!(Pallet::::set_crowdloan_allocation(RawOrigin::Root.into(), total_pot.into())); + + // The first reward account that will associate the account + let first_reward_account: T::AccountId = create_funded_user::("user", SEED, 100u32.into()); + + // The account to which we will update our reward account + let second_reward_account: T::AccountId = create_funded_user::("user", SEED-1, 100u32.into()); + + let mut proofs: Vec<(T::RelayChainAccountId, EthereumSignature)> = Vec::new(); + + // Construct payload + let mut payload = WRAPPED_BYTES_PREFIX.to_vec(); + payload.append(&mut T::SignatureNetworkIdentifier::get().to_vec()); + payload.append(&mut second_reward_account.clone().encode()); + payload.append(&mut first_reward_account.clone().encode()); + payload.append(&mut WRAPPED_BYTES_POSTFIX.to_vec()); + + // Create N sigs for N accounts + for i in 0..x { + let (relay_account, signature) = create_sig::(SEED-i, payload.clone()); + proofs.push((relay_account.into(), signature)); + } + + // Create x contributors + // All of them map to the same account + let mut contributors: Vec<(T::RelayChainAccountId, Option, BalanceOf)> = Vec::new(); + for (relay_account, _) in proofs.clone() { + contributors.push((relay_account, Some(first_reward_account.clone()), 100u32.into())); + } + + // Insert them + insert_contributors::(contributors.clone())?; + + // Clonse initialization + close_initialization::(1u32.into(), 10u32.into())?; + + // First inherent + T::VestingBlockProvider::set_block_number(1u32.into()); + as OnFinalize>>::on_finalize(BlockNumberFor::::one()); + + }: _(RawOrigin::Root, second_reward_account.clone(), first_reward_account.clone(), proofs) + verify { + assert!(Pallet::::accounts_payable(0u32, &second_reward_account).is_some()); + assert_eq!(Pallet::::accounts_payable(0u32, &second_reward_account).unwrap().total_reward, (100u32*x).into()); + assert!(Pallet::::accounts_payable(0u32, &first_reward_account).is_none()); + + } + +} +#[cfg(test)] +mod tests { + use crate::mock::Test; + use sp_io::TestExternalities; + use sp_keystore::{testing::MemoryKeystore, KeystoreExt}; + use sp_runtime::BuildStorage; + + pub fn new_test_ext() -> TestExternalities { + let t = frame_system::GenesisConfig::::default().build_storage().unwrap(); + let mut ext = TestExternalities::new(t); + ext.register_extension(KeystoreExt::new(MemoryKeystore::new())); + ext + } +} + +impl_benchmark_test_suite!(Pallet, crate::benchmarks::tests::new_test_ext(), crate::mock::Test); diff --git a/gasp-node/pallets/crowdloan-rewards/src/lib.rs b/gasp-node/pallets/crowdloan-rewards/src/lib.rs new file mode 100644 index 000000000..6ef9c5057 --- /dev/null +++ b/gasp-node/pallets/crowdloan-rewards/src/lib.rs @@ -0,0 +1,780 @@ +// Copyright 2019-2022 PureStake Inc. +// This file is part of Moonbeam. + +// Moonbeam 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. + +// Moonbeam 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 Moonbeam. If not, see . + +//! # Crowdloan Rewards Pallet +//! +//! This pallet issues rewards to citizens who participated in a crowdloan on the backing relay +//! chain (eg Kusama) in order to help this parrachain acquire a parachain slot. +//! +//! ## Monetary Policy +//! +//! This is simple and mock for now. We can do whatever we want. +//! This pallet stores a constant "reward ratio" which is the number of reward tokens to pay per +//! contributed token. In simple cases this can be 1, but needs to be customizeable to allow for +//! vastly differing absolute token supplies between relay and para. +//! Vesting is also linear. No tokens are vested at genesis and they unlock linearly until a +//! predecided block number. Vesting computations happen on demand when payouts are requested. So +//! no block weight is ever wasted on this, and there is no "base-line" cost of updating vestings. +//! Like I said, we can anything we want there. Even a non-linear reward curve to disincentivize +//! whales. +//! +//! ## Payout Mechanism +//! +//! The current payout mechanism requires contributors to claim their payouts. Because they are +//! paying the transaction fees for this themselves, they can do it as often as every block, or +//! wait and claim the entire thing once it is fully vested. We could consider auto payouts if we +//! want. +//! +//! ## Sourcing Contribution Information +//! +//! The pallet can learn about the crowdloan contributions in several ways. +//! +//! * **Through the initialize_reward_vec extrinsic* +//! +//! The simplest way is to call the initialize_reward_vec through a democracy proposal/sudo call. +//! This makes sense in a scenario where the crowdloan took place entirely offchain. +//! This extrinsic initializes the associated and unassociated stoerage with the provided data +//! +//! * **ReadingRelayState** +//! +//! The most elegant, but most complex solution would be for the para to read the contributions +//! directly from the relay state. Blocked by https://github.com/paritytech/cumulus/issues/320 so +//! I won't pursue it further right now. I can't decide whether that would really add security / +//! trustlessness, or is just a sexy blockchain thing to do. Contributors can always audit the +//! democracy proposal and make sure their contribution is in it, so in that sense reading relay state +//! isn't necessary. But if a single contribution is left out, the rest of the contributors might +//! not care enough to delay network launch. The little guy might get censored. + +#![cfg_attr(not(feature = "std"), no_std)] + +use frame_support::{ + pallet, + pallet_prelude::*, + traits::{MultiTokenCurrency, MultiTokenVestingLocks, OnRuntimeUpgrade}, +}; +use frame_system::pallet_prelude::*; +use orml_tokens::MultiTokenCurrencyExtended; +use sp_runtime::{ + account::EthereumSignature, + traits::{AtLeast32BitUnsigned, BlockNumberProvider, CheckedSub, Saturating, Verify}, + AccountId20, Perbill, +}; +use sp_std::{collections::btree_map::BTreeMap, vec, vec::Vec}; + +pub use pallet::*; +#[cfg(feature = "try-runtime")] +pub use sp_runtime::TryRuntimeError; +pub use weights::WeightInfo; + +#[cfg(feature = "runtime-benchmarks")] +mod benchmarks; +#[cfg(test)] +pub(crate) mod mock; +#[cfg(test)] +mod tests; +pub mod weights; + +#[macro_export] +macro_rules! log { + ($level:tt, $patter:expr $(, $values:expr)* $(,)?) => { + log::$level!( + target: "crowdloan", + concat!("[{:?}] 💸 ", $patter), >::block_number() $(, $values)* + ) + }; +} + +pub type BalanceOf = + <::Tokens as MultiTokenCurrency<::AccountId>>::Balance; + +pub type TokenIdOf = <::Tokens as MultiTokenCurrency< + ::AccountId, +>>::CurrencyId; + +#[pallet] +pub mod pallet { + use super::*; + + const STORAGE_VERSION: StorageVersion = StorageVersion::new(1); + + #[pallet::pallet] + #[pallet::without_storage_info] + #[pallet::storage_version(STORAGE_VERSION)] + // The crowdloan rewards pallet + pub struct Pallet(PhantomData); + + // The wrapper around which the reward changing message needs to be wrapped + pub const WRAPPED_BYTES_PREFIX: &[u8] = b""; + pub const WRAPPED_BYTES_POSTFIX: &[u8] = b""; + + /// Configuration trait of this pallet. + #[pallet::config] + pub trait Config: frame_system::Config { + /// The overarching event type + type RuntimeEvent: From> + IsType<::RuntimeEvent>; + /// Checker for the reward vec, is it initalized already? + type Initialized: Get; + /// Percentage to be payed at initialization + #[pallet::constant] + type InitializationPayment: Get; + // Max number of contributors that can be inserted at once in initialize_reward_vec + #[pallet::constant] + type MaxInitContributors: Get; + /// The minimum contribution to which rewards will be paid. + type MinimumReward: Get>; + /// A fraction representing the percentage of proofs + /// that need to be presented to change a reward address through the relay keys + #[pallet::constant] + type RewardAddressRelayVoteThreshold: Get; + /// MGA token Id + #[pallet::constant] + type NativeTokenId: Get>; + /// The currency in which the rewards will be paid (probably the parachain native currency) + type Tokens: MultiTokenCurrency + + MultiTokenCurrencyExtended; + /// The AccountId type contributors used on the relay chain. + type RelayChainAccountId: Parameter + //TODO these AccountId20 bounds feel a little extraneous. I wonder if we can remove them. + // Since our "relaychain" is now ethereum + + Into + + From + + Ord; + + // The origin that is allowed to change the reward address with relay signatures + type RewardAddressChangeOrigin: EnsureOrigin; + + /// Network Identifier to be appended into the signatures for reward address change/association + /// Prevents replay attacks from one network to the other + #[pallet::constant] + type SignatureNetworkIdentifier: Get<&'static [u8]>; + + // The origin that is allowed to change the reward address with relay signatures + type RewardAddressAssociateOrigin: EnsureOrigin; + + /// The type that will be used to track vesting progress + type VestingBlockNumber: AtLeast32BitUnsigned + Parameter + Default + Into>; + + /// The notion of time that will be used for vesting. Probably + /// either the relay chain or sovereignchain block number. + type VestingBlockProvider: BlockNumberProvider; + + /// Vesting provider for paying out vested rewards + type VestingProvider: MultiTokenVestingLocks< + Self::AccountId, + Moment = Self::VestingBlockNumber, + Currency = Self::Tokens, + >; + + type WeightInfo: WeightInfo; + } + + /// Stores info about the rewards owed as well as how much has been vested so far. + /// For a primer on this kind of design, see the recipe on compounding interest + /// https://substrate.dev/recipes/fixed-point.html#continuously-compounding + #[derive(Default, Clone, Encode, Decode, RuntimeDebug, PartialEq, scale_info::TypeInfo)] + #[scale_info(skip_type_params(T))] + pub struct RewardInfo { + pub total_reward: BalanceOf, + pub claimed_reward: BalanceOf, + pub contributed_relay_addresses: Vec, + } + + #[pallet::call] + impl Pallet { + /// Associate a native rewards_destination identity with a crowdloan contribution. + /// + /// The caller needs to provide the unassociated relay account and a proof to succeed + /// with the association + /// The proof is nothing but a signature over the reward_address using the relay keys + #[pallet::call_index(0)] + #[pallet::weight(T::WeightInfo::associate_native_identity())] + pub fn associate_native_identity( + origin: OriginFor, + reward_account: T::AccountId, + relay_account: T::RelayChainAccountId, + proof: EthereumSignature, + ) -> DispatchResultWithPostInfo { + // Check that the origin is the one able to asociate the reward addrss + T::RewardAddressChangeOrigin::ensure_origin(origin)?; + + // Check the proof: + // 1. Is signed by an actual unassociated contributor + // 2. Signs a valid native identity + // Check the proof. The Proof consists of a Signature of the rewarded account with the + // claimer key + + // The less costly checks will go first + + // The relay account should be unassociated + let reward_info = + UnassociatedContributions::::get(CrowdloanId::::get(), &relay_account) + .ok_or(Error::::NoAssociatedClaim)?; + + // We ensure the relay chain id wast not yet associated to avoid multi-claiming + // We dont need this right now, as it will always be true if the above check is true + ensure!( + ClaimedRelayChainIds::::get(CrowdloanId::::get(), &relay_account).is_none(), + Error::::AlreadyAssociated + ); + + // For now I prefer that we dont support providing an existing account here + ensure!( + AccountsPayable::::get(CrowdloanId::::get(), &reward_account).is_none(), + Error::::AlreadyAssociated + ); + + // b"" "SignatureNetworkIdentifier" + "new_account" + b"" + let mut payload = WRAPPED_BYTES_PREFIX.to_vec(); + payload.append(&mut T::SignatureNetworkIdentifier::get().to_vec()); + payload.append(&mut reward_account.encode()); + payload.append(&mut WRAPPED_BYTES_POSTFIX.to_vec()); + + // Check the signature + Self::verify_signatures( + vec![(relay_account.clone(), proof)], + reward_info.clone(), + payload, + )?; + + // Insert on payable + AccountsPayable::::insert(CrowdloanId::::get(), &reward_account, &reward_info); + + // Remove from unassociated + >::remove(CrowdloanId::::get(), &relay_account); + + // Insert in mapping + ClaimedRelayChainIds::::insert(CrowdloanId::::get(), &relay_account, ()); + + // Emit Event + Self::deposit_event(Event::NativeIdentityAssociated( + relay_account, + reward_account, + reward_info.total_reward, + )); + + Ok(Default::default()) + } + + /// Change reward account by submitting proofs from relay accounts + /// + /// The number of valid proofs needs to be bigger than 'RewardAddressRelayVoteThreshold' + /// The account to be changed needs to be submitted as 'previous_account' + + /// Origin must be RewardAddressChangeOrigin + #[pallet::call_index(1)] + #[pallet::weight(T::WeightInfo::change_association_with_relay_keys(proofs.len() as u32))] + pub fn change_association_with_relay_keys( + origin: OriginFor, + reward_account: T::AccountId, + previous_account: T::AccountId, + proofs: Vec<(T::RelayChainAccountId, EthereumSignature)>, + ) -> DispatchResultWithPostInfo { + // Check that the origin is the one able to change the reward addrss + T::RewardAddressChangeOrigin::ensure_origin(origin)?; + + // For now I prefer that we dont support providing an existing account here + ensure!( + AccountsPayable::::get(CrowdloanId::::get(), &reward_account).is_none(), + Error::::AlreadyAssociated + ); + + // To avoid replay attacks, we make sure the payload contains the previous address too + // I am assuming no rational user will go back to a previously changed reward address + // b"" + "SignatureNetworkIdentifier" + new_account" + "previous_account" + b"" + let mut payload = WRAPPED_BYTES_PREFIX.to_vec(); + payload.append(&mut T::SignatureNetworkIdentifier::get().to_vec()); + payload.append(&mut reward_account.encode()); + payload.append(&mut previous_account.encode()); + payload.append(&mut WRAPPED_BYTES_POSTFIX.to_vec()); + + // Get the reward info for the account to be changed + let reward_info = AccountsPayable::::get(CrowdloanId::::get(), &previous_account) + .ok_or(Error::::NoAssociatedClaim)?; + + Self::verify_signatures(proofs, reward_info.clone(), payload)?; + + // Remove fromon payable + AccountsPayable::::remove(CrowdloanId::::get(), &previous_account); + + // Insert on payable + AccountsPayable::::insert(CrowdloanId::::get(), &reward_account, &reward_info); + + // Emit Event + Self::deposit_event(Event::RewardAddressUpdated(previous_account, reward_account)); + + Ok(Default::default()) + } + + /// Collect rewards from particular crowdloan. + /// If crowdloan_id is not set current [`CrowdloanId`] id will be used. + /// Caller is instantly rewarded with [`InitializationPayment`] % of available rewards, + /// remaining funds are locked according to schedule(using `pallet_mangata_vesting` configured + /// by [`Pallet::::complete_initialization`] call. + #[pallet::call_index(2)] + #[pallet::weight(T::WeightInfo::claim())] + pub fn claim( + origin: OriginFor, + crowdloan_id: Option, + ) -> DispatchResultWithPostInfo { + let payee = ensure_signed(origin)?; + + let current_crowdloan_id = CrowdloanId::::get(); + let crowdloan_id = crowdloan_id.unwrap_or(current_crowdloan_id); + + ensure!( + >::get() || crowdloan_id < current_crowdloan_id, + Error::::RewardVecNotFullyInitializedYet + ); + // Calculate the veted amount on demand. + let mut info = AccountsPayable::::get(crowdloan_id, &payee) + .ok_or(Error::::NoAssociatedClaim)?; + ensure!(info.claimed_reward < info.total_reward, Error::::RewardsAlreadyClaimed); + + // Get the current block used for vesting purposes + let _now = T::VestingBlockProvider::current_block_number(); + + // How much should the contributor have already claimed by this block? + // By multiplying first we allow the conversion to integer done with the biggest number + let amount: BalanceOf = info + .total_reward + .checked_sub(&info.claimed_reward) + .ok_or(Error::::MathOverflow)?; + info.claimed_reward += amount; + + T::Tokens::mint(T::NativeTokenId::get(), &payee, amount)?; + + let period = CrowdloanPeriod::::get(crowdloan_id).ok_or(Error::::PeriodNotSet)?; + + T::VestingProvider::lock_tokens( + &payee, + T::NativeTokenId::get(), + amount - T::InitializationPayment::get() * amount, + Some(period.0), + period.1.into(), + )?; + + Self::deposit_event(Event::RewardsPaid(payee.clone(), amount)); + AccountsPayable::::insert(crowdloan_id, &payee, &info); + + Ok(Default::default()) + } + + /// Update reward address, proving that the caller owns the current native key + #[pallet::call_index(3)] + #[pallet::weight(T::WeightInfo::update_reward_address())] + pub fn update_reward_address( + origin: OriginFor, + new_reward_account: T::AccountId, + crowdloan_id: Option, + ) -> DispatchResultWithPostInfo { + let signer = ensure_signed(origin)?; + + let crowdloan_id = crowdloan_id.unwrap_or(CrowdloanId::::get()); + + // Calculate the veted amount on demand. + let info = AccountsPayable::::get(crowdloan_id, &signer) + .ok_or(Error::::NoAssociatedClaim)?; + + // For now I prefer that we dont support providing an existing account here + ensure!( + AccountsPayable::::get(crowdloan_id, &new_reward_account).is_none(), + Error::::AlreadyAssociated + ); + + // Remove previous rewarded account + AccountsPayable::::remove(crowdloan_id, &signer); + + // Update new rewarded acount + AccountsPayable::::insert(crowdloan_id, &new_reward_account, &info); + + // Emit event + Self::deposit_event(Event::RewardAddressUpdated(signer, new_reward_account)); + + Ok(Default::default()) + } + + /// This extrinsic completes the initialization if some checks are fullfiled. These checks are: + /// -The reward contribution money matches the crowdloan pot + /// -The end vesting block is higher than the init vesting block + /// -The initialization has not complete yet + #[pallet::call_index(4)] + #[pallet::weight(T::WeightInfo::complete_initialization())] + pub fn complete_initialization( + origin: OriginFor, + lease_start_block: T::VestingBlockNumber, + lease_ending_block: T::VestingBlockNumber, + ) -> DispatchResultWithPostInfo { + ensure_root(origin)?; + + let initialized = >::get(); + + // This ensures there was no prior initialization + ensure!(!initialized, Error::::RewardVecAlreadyInitialized); + + // This ensures the end vesting block (when all funds are fully vested) + // is bigger than the init vesting block + ensure!(lease_ending_block > lease_start_block, Error::::VestingPeriodNonValid); + + let total_initialized_rewards = + InitializedRewardAmount::::get(CrowdloanId::::get()); + + let reward_difference = Pallet::::get_crowdloan_allocation(CrowdloanId::::get()) + .saturating_sub(total_initialized_rewards); + + // Ensure the difference is not bigger than the total number of contributors + ensure!( + reward_difference < TotalContributors::::get(CrowdloanId::::get()).into(), + Error::::RewardsDoNotMatchFund + ); + + >::insert( + CrowdloanId::::get(), + (lease_start_block, lease_ending_block), + ); + >::put(true); + + Ok(Default::default()) + } + + /// Initialize the reward distribution storage. It shortcuts whenever an error is found + + /// Sets crowdloan allocation for: + /// - current round of crowdloan - if it has not been completed (`[Pallet::::complete_initialization]`) + /// - following round of crowdloan rewards payment if previous one has been already + /// completed + #[pallet::call_index(5)] + #[pallet::weight(T::WeightInfo::set_crowdloan_allocation())] + pub fn set_crowdloan_allocation( + origin: OriginFor, + crowdloan_allocation_amount: BalanceOf, + ) -> DispatchResultWithPostInfo { + ensure_root(origin)?; + + if >::get() { + let zero: BalanceOf = 0_u32.into(); + >::mutate(|val| *val += 1); + >::put(false); + >::insert(CrowdloanId::::get(), zero); + } + + ensure!( + crowdloan_allocation_amount >= + InitializedRewardAmount::::get(CrowdloanId::::get()), + Error::::AllocationDoesNotMatch + ); + + CrowdloanAllocation::::insert(CrowdloanId::::get(), crowdloan_allocation_amount); + Ok(Default::default()) + } + + /// Initialize the reward distribution storage. It shortcuts whenever an error is found + + /// This does not enforce any checks other than making sure we dont go over funds + /// complete_initialization should perform any additional + #[pallet::call_index(6)] + #[pallet::weight(T::WeightInfo::initialize_reward_vec(rewards.len() as u32))] + pub fn initialize_reward_vec( + origin: OriginFor, + rewards: Vec<(T::RelayChainAccountId, Option, BalanceOf)>, + ) -> DispatchResultWithPostInfo { + ensure_root(origin)?; + let initialized = >::get(); + ensure!(!initialized, Error::::RewardVecAlreadyInitialized); + + // Ensure we are below the max number of contributors + ensure!( + rewards.len() as u32 <= T::MaxInitContributors::get(), + Error::::TooManyContributors + ); + + // What is the amount initialized so far? + let mut total_initialized_rewards = + InitializedRewardAmount::::get(CrowdloanId::::get()); + + // Total number of contributors + let mut total_contributors = TotalContributors::::get(CrowdloanId::::get()); + + let incoming_rewards: BalanceOf = rewards + .iter() + .fold(0_u32.into(), |acc: BalanceOf, (_, _, reward)| acc + *reward); + + // Ensure we dont go over funds + ensure!( + total_initialized_rewards + incoming_rewards <= + Pallet::::get_crowdloan_allocation(CrowdloanId::::get()), + Error::::BatchBeyondFundPot + ); + + for (relay_account, native_account, reward) in &rewards { + if ClaimedRelayChainIds::::get(CrowdloanId::::get(), relay_account).is_some() || + UnassociatedContributions::::get(CrowdloanId::::get(), relay_account) + .is_some() + { + // Dont fail as this is supposed to be called with batch calls and we + // dont want to stall the rest of the contributions + Self::deposit_event(Event::InitializedAlreadyInitializedAccount( + relay_account.clone(), + native_account.clone(), + *reward, + )); + continue + } + + total_initialized_rewards += *reward; + total_contributors += 1; + + if *reward < T::MinimumReward::get() { + // Don't fail as this is supposed to be called with batch calls and we + // dont want to stall the rest of the contributions + Self::deposit_event(Event::InitializedAccountWithNotEnoughContribution( + relay_account.clone(), + native_account.clone(), + *reward, + )); + continue + } + + if let Some(native_account) = native_account { + AccountsPayable::::mutate(CrowdloanId::::get(), native_account, |info| { + let result = info.as_mut().map_or( + RewardInfo { + total_reward: *reward, + claimed_reward: 0_u32.into(), + contributed_relay_addresses: vec![relay_account.clone()], + }, + |i| { + i.total_reward += *reward; + i.contributed_relay_addresses.push(relay_account.clone()); + i.clone() + }, + ); + *info = Some(result); + }); + ClaimedRelayChainIds::::insert(CrowdloanId::::get(), relay_account, ()); + } else { + UnassociatedContributions::::insert( + CrowdloanId::::get(), + relay_account, + RewardInfo { + total_reward: *reward, + claimed_reward: 0_u32.into(), + contributed_relay_addresses: vec![relay_account.clone()], + }, + ); + } + } + InitializedRewardAmount::::insert( + CrowdloanId::::get(), + total_initialized_rewards, + ); + TotalContributors::::insert(CrowdloanId::::get(), total_contributors); + + Ok(Default::default()) + } + } + + impl Pallet { + pub(crate) fn total_contributors() -> u32 { + TotalContributors::::get(CrowdloanId::::get()) + } + /// Verify a set of signatures made with relay chain accounts + /// We are verifying all the signatures, and then counting + /// We could do something more efficient like count as we verify + /// In any of the cases the weight will need to account for all the signatures, + /// as we dont know beforehand whether they will be valid + fn verify_signatures( + proofs: Vec<(T::RelayChainAccountId, EthereumSignature)>, + reward_info: RewardInfo, + payload: Vec, + ) -> DispatchResult { + // The proofs should + // 1. be signed by contributors to this address, otherwise they are not counted + // 2. Signs a valid native identity + // 3. The sum of the valid proofs needs to be bigger than InsufficientNumberOfValidProofs + + // I use a map here for faster lookups + let mut voted: BTreeMap = BTreeMap::new(); + for (relay_account, signature) in proofs { + // We just count votes that we have not seen + if voted.get(&relay_account).is_none() { + // Maybe I should not error here? + ensure!( + reward_info.contributed_relay_addresses.contains(&relay_account), + Error::::NonContributedAddressProvided + ); + + // I am erroring here as I think it is good to know the reason in the single-case + // signature + ensure!( + signature.verify(payload.as_slice(), &relay_account.clone().into()), + Error::::InvalidClaimSignature + ); + voted.insert(relay_account, ()); + } + } + + // Ensure the votes are sufficient + ensure!( + Perbill::from_rational( + voted.len() as u32, + reward_info.contributed_relay_addresses.len() as u32 + ) >= T::RewardAddressRelayVoteThreshold::get(), + Error::::InsufficientNumberOfValidProofs + ); + Ok(()) + } + } + + #[pallet::error] + pub enum Error { + /// User trying to associate a native identity with a relay chain identity for posterior + /// reward claiming provided an already associated relay chain identity + AlreadyAssociated, + /// Trying to introduce a batch that goes beyond the limits of the funds + BatchBeyondFundPot, + /// First claim already done + FirstClaimAlreadyDone, + /// The contribution is not high enough to be eligible for rewards + RewardNotHighEnough, + /// User trying to associate a native identity with a relay chain identity for posterior + /// reward claiming provided a wrong signature + InvalidClaimSignature, + /// User trying to claim the first free reward provided the wrong signature + InvalidFreeClaimSignature, + /// User trying to claim an award did not have an claim associated with it. This may mean + /// they did not contribute to the crowdloan, or they have not yet associated a native id + /// with their contribution + NoAssociatedClaim, + /// User trying to claim rewards has already claimed all rewards associated with its + /// identity and contribution + RewardsAlreadyClaimed, + /// Reward vec has already been initialized + RewardVecAlreadyInitialized, + /// Reward vec has not yet been fully initialized + RewardVecNotFullyInitializedYet, + /// Rewards should match funds of the pallet + RewardsDoNotMatchFund, + /// Initialize_reward_vec received too many contributors + TooManyContributors, + /// Provided vesting period is not valid + VestingPeriodNonValid, + /// User provided a signature from a non-contributor relay account + NonContributedAddressProvided, + /// User submitted an unsifficient number of proofs to change the reward address + InsufficientNumberOfValidProofs, + /// The mint operation during claim has resulted in err. + /// This is expected when claiming less than existential desposit on a non-existent account + /// Please consider waiting until the EndVestingBlock to attempt this + ClaimingLessThanED, + /// Math overflow + MathOverflow, + /// Period not set + PeriodNotSet, + /// Trying to introduce a batch that goes beyond the limits of the funds + AllocationDoesNotMatch, + } + + #[pallet::storage] + #[pallet::getter(fn get_crowdloan_allocation)] + pub type CrowdloanAllocation = + StorageMap<_, Blake2_128Concat, u32, BalanceOf, ValueQuery>; + + /// Id of current crowdloan rewards distribution, automatically incremented by + /// [`Pallet::::complete_initialization`] + #[pallet::storage] + #[pallet::getter(fn get_crowdloan_id)] + pub type CrowdloanId = StorageValue<_, u32, ValueQuery>; + + #[pallet::storage] + #[pallet::getter(fn accounts_payable)] + pub type AccountsPayable = + StorageDoubleMap<_, Blake2_128Concat, u32, Blake2_128Concat, T::AccountId, RewardInfo>; + + #[pallet::storage] + #[pallet::getter(fn crowdloan_period)] + pub type CrowdloanPeriod = + StorageMap<_, Blake2_128Concat, u32, (T::VestingBlockNumber, T::VestingBlockNumber)>; + + #[pallet::storage] + #[pallet::getter(fn claimed_relay_chain_ids)] + pub type ClaimedRelayChainIds = + StorageDoubleMap<_, Blake2_128Concat, u32, Blake2_128Concat, T::RelayChainAccountId, ()>; + #[pallet::storage] + #[pallet::getter(fn unassociated_contributions)] + pub type UnassociatedContributions = StorageDoubleMap< + _, + Blake2_128Concat, + u32, + Blake2_128Concat, + T::RelayChainAccountId, + RewardInfo, + >; + #[pallet::storage] + #[pallet::getter(fn initialized)] + pub type Initialized = StorageValue<_, bool, ValueQuery, T::Initialized>; + + // #[pallet::storage] + // #[pallet::storage_prefix = "InitRelayBlock"] + // #[pallet::getter(fn init_vesting_block)] + // /// Vesting block height at the initialization of the pallet + // type InitVestingBlock = StorageValue<_, T::VestingBlockNumber, ValueQuery>; + // + // #[pallet::storage] + // #[pallet::storage_prefix = "EndRelayBlock"] + // #[pallet::getter(fn end_vesting_block)] + // /// Vesting block height at the initialization of the pallet + // type EndVestingBlock = StorageValue<_, T::VestingBlockNumber, ValueQuery>; + // + #[pallet::storage] + #[pallet::getter(fn init_reward_amount)] + /// Total initialized amount so far. We store this to make pallet funds == contributors reward + /// check easier and more efficient + pub(crate) type InitializedRewardAmount = + StorageMap<_, Blake2_128Concat, u32, BalanceOf, ValueQuery>; + + #[pallet::storage] + #[pallet::getter(fn total_contributors_by_id)] + /// Total number of contributors to aid hinting benchmarking + pub(crate) type TotalContributors = + StorageMap<_, Blake2_128Concat, u32, u32, ValueQuery>; + + #[pallet::event] + #[pallet::generate_deposit(fn deposit_event)] + pub enum Event { + /// The initial payment of InitializationPayment % was paid + InitialPaymentMade(T::AccountId, BalanceOf), + /// Someone has proven they made a contribution and associated a native identity with it. + /// Data is the relay account, native account and the total amount of _rewards_ that will be paid + NativeIdentityAssociated(T::RelayChainAccountId, T::AccountId, BalanceOf), + /// A contributor has claimed some rewards. + /// Data is the account getting paid and the amount of rewards paid. + RewardsPaid(T::AccountId, BalanceOf), + /// A contributor has updated the reward address. + RewardAddressUpdated(T::AccountId, T::AccountId), + /// When initializing the reward vec an already initialized account was found + InitializedAlreadyInitializedAccount( + T::RelayChainAccountId, + Option, + BalanceOf, + ), + /// When initializing the reward vec an already initialized account was found + InitializedAccountWithNotEnoughContribution( + T::RelayChainAccountId, + Option, + BalanceOf, + ), + } +} diff --git a/gasp-node/pallets/crowdloan-rewards/src/mock.rs b/gasp-node/pallets/crowdloan-rewards/src/mock.rs new file mode 100644 index 000000000..865c3f45a --- /dev/null +++ b/gasp-node/pallets/crowdloan-rewards/src/mock.rs @@ -0,0 +1,233 @@ +// Copyright 2019-2022 PureStake Inc. +// This file is part of Moonbeam. + +// Moonbeam 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. + +// Moonbeam 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 Moonbeam. If not, see . + +//! Test utilities +use crate::{self as pallet_crowdloan_rewards, Config}; +use frame_support::{ + construct_runtime, derive_impl, parameter_types, + traits::{Contains, Nothing, OnFinalize, OnInitialize, WithdrawReasons}, + PalletId, +}; +use frame_system::{pallet_prelude::BlockNumberFor, EnsureRoot}; +use orml_traits::parameter_type_with_key; +use sp_core::{ecdsa, Pair, H256}; +use sp_keystore::{testing::MemoryKeystore, KeystoreExt}; + +use sp_application_crypto::{ecdsa::Public, RuntimePublic}; +use sp_runtime::{ + account::AccountId20, + traits::{AccountIdConversion, BlakeTwo256, IdentityLookup}, + BuildStorage, Perbill, +}; +use sp_std::{ + convert::{From, TryInto}, + fmt::Write, +}; + +pub const MGA_TOKEN_ID: TokenId = 0; +pub(crate) type AccountId = u64; +pub(crate) type Balance = u128; +pub(crate) type TokenId = u32; +pub(crate) type Amount = i128; + +type Block = frame_system::mocking::MockBlock; + +construct_runtime!( + pub enum Test { + System: frame_system, + Tokens: orml_tokens, + Crowdloan: pallet_crowdloan_rewards, + Utility: pallet_utility, + Vesting: pallet_vesting_mangata, + } +); + +#[derive_impl(frame_system::config_preludes::TestDefaultConfig as frame_system::DefaultConfig)] +impl frame_system::Config for Test { + type Block = Block; +} + +parameter_type_with_key! { + pub ExistentialDeposits: |_currency_id: TokenId| -> Balance { + 0 + }; +} + +pub struct DustRemovalWhitelist; +impl Contains for DustRemovalWhitelist { + fn contains(a: &AccountId) -> bool { + *a == TreasuryAccount::get() + } +} + +parameter_types! { + pub const TreasuryPalletId: PalletId = PalletId(*b"py/trsry"); + pub TreasuryAccount: AccountId = TreasuryPalletId::get().into_account_truncating(); + pub const MaxLocks: u32 = 50; + pub const MgaTokenId: TokenId = MGA_TOKEN_ID; +} + +impl orml_tokens::Config for Test { + type RuntimeEvent = RuntimeEvent; + type Balance = Balance; + type Amount = Amount; + type CurrencyId = TokenId; + type WeightInfo = (); + type ExistentialDeposits = ExistentialDeposits; + type MaxLocks = MaxLocks; + type DustRemovalWhitelist = DustRemovalWhitelist; + type CurrencyHooks = (); + type MaxReserves = (); + type ReserveIdentifier = [u8; 8]; + type NontransferableTokens = Nothing; + type NontransferableTokensAllowList = Nothing; +} + +parameter_types! { + pub const MinVestedTransfer: Balance = 0; + pub UnvestedFundsAllowedWithdrawReasons: WithdrawReasons = + WithdrawReasons::except(WithdrawReasons::TRANSFER | WithdrawReasons::RESERVE); +} + +impl pallet_vesting_mangata::Config for Test { + type RuntimeEvent = RuntimeEvent; + type Tokens = orml_tokens::MultiTokenCurrencyAdapter; + type BlockNumberToBalance = sp_runtime::traits::ConvertInto; + type MinVestedTransfer = MinVestedTransfer; + type WeightInfo = pallet_vesting_mangata::weights::SubstrateWeight; + type UnvestedFundsAllowedWithdrawReasons = UnvestedFundsAllowedWithdrawReasons; + type BlockNumberProvider = System; + // `VestingInfo` encode length is 36bytes. 28 schedules gets encoded as 1009 bytes, which is the + // highest number of schedules that encodes less than 2^10. + const MAX_VESTING_SCHEDULES: u32 = 28; +} + +parameter_types! { + pub const TestMaxInitContributors: u32 = 8; + pub const TestMinimumReward: u128 = 0; + pub const TestInitialized: bool = false; + pub const TestInitializationPayment: Perbill = Perbill::from_percent(20); + pub const TestRewardAddressRelayVoteThreshold: Perbill = Perbill::from_percent(50); + pub const TestSigantureNetworkIdentifier: &'static [u8] = b"test-"; +} + +impl Config for Test { + type RuntimeEvent = RuntimeEvent; + type Initialized = TestInitialized; + type InitializationPayment = TestInitializationPayment; + type MaxInitContributors = TestMaxInitContributors; + type MinimumReward = TestMinimumReward; + type NativeTokenId = MgaTokenId; + type Tokens = orml_tokens::MultiTokenCurrencyAdapter; + type RelayChainAccountId = [u8; 20]; + type RewardAddressRelayVoteThreshold = TestRewardAddressRelayVoteThreshold; + // The origin that is allowed to associate the reward + type RewardAddressAssociateOrigin = EnsureRoot; + // The origin that is allowed to change the reward + type RewardAddressChangeOrigin = EnsureRoot; + type SignatureNetworkIdentifier = TestSigantureNetworkIdentifier; + type VestingBlockNumber = BlockNumberFor; + type VestingBlockProvider = System; + type VestingProvider = Vesting; + type WeightInfo = (); +} + +impl pallet_utility::Config for Test { + type RuntimeEvent = RuntimeEvent; + type RuntimeCall = RuntimeCall; + type WeightInfo = (); + type PalletsOrigin = OriginCaller; +} + +fn genesis() -> sp_io::TestExternalities { + let mut storage = frame_system::GenesisConfig::::default().build_storage().unwrap(); + + orml_tokens::GenesisConfig:: { + tokens_endowment: vec![(0u64, 0u32, 2_000_000_000)], + created_tokens_for_staking: Default::default(), + } + .assimilate_storage(&mut storage) + .expect("Tokens storage can be assimilated"); + + let mut ext = sp_io::TestExternalities::from(storage); + ext.register_extension(KeystoreExt::new(MemoryKeystore::new())); + ext.execute_with(|| { + System::set_block_number(1); + Crowdloan::set_crowdloan_allocation(RuntimeOrigin::root(), 2500u128).unwrap(); + }); + ext +} + +pub type UtilityCall = pallet_utility::Call; + +pub(crate) fn get_ecdsa_pairs(num: u32) -> Vec { + let seed: u128 = 12345678901234567890123456789012; + let mut pairs = Vec::new(); + for i in 0..num { + pairs.push({ + let mut buffer = String::new(); + let _ = write!(&mut buffer, "//{}", seed + i as u128); + Public::generate_pair(sp_core::testing::ECDSA, Some(buffer.into_bytes())) + }) + } + pairs +} + +pub(crate) fn empty() -> sp_io::TestExternalities { + genesis() +} + +pub(crate) fn events() -> Vec> { + System::events() + .into_iter() + .map(|r| r.event) + .filter_map(|e| if let RuntimeEvent::Crowdloan(inner) = e { Some(inner) } else { None }) + .collect::>() +} + +pub(crate) fn batch_events() -> Vec { + System::events() + .into_iter() + .map(|r| r.event) + .filter_map(|e| if let RuntimeEvent::Utility(inner) = e { Some(inner) } else { None }) + .collect::>() +} + +// pub(crate) fn roll_to(n: u64) { +// let mut current_block_number = System::block_number(); +// while current_block_number < n { +// Crowdloan::on_finalize(System::block_number()); +// Tokens::on_finalize(System::block_number()); +// System::on_finalize(System::block_number()); +// System::set_block_number(current_block_number); +// current_block_number = current_block_number.saturating_add(1); +// System::on_initialize(System::block_number()); +// Tokens::on_initialize(System::block_number()); +// Crowdloan::on_initialize(System::block_number()); +// } +// } + +pub(crate) fn roll_to(n: u64) { + while System::block_number() < n { + Crowdloan::on_finalize(System::block_number()); + Tokens::on_finalize(System::block_number()); + System::on_finalize(System::block_number()); + System::set_block_number(System::block_number() + 1); + System::on_initialize(System::block_number()); + Tokens::on_initialize(System::block_number()); + Crowdloan::on_initialize(System::block_number()); + } +} diff --git a/gasp-node/pallets/crowdloan-rewards/src/tests.rs b/gasp-node/pallets/crowdloan-rewards/src/tests.rs new file mode 100644 index 000000000..295ae1085 --- /dev/null +++ b/gasp-node/pallets/crowdloan-rewards/src/tests.rs @@ -0,0 +1,1361 @@ +// Copyright 2019-2022 PureStake Inc. +// This file is part of Moonbeam. + +// Moonbeam 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. + +// Moonbeam 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 Moonbeam. If not, see . + +//! Unit testing + +use crate::*; +use frame_support::traits::tokens::currency::MultiTokenCurrency; + +use codec::Encode; +use frame_support::{assert_err, assert_noop, assert_ok, traits::MultiTokenVestingSchedule}; +use mock::*; +use sp_application_crypto::RuntimePublic; +use sp_core::Pair; +use sp_runtime::{account::EthereumSignature, traits::Dispatchable, DispatchError, ModuleError}; + +type TokensOf = ::Tokens; + +// Constant that reflects the desired vesting period for the tests +// Most tests complete initialization passing initRelayBlock + VESTING as the endRelayBlock +const VESTING: u64 = 8; + +const ALICE: AccountId = 1; +const ALICE_NEW: AccountId = 11; +const BOB: AccountId = 2; +const BOB_NEW: AccountId = 12; + +fn transferable_balance(who: &u64, token_id: TokenId) -> u128 { + TokensOf::::free_balance(token_id, who) - Vesting::vesting_balance(who, token_id).unwrap() +} + +#[test] +fn geneses() { + empty().execute_with(|| { + assert!(System::events().is_empty()); + // Insert contributors + let pairs = get_ecdsa_pairs(3); + let init_block = 1u64; + assert_ok!(Crowdloan::initialize_reward_vec( + RuntimeOrigin::root(), + vec![ + ([1u8; 20], Some(1), 500u32.into()), + ([2u8; 20], Some(2), 500u32.into()), + (AccountId20::from(pairs[0]).into(), None, 500u32.into()), + (AccountId20::from(pairs[1]).into(), None, 500u32.into()), + (AccountId20::from(pairs[2]).into(), None, 500u32.into()) + ] + )); + + assert_ok!(Crowdloan::complete_initialization( + RuntimeOrigin::root(), + init_block, + init_block + VESTING + )); + assert_eq!(Crowdloan::total_contributors(), 5); + + // accounts_payable + assert!(Crowdloan::accounts_payable(0, 1).is_some()); + assert!(Crowdloan::accounts_payable(0, 2).is_some()); + assert!(Crowdloan::accounts_payable(0, 3).is_none()); + assert!(Crowdloan::accounts_payable(0, 4).is_none()); + assert!(Crowdloan::accounts_payable(0, 5).is_none()); + + // claimed address existence + assert!(Crowdloan::claimed_relay_chain_ids(0, [1u8; 20]).is_some()); + assert!(Crowdloan::claimed_relay_chain_ids(0, [2u8; 20]).is_some()); + assert!(Crowdloan::claimed_relay_chain_ids( + 0, + <[u8; 20]>::from(AccountId20::from(pairs[0])) + ) + .is_none()); + assert!(Crowdloan::claimed_relay_chain_ids( + 0, + <[u8; 20]>::from(AccountId20::from(pairs[1])) + ) + .is_none()); + assert!(Crowdloan::claimed_relay_chain_ids( + 0, + <[u8; 20]>::from(AccountId20::from(pairs[2])) + ) + .is_none()); + + // unassociated_contributions + assert!(Crowdloan::unassociated_contributions(0, [1u8; 20]).is_none()); + assert!(Crowdloan::unassociated_contributions(0, [2u8; 20]).is_none()); + assert!(Crowdloan::unassociated_contributions( + 0, + <[u8; 20]>::from(AccountId20::from(pairs[0])) + ) + .is_some()); + assert!(Crowdloan::unassociated_contributions( + 0, + <[u8; 20]>::from(AccountId20::from(pairs[1])) + ) + .is_some()); + assert!(Crowdloan::unassociated_contributions( + 0, + <[u8; 20]>::from(AccountId20::from(pairs[2])) + ) + .is_some()); + }); +} + +#[test] +fn proving_assignation_works() { + empty().execute_with(|| { + let pairs = get_ecdsa_pairs(3); + let mut payload = WRAPPED_BYTES_PREFIX.to_vec(); + payload.append(&mut TestSigantureNetworkIdentifier::get().to_vec()); + payload.append(&mut 3u64.encode()); + payload.append(&mut WRAPPED_BYTES_POSTFIX.to_vec()); + let signature: EthereumSignature = + pairs[0].sign(sp_core::testing::ECDSA, &payload).unwrap().into(); + let alread_associated_signature: EthereumSignature = + pairs[0].sign(sp_core::testing::ECDSA, &1u64.encode()).unwrap().into(); + // Insert contributors + let pairs = get_ecdsa_pairs(3); + let init_block = 1u64; + assert_ok!(Crowdloan::initialize_reward_vec( + RuntimeOrigin::root(), + vec![ + ([1u8; 20], Some(1), 500u32.into()), + ([2u8; 20], Some(2), 500u32.into()), + (AccountId20::from(pairs[0]).into(), None, 500u32.into()), + (AccountId20::from(pairs[1]).into(), None, 500u32.into()), + (AccountId20::from(pairs[2]).into(), None, 500u32.into()) + ], + )); + assert_ok!(Crowdloan::complete_initialization( + RuntimeOrigin::root(), + init_block, + init_block + VESTING + )); + // 4 is not payable first + assert!(Crowdloan::accounts_payable(0, 3).is_none()); + assert_eq!( + Crowdloan::accounts_payable(0, 1).unwrap().contributed_relay_addresses, + vec![[1u8; 20]] + ); + + roll_to(4); + // Signature is wrong, prove fails + assert_noop!( + Crowdloan::associate_native_identity( + RuntimeOrigin::root(), + 4, + AccountId20::from(pairs[0]).into(), + signature.clone() + ), + Error::::InvalidClaimSignature + ); + + // Signature is right, but address already claimed + assert_noop!( + Crowdloan::associate_native_identity( + RuntimeOrigin::root(), + 1, + AccountId20::from(pairs[0]).into(), + alread_associated_signature + ), + Error::::AlreadyAssociated + ); + + // Signature is right, prove passes + assert_ok!(Crowdloan::associate_native_identity( + RuntimeOrigin::root(), + 3, + AccountId20::from(pairs[0]).into(), + signature.clone() + )); + + // Signature is right, but relay address is no longer on unassociated + assert_noop!( + Crowdloan::associate_native_identity( + RuntimeOrigin::root(), + 3, + AccountId20::from(pairs[0]).into(), + signature + ), + Error::::NoAssociatedClaim + ); + + // now three is payable + assert!(Crowdloan::accounts_payable(0, 3).is_some()); + assert_eq!( + Crowdloan::accounts_payable(0, 3).unwrap().contributed_relay_addresses, + vec![<[u8; 20]>::from(AccountId20::from(pairs[0]))] + ); + + assert!(Crowdloan::unassociated_contributions( + 0, + <[u8; 20]>::from(AccountId20::from(pairs[0])) + ) + .is_none()); + assert!(Crowdloan::claimed_relay_chain_ids( + 0, + <[u8; 20]>::from(AccountId20::from(pairs[0])) + ) + .is_some()); + + let expected = vec![crate::Event::NativeIdentityAssociated( + AccountId20::from(pairs[0]).into(), + 3, + 500, + )]; + assert_eq!(events(), expected); + }); +} + +#[test] +fn initializing_multi_relay_to_single_native_address_works() { + empty().execute_with(|| { + // Insert contributors + let pairs = get_ecdsa_pairs(3); + // The init relay block gets inserted + let init_block = 1u64; + assert_ok!(Crowdloan::initialize_reward_vec( + RuntimeOrigin::root(), + vec![ + ([1u8; 20], Some(1), 500u32.into()), + ([2u8; 20], Some(1), 500u32.into()), + (AccountId20::from(pairs[0]).into(), None, 500u32.into()), + (AccountId20::from(pairs[1]).into(), None, 500u32.into()), + (AccountId20::from(pairs[2]).into(), None, 500u32.into()) + ] + )); + assert_ok!(Crowdloan::complete_initialization( + RuntimeOrigin::root(), + init_block, + init_block + VESTING + )); + // 1 is payable + assert!(Crowdloan::accounts_payable(0, 1).is_some()); + assert_eq!( + Crowdloan::accounts_payable(0, 1).unwrap().contributed_relay_addresses, + vec![[1u8; 20], [2u8; 20]] + ); + + roll_to(3); + assert_ok!(Crowdloan::claim(RuntimeOrigin::signed(1), None)); + assert_eq!(Crowdloan::accounts_payable(0, 1).unwrap().claimed_reward, 1000); + assert_noop!( + Crowdloan::claim(RuntimeOrigin::signed(3), None), + Error::::NoAssociatedClaim + ); + + let expected = vec![crate::Event::RewardsPaid(1, 1000)]; + assert_eq!(events(), expected); + }); +} + +#[test] +fn paying_works_step_by_step() { + empty().execute_with(|| { + // Insert contributors + let pairs = get_ecdsa_pairs(3); + // The init relay block gets inserted + let init_block = 1u64; + assert_ok!(Crowdloan::initialize_reward_vec( + RuntimeOrigin::root(), + vec![ + ([1u8; 20], Some(1), 500u32.into()), + ([2u8; 20], Some(2), 500u32.into()), + (AccountId20::from(pairs[0]).into(), None, 500u32.into()), + (AccountId20::from(pairs[1]).into(), None, 500u32.into()), + (AccountId20::from(pairs[2]).into(), None, 500u32.into()) + ] + )); + assert_ok!(Crowdloan::complete_initialization( + RuntimeOrigin::root(), + init_block, + init_block + VESTING + )); + // 1 is payable + assert!(Crowdloan::accounts_payable(0, 1).is_some()); + assert_ok!(Crowdloan::claim(RuntimeOrigin::signed(1), None)); + assert_eq!(Crowdloan::accounts_payable(0, 1).unwrap().claimed_reward, 500); + assert_eq!(transferable_balance(&1, 0), 100); + + roll_to(3); + + assert_eq!(transferable_balance(&1, 0), 200); + assert_noop!( + Crowdloan::claim(RuntimeOrigin::signed(3), None), + Error::::NoAssociatedClaim + ); + roll_to(4); + assert_eq!(transferable_balance(&1, 0), 250); + roll_to(5); + assert_eq!(transferable_balance(&1, 0), 300); + roll_to(6); + assert_eq!(transferable_balance(&1, 0), 350); + roll_to(7); + assert_eq!(transferable_balance(&1, 0), 400); + roll_to(8); + assert_eq!(transferable_balance(&1, 0), 450); + roll_to(9); + assert_eq!(transferable_balance(&1, 0), 500); + roll_to(10); + assert_noop!( + Crowdloan::claim(RuntimeOrigin::signed(1), None), + Error::::RewardsAlreadyClaimed + ); + + let expected = vec![crate::Event::RewardsPaid(1, 500)]; + assert_eq!(events(), expected); + }); +} + +#[test] +fn paying_works_after_unclaimed_period() { + empty().execute_with(|| { + // Insert contributors + let pairs = get_ecdsa_pairs(3); + // The init relay block gets inserted + let init_block = 1u64; + assert_ok!(Crowdloan::initialize_reward_vec( + RuntimeOrigin::root(), + vec![ + ([1u8; 20], Some(1), 500u32.into()), + ([2u8; 20], Some(2), 500u32.into()), + (AccountId20::from(pairs[0]).into(), None, 500u32.into()), + (AccountId20::from(pairs[1]).into(), None, 500u32.into()), + (AccountId20::from(pairs[2]).into(), None, 500u32.into()) + ] + )); + assert_ok!(Crowdloan::complete_initialization( + RuntimeOrigin::root(), + init_block, + init_block + VESTING + )); + + // 1 is payable + assert!(Crowdloan::accounts_payable(0, 1).is_some()); + roll_to(3); + assert_ok!(Crowdloan::claim(RuntimeOrigin::signed(1), None)); + assert_eq!(Crowdloan::accounts_payable(0, 1).unwrap().claimed_reward, 500); + assert_noop!( + Crowdloan::claim(RuntimeOrigin::signed(3), None), + Error::::NoAssociatedClaim + ); + roll_to(4); + // assert_ok!(Crowdloan::claim(RuntimeOrigin::signed(1), None)); + assert_eq!(transferable_balance(&1, 0), 250); + roll_to(5); + // assert_ok!(Crowdloan::claim(RuntimeOrigin::signed(1), None)); + assert_eq!(transferable_balance(&1, 0), 300); + roll_to(6); + // assert_ok!(Crowdloan::claim(RuntimeOrigin::signed(1), None)); + assert_eq!(transferable_balance(&1, 0), 350); + roll_to(10); + // assert_ok!(Crowdloan::claim(RuntimeOrigin::signed(1), None)); + assert_eq!(transferable_balance(&1, 0), 500); + roll_to(329); + assert_noop!( + Crowdloan::claim(RuntimeOrigin::signed(1), None), + Error::::RewardsAlreadyClaimed + ); + + let expected = vec![crate::Event::RewardsPaid(1, 500)]; + assert_eq!(events(), expected); + }); +} + +#[test] +fn paying_late_joiner_works() { + empty().execute_with(|| { + let pairs = get_ecdsa_pairs(3); + let mut payload = WRAPPED_BYTES_PREFIX.to_vec(); + payload.append(&mut TestSigantureNetworkIdentifier::get().to_vec()); + payload.append(&mut 3u64.encode()); + payload.append(&mut WRAPPED_BYTES_POSTFIX.to_vec()); + let signature: EthereumSignature = + pairs[0].sign(sp_core::testing::ECDSA, &payload).unwrap().into(); + // Insert contributors + let pairs = get_ecdsa_pairs(3); + let init_block = 1u64; + assert_ok!(Crowdloan::initialize_reward_vec( + RuntimeOrigin::root(), + vec![ + ([1u8; 20], Some(1), 500u32.into()), + ([2u8; 20], Some(2), 500u32.into()), + (AccountId20::from(pairs[0]).into(), None, 500u32.into()), + (AccountId20::from(pairs[1]).into(), None, 500u32.into()), + (AccountId20::from(pairs[2]).into(), None, 500u32.into()) + ] + )); + assert_ok!(Crowdloan::complete_initialization( + RuntimeOrigin::root(), + init_block, + init_block + VESTING + )); + + roll_to(12); + assert_ok!(Crowdloan::associate_native_identity( + RuntimeOrigin::root(), + 3, + AccountId20::from(pairs[0]).into(), + signature.clone() + )); + assert_ok!(Crowdloan::claim(RuntimeOrigin::signed(3), None)); + assert_eq!(Crowdloan::accounts_payable(0, 3).unwrap().claimed_reward, 500); + let expected = vec![ + crate::Event::NativeIdentityAssociated(AccountId20::from(pairs[0]).into(), 3, 500), + crate::Event::RewardsPaid(3, 500), + ]; + assert_eq!(events(), expected); + }); +} + +#[test] +fn update_address_works() { + empty().execute_with(|| { + // Insert contributors + let pairs = get_ecdsa_pairs(3); + // The init relay block gets inserted + let init_block = 1u64; + assert_ok!(Crowdloan::initialize_reward_vec( + RuntimeOrigin::root(), + vec![ + ([1u8; 20], Some(1), 500u32.into()), + ([2u8; 20], Some(2), 500u32.into()), + (AccountId20::from(pairs[0]).into(), None, 500u32.into()), + (AccountId20::from(pairs[1]).into(), None, 500u32.into()), + (AccountId20::from(pairs[2]).into(), None, 500u32.into()) + ] + )); + assert_ok!(Crowdloan::complete_initialization( + RuntimeOrigin::root(), + init_block, + init_block + VESTING + )); + + roll_to(3); + assert_ok!(Crowdloan::claim(RuntimeOrigin::signed(1), None)); + assert_noop!( + Crowdloan::claim(RuntimeOrigin::signed(8), None), + Error::::NoAssociatedClaim + ); + assert_ok!(Crowdloan::update_reward_address(RuntimeOrigin::signed(1), 8, None,)); + assert_eq!(transferable_balance(&1, 0), 200); + roll_to(5); + + assert_eq!(transferable_balance(&1, 0), 300); + // The initial payment is not + let expected = + vec![crate::Event::RewardsPaid(1, 500), crate::Event::RewardAddressUpdated(1, 8)]; + assert_eq!(events(), expected); + }); +} + +#[test] +fn update_address_with_existing_address_fails() { + empty().execute_with(|| { + // Insert contributors + let pairs = get_ecdsa_pairs(3); + // The init relay block gets inserted + roll_to(2); + let init_block = 1u64; + assert_ok!(Crowdloan::initialize_reward_vec( + RuntimeOrigin::root(), + vec![ + ([1u8; 20], Some(1), 500u32.into()), + ([2u8; 20], Some(2), 500u32.into()), + (AccountId20::from(pairs[0]).into(), None, 500u32.into()), + (AccountId20::from(pairs[1]).into(), None, 500u32.into()), + (AccountId20::from(pairs[2]).into(), None, 500u32.into()) + ] + )); + assert_ok!(Crowdloan::complete_initialization( + RuntimeOrigin::root(), + init_block, + init_block + VESTING + )); + + roll_to(4); + assert_ok!(Crowdloan::claim(RuntimeOrigin::signed(1), None)); + assert_ok!(Crowdloan::claim(RuntimeOrigin::signed(2), None)); + assert_noop!( + Crowdloan::update_reward_address(RuntimeOrigin::signed(1), 2, None), + Error::::AlreadyAssociated + ); + }); +} + +#[test] +fn update_address_with_existing_with_multi_address_works() { + empty().execute_with(|| { + // Insert contributors + let pairs = get_ecdsa_pairs(3); + // The init relay block gets inserted + let init_block = 1u64; + assert_ok!(Crowdloan::initialize_reward_vec( + RuntimeOrigin::root(), + vec![ + ([1u8; 20], Some(1), 500u32.into()), + ([2u8; 20], Some(1), 500u32.into()), + (AccountId20::from(pairs[0]).into(), None, 500u32.into()), + (AccountId20::from(pairs[1]).into(), None, 500u32.into()), + (AccountId20::from(pairs[2]).into(), None, 500u32.into()) + ] + )); + assert_ok!(Crowdloan::complete_initialization( + RuntimeOrigin::root(), + init_block, + init_block + VESTING + )); + + roll_to(3); + assert_ok!(Crowdloan::claim(RuntimeOrigin::signed(1), None)); + + // We make sure all rewards go to the new address + assert_ok!(Crowdloan::update_reward_address(RuntimeOrigin::signed(1), 2, None)); + assert_eq!(Crowdloan::accounts_payable(0, 2).unwrap().claimed_reward, 1000); + assert_eq!(Crowdloan::accounts_payable(0, 2).unwrap().total_reward, 1000); + + assert_noop!( + Crowdloan::claim(RuntimeOrigin::signed(1), None), + Error::::NoAssociatedClaim + ); + }); +} + +#[test] +fn initialize_new_addresses() { + empty().execute_with(|| { + // The init relay block gets inserted + roll_to(2); + // Insert contributors + let pairs = get_ecdsa_pairs(3); + let init_block = 1u64; + assert_ok!(Crowdloan::initialize_reward_vec( + RuntimeOrigin::root(), + vec![ + ([1u8; 20], Some(1), 500u32.into()), + ([2u8; 20], Some(2), 500u32.into()), + (AccountId20::from(pairs[0]).into(), None, 500u32.into()), + (AccountId20::from(pairs[1]).into(), None, 500u32.into()), + (AccountId20::from(pairs[2]).into(), None, 500u32.into()) + ] + )); + assert_ok!(Crowdloan::complete_initialization( + RuntimeOrigin::root(), + init_block, + init_block + VESTING + )); + + assert!(Crowdloan::initialized()); + + roll_to(4); + assert_noop!( + Crowdloan::initialize_reward_vec( + RuntimeOrigin::root(), + vec![([1u8; 20], Some(1), 500u32.into())] + ), + Error::::RewardVecAlreadyInitialized, + ); + + assert_noop!( + Crowdloan::complete_initialization( + RuntimeOrigin::root(), + init_block, + init_block + VESTING * 2 + ), + Error::::RewardVecAlreadyInitialized, + ); + }); +} + +#[test] +fn initialize_new_addresses_not_matching_funds() { + empty().execute_with(|| { + // The init relay block gets inserted + roll_to(2); + // Insert contributors + let pairs = get_ecdsa_pairs(2); + let init_block = 1u64; + // Total supply is 2500.Lets ensure inserting 2495 is not working. + assert_ok!(Crowdloan::initialize_reward_vec( + RuntimeOrigin::root(), + vec![ + ([1u8; 20], Some(1), 500u32.into()), + ([2u8; 20], Some(2), 500u32.into()), + (AccountId20::from(pairs[0]).into(), None, 500u32.into()), + (AccountId20::from(pairs[1]).into(), None, 995u32.into()), + ] + )); + assert_noop!( + Crowdloan::complete_initialization( + RuntimeOrigin::root(), + init_block, + init_block + VESTING + ), + Error::::RewardsDoNotMatchFund + ); + }); +} + +#[test] +fn initialize_new_addresses_with_batch() { + empty().execute_with(|| { + // This time should succeed trully + roll_to(10); + let init_block = 1u64; + assert_ok!(mock::RuntimeCall::Utility(UtilityCall::batch_all { + calls: vec![ + mock::RuntimeCall::Crowdloan(crate::Call::initialize_reward_vec { + rewards: vec![([4u8; 20], Some(3), 1250)], + }), + mock::RuntimeCall::Crowdloan(crate::Call::initialize_reward_vec { + rewards: vec![([5u8; 20], Some(1), 1250)], + }) + ] + }) + .dispatch(RuntimeOrigin::root())); + assert_ok!(Crowdloan::complete_initialization( + RuntimeOrigin::root(), + init_block, + init_block + VESTING + )); + assert_eq!(Crowdloan::total_contributors(), 2); + // Verify that the second ending block provider had no effect + assert_eq!(Crowdloan::crowdloan_period(0), Some((init_block, init_block + VESTING))); + + // Batch calls always succeed. We just need to check the inner event + assert_ok!(mock::RuntimeCall::Utility(UtilityCall::batch { + calls: vec![mock::RuntimeCall::Crowdloan(crate::Call::initialize_reward_vec { + rewards: vec![([4u8; 20], Some(3), 500)] + })] + }) + .dispatch(RuntimeOrigin::root())); + + let expected = vec![ + pallet_utility::Event::ItemCompleted, + pallet_utility::Event::ItemCompleted, + pallet_utility::Event::BatchCompleted, + pallet_utility::Event::BatchInterrupted { + index: 0, + error: DispatchError::Module(ModuleError { + index: 2, + error: [8, 0, 0, 0], + message: None, + }), + }, + ]; + assert_eq!(batch_events(), expected); + }); +} + +#[test] +fn floating_point_arithmetic_works() { + empty().execute_with(|| { + // The init relay block gets inserted + let init_block = 1u64; + assert_ok!(mock::RuntimeCall::Utility(UtilityCall::batch_all { + calls: vec![ + mock::RuntimeCall::Crowdloan(crate::Call::initialize_reward_vec { + rewards: vec![([4u8; 20], Some(1), 1190)] + }), + mock::RuntimeCall::Crowdloan(crate::Call::initialize_reward_vec { + rewards: vec![([5u8; 20], Some(2), 1185)] + }), + // We will work with this. This has 100/8=12.5 payable per block + mock::RuntimeCall::Crowdloan(crate::Call::initialize_reward_vec { + rewards: vec![([3u8; 20], Some(3), 125)] + }) + ] + }) + .dispatch(RuntimeOrigin::root())); + assert_ok!(Crowdloan::complete_initialization( + RuntimeOrigin::root(), + init_block, + init_block + VESTING + )); + assert_eq!(Crowdloan::total_contributors(), 3); + + assert_eq!(Crowdloan::accounts_payable(0, 3).unwrap().claimed_reward, 0); + + assert_ok!(Crowdloan::claim(RuntimeOrigin::signed(3), None)); + + let expected = vec![crate::Event::RewardsPaid(3, 125)]; + assert_eq!(events(), expected); + }); +} + +#[test] +fn test_initialization_errors() { + empty().execute_with(|| { + // The init relay block gets inserted + roll_to(2); + let init_block = 1u64; + + let pot = Crowdloan::get_crowdloan_allocation(0); + + // Too many contributors + assert_noop!( + Crowdloan::initialize_reward_vec( + RuntimeOrigin::root(), + vec![ + ([1u8; 20], Some(1), 1), + ([2u8; 20], Some(2), 1), + ([3u8; 20], Some(3), 1), + ([4u8; 20], Some(4), 1), + ([5u8; 20], Some(5), 1), + ([6u8; 20], Some(6), 1), + ([7u8; 20], Some(7), 1), + ([8u8; 20], Some(8), 1), + ([9u8; 20], Some(9), 1) + ] + ), + Error::::TooManyContributors + ); + + // Go beyond fund pot + assert_noop!( + Crowdloan::initialize_reward_vec( + RuntimeOrigin::root(), + vec![([1u8; 20], Some(1), pot + 1)] + ), + Error::::BatchBeyondFundPot + ); + + // Dont fill rewards + assert_ok!(Crowdloan::initialize_reward_vec( + RuntimeOrigin::root(), + vec![([1u8; 20], Some(1), pot - 1)] + )); + + // Fill rewards + assert_ok!(Crowdloan::initialize_reward_vec( + RuntimeOrigin::root(), + vec![([2u8; 20], Some(2), 1)] + )); + + // Insert a non-valid vesting period + assert_noop!( + Crowdloan::complete_initialization(RuntimeOrigin::root(), init_block, init_block), + Error::::VestingPeriodNonValid + ); + + // Cannot claim if we dont complete initialization + assert_noop!( + Crowdloan::claim(RuntimeOrigin::signed(1), None), + Error::::RewardVecNotFullyInitializedYet + ); + // Complete + assert_ok!(Crowdloan::complete_initialization( + RuntimeOrigin::root(), + init_block, + init_block + VESTING + )); + + // Cannot initialize again + assert_noop!( + Crowdloan::complete_initialization(RuntimeOrigin::root(), init_block, init_block), + Error::::RewardVecAlreadyInitialized + ); + }); +} + +#[test] +fn test_relay_signatures_can_change_reward_addresses() { + empty().execute_with(|| { + // 5 relay keys + let pairs = get_ecdsa_pairs(5); + + // The init relay block gets inserted + roll_to(2); + let init_block = 1u64; + + // We will have all pointint to the same reward account + assert_ok!(Crowdloan::initialize_reward_vec( + RuntimeOrigin::root(), + vec![ + (AccountId20::from(pairs[0]).into(), Some(1), 500u32.into()), + (AccountId20::from(pairs[1]).into(), Some(1), 500u32.into()), + (AccountId20::from(pairs[2]).into(), Some(1), 500u32.into()), + (AccountId20::from(pairs[3]).into(), Some(1), 500u32.into()), + (AccountId20::from(pairs[4]).into(), Some(1), 500u32.into()) + ], + )); + + // Complete + assert_ok!(Crowdloan::complete_initialization( + RuntimeOrigin::root(), + init_block, + init_block + VESTING + )); + + let reward_info = Crowdloan::accounts_payable(0, 1).unwrap(); + + // We should have all of them as contributors + for pair in pairs.clone() { + assert!(reward_info + .contributed_relay_addresses + .contains(&AccountId20::from(pair).into())) + } + + // Threshold is set to 50%, so we need at least 3 votes to pass + // Let's make sure that we dont pass with 2 + let mut payload = WRAPPED_BYTES_PREFIX.to_vec(); + payload.append(&mut TestSigantureNetworkIdentifier::get().to_vec()); + payload.append(&mut 2u64.encode()); + payload.append(&mut 1u64.encode()); + payload.append(&mut WRAPPED_BYTES_POSTFIX.to_vec()); + + let mut insufficient_proofs: Vec<([u8; 20], EthereumSignature)> = vec![]; + for i in 0..2 { + insufficient_proofs.push(( + AccountId20::from(pairs[i]).into(), + pairs[i].sign(sp_core::testing::ECDSA, &payload).unwrap().into(), + )); + } + + // Not sufficient proofs presented + assert_noop!( + Crowdloan::change_association_with_relay_keys( + RuntimeOrigin::root(), + 2, + 1, + insufficient_proofs.clone() + ), + Error::::InsufficientNumberOfValidProofs + ); + + // With three votes we should passs + let mut sufficient_proofs = insufficient_proofs.clone(); + + // We push one more + sufficient_proofs.push(( + AccountId20::from(pairs[2]).into(), + pairs[2].sign(sp_core::testing::ECDSA, &payload).unwrap().into(), + )); + + // This time should pass + assert_ok!(Crowdloan::change_association_with_relay_keys( + RuntimeOrigin::root(), + 2, + 1, + sufficient_proofs.clone() + )); + + // 1 should no longer be payable + assert!(Crowdloan::accounts_payable(0, 1).is_none()); + + // 2 should be now payable + let reward_info_2 = Crowdloan::accounts_payable(0, 2).unwrap(); + + // The reward info should be identical + assert_eq!(reward_info, reward_info_2); + }); +} + +#[test] +fn test_restart_crowdloan() { + empty().execute_with(|| { + // 5 relay keys + let pairs = get_ecdsa_pairs(2); + + // The init relay block gets inserted + roll_to(2); + let init_block = 1u64; + + // We will have all pointint to the same reward account + assert_ok!(Crowdloan::initialize_reward_vec( + RuntimeOrigin::root(), + vec![ + (AccountId20::from(pairs[0]).into(), Some(1), 2000u32.into()), + (AccountId20::from(pairs[1]).into(), Some(2), 500u32.into()), + ], + )); + + // Complete + assert_ok!(Crowdloan::complete_initialization( + RuntimeOrigin::root(), + init_block, + init_block + VESTING + )); + + assert!(Crowdloan::accounts_payable(0, 1).is_some()); + assert!(Crowdloan::accounts_payable(0, 2).is_some()); + + assert_ok!(Crowdloan::claim(RuntimeOrigin::signed(1), None)); + + assert_ok!(Crowdloan::set_crowdloan_allocation(RuntimeOrigin::root(), 2500u128)); + assert_eq!(Crowdloan::get_crowdloan_id(), 1); + + assert_ok!(Crowdloan::initialize_reward_vec( + RuntimeOrigin::root(), + vec![(AccountId20::from(pairs[0]).into(), Some(3), 2500u32.into()),], + )); + + assert_err!( + Crowdloan::claim(RuntimeOrigin::signed(3), Some(1)), + Error::::RewardVecNotFullyInitializedYet + ); + + assert_ok!(Crowdloan::complete_initialization(RuntimeOrigin::root(), 100, 200)); + + assert_ok!(Crowdloan::claim(RuntimeOrigin::signed(3), Some(1))); + }); +} + +#[test] +fn test_claim_rewards_from_consecutive_crowdloans_with_different_schedules() { + empty().execute_with(|| { + let pairs = get_ecdsa_pairs(2); + let first_crowdloan_period = (1u64, 9u64); + let second_crowdloan_period = (101u64, 109u64); + + Crowdloan::set_crowdloan_allocation(RuntimeOrigin::root(), 2500u128).unwrap(); + // We will have all pointint to the same reward account + assert_ok!(Crowdloan::initialize_reward_vec( + RuntimeOrigin::root(), + vec![(AccountId20::from(pairs[0]).into(), Some(ALICE), 2500u32.into()),], + )); + + // Complete + assert_ok!(Crowdloan::complete_initialization( + RuntimeOrigin::root(), + first_crowdloan_period.0, + first_crowdloan_period.1 + )); + + assert_ok!(Crowdloan::claim(RuntimeOrigin::signed(ALICE), None)); + + // 20% of all rewards + assert_eq!(transferable_balance(&ALICE, 0), 500); + assert_eq!(Crowdloan::get_crowdloan_id(), 0); + + assert_ok!(Crowdloan::set_crowdloan_allocation(RuntimeOrigin::root(), 2500u128)); + assert_eq!(Crowdloan::get_crowdloan_id(), 1); + + assert_ok!(Crowdloan::initialize_reward_vec( + RuntimeOrigin::root(), + vec![(AccountId20::from(pairs[0]).into(), Some(ALICE), 2500u32.into()),], + )); + + assert_err!( + Crowdloan::claim(RuntimeOrigin::signed(ALICE), Some(1)), + Error::::RewardVecNotFullyInitializedYet + ); + + assert_ok!(Crowdloan::complete_initialization( + RuntimeOrigin::root(), + second_crowdloan_period.0, + second_crowdloan_period.1 + )); + assert_ok!(Crowdloan::claim(RuntimeOrigin::signed(ALICE), Some(1))); + + assert_eq!(transferable_balance(&ALICE, 0), 1000); + + roll_to(first_crowdloan_period.0 + 1); + assert_eq!(transferable_balance(&ALICE, 0), 1000 + (2000 / 8)); + roll_to(first_crowdloan_period.0 + 2); + assert_eq!(transferable_balance(&ALICE, 0), 1000 + (2000 / 8) * 2); + roll_to(first_crowdloan_period.1 + 1); + assert_eq!(transferable_balance(&ALICE, 0), 3000); + + roll_to(second_crowdloan_period.0 + 1); + assert_eq!(transferable_balance(&ALICE, 0), 3000 + (2000 / 8)); + roll_to(second_crowdloan_period.0 + 2); + assert_eq!(transferable_balance(&ALICE, 0), 3000 + (2000 / 8) * 2); + roll_to(second_crowdloan_period.1 + 1); + assert_eq!(transferable_balance(&ALICE, 0), 5000); + }); +} + +#[test] +fn test_claim_rewards_from_consecutive_crowdloans_with_overlapping_schedules() { + empty().execute_with(|| { + let pairs = get_ecdsa_pairs(2); + let first_crowdloan_period = (1u64, 9u64); + let second_crowdloan_period = (5u64, 13u64); + + Crowdloan::set_crowdloan_allocation(RuntimeOrigin::root(), 2500u128).unwrap(); + // We will have all pointint to the same reward account + assert_ok!(Crowdloan::initialize_reward_vec( + RuntimeOrigin::root(), + vec![(AccountId20::from(pairs[0]).into(), Some(ALICE), 2500u32.into()),], + )); + + // Complete + assert_ok!(Crowdloan::complete_initialization( + RuntimeOrigin::root(), + first_crowdloan_period.0, + first_crowdloan_period.1 + )); + + assert_ok!(Crowdloan::claim(RuntimeOrigin::signed(ALICE), None)); + + // 20% of all rewards + assert_eq!(transferable_balance(&ALICE, 0), 500); + assert_eq!(Crowdloan::get_crowdloan_id(), 0); + + assert_ok!(Crowdloan::set_crowdloan_allocation(RuntimeOrigin::root(), 2500u128)); + assert_eq!(Crowdloan::get_crowdloan_id(), 1); + + assert_ok!(Crowdloan::initialize_reward_vec( + RuntimeOrigin::root(), + vec![(AccountId20::from(pairs[0]).into(), Some(ALICE), 2500u32.into()),], + )); + + assert_err!( + Crowdloan::claim(RuntimeOrigin::signed(ALICE), Some(1)), + Error::::RewardVecNotFullyInitializedYet + ); + assert_ok!(Crowdloan::complete_initialization( + RuntimeOrigin::root(), + second_crowdloan_period.0, + second_crowdloan_period.1 + )); + assert_ok!(Crowdloan::claim(RuntimeOrigin::signed(ALICE), Some(1))); + + assert_eq!(transferable_balance(&ALICE, 0), 1000); + + roll_to(2); + assert_eq!(transferable_balance(&ALICE, 0), 1000 + (2000 / 8)); + roll_to(3); + assert_eq!(transferable_balance(&ALICE, 0), 1000 + (2000 / 8) * 2); + roll_to(4); + assert_eq!(transferable_balance(&ALICE, 0), 1000 + (2000 / 8) * 3); + + roll_to(6); + assert_eq!(transferable_balance(&ALICE, 0), 1000 + (2000 / 8) * 5 + (2000 / 8)); + roll_to(7); + assert_eq!(transferable_balance(&ALICE, 0), 1000 + (2000 / 8) * 6 + (2000 / 8) * 2); + roll_to(second_crowdloan_period.1); + assert_eq!(transferable_balance(&ALICE, 0), 5000); + }); +} + +#[test] +fn change_crowdloan_allocation_before_finalization() { + empty().execute_with(|| { + let pairs = get_ecdsa_pairs(2); + let first_crowdloan_period = (1u64, 9u64); + let _second_crowdloan_period = (5u64, 13u64); + + Crowdloan::set_crowdloan_allocation(RuntimeOrigin::root(), 2500u128).unwrap(); + // We will have all pointint to the same reward account + assert_ok!(Crowdloan::initialize_reward_vec( + RuntimeOrigin::root(), + vec![(AccountId20::from(pairs[0]).into(), Some(ALICE), 500u32.into()),], + )); + + assert_err!( + Crowdloan::complete_initialization( + RuntimeOrigin::root(), + first_crowdloan_period.0, + first_crowdloan_period.1 + ), + Error::::RewardsDoNotMatchFund + ); + + Crowdloan::set_crowdloan_allocation(RuntimeOrigin::root(), 500u128).unwrap(); + + assert_ok!(Crowdloan::complete_initialization( + RuntimeOrigin::root(), + first_crowdloan_period.0, + first_crowdloan_period.1 + )); + assert!(Initialized::::get()); + assert_eq!(Crowdloan::get_crowdloan_id(), 0); + + // schedule following crowdloan + Crowdloan::set_crowdloan_allocation(RuntimeOrigin::root(), 1000u128).unwrap(); + assert_eq!(Crowdloan::get_crowdloan_id(), 1); + assert!(!Initialized::::get()); + assert_ok!(Crowdloan::initialize_reward_vec( + RuntimeOrigin::root(), + vec![(AccountId20::from(pairs[0]).into(), Some(ALICE), 500u32.into()),], + )); + assert!(!Initialized::::get()); + + Crowdloan::set_crowdloan_allocation(RuntimeOrigin::root(), 500u128).unwrap(); + assert_ok!(Crowdloan::complete_initialization( + RuntimeOrigin::root(), + first_crowdloan_period.0, + first_crowdloan_period.1 + )); + + Crowdloan::set_crowdloan_allocation(RuntimeOrigin::root(), 1000u128).unwrap(); + assert_eq!(Crowdloan::get_crowdloan_id(), 2); + }); +} + +#[test] +fn change_crowdloan_allocation_before_finalization_to_lower_value_than_initialized_so_far() { + empty().execute_with(|| { + let pairs = get_ecdsa_pairs(2); + let first_crowdloan_period = (1u64, 9u64); + let _second_crowdloan_period = (5u64, 13u64); + + Crowdloan::set_crowdloan_allocation(RuntimeOrigin::root(), 2500u128).unwrap(); + // We will have all pointint to the same reward account + assert_ok!(Crowdloan::initialize_reward_vec( + RuntimeOrigin::root(), + vec![(AccountId20::from(pairs[0]).into(), Some(ALICE), 500u32.into()),], + )); + + assert_err!( + Crowdloan::set_crowdloan_allocation(RuntimeOrigin::root(), 499u128), + Error::::AllocationDoesNotMatch + ); + + assert_ok!(Crowdloan::set_crowdloan_allocation(RuntimeOrigin::root(), 2500u128)); + + assert_err!( + Crowdloan::complete_initialization( + RuntimeOrigin::root(), + first_crowdloan_period.0, + first_crowdloan_period.1 + ), + Error::::RewardsDoNotMatchFund + ); + + assert_ok!(Crowdloan::set_crowdloan_allocation(RuntimeOrigin::root(), 500u128)); + + assert_ok!(Crowdloan::complete_initialization( + RuntimeOrigin::root(), + first_crowdloan_period.0, + first_crowdloan_period.1 + )); + }); +} + +#[test] +fn track_total_crowdloan_contributors_for_each_crowdloan_separately() { + empty().execute_with(|| { + let pairs = get_ecdsa_pairs(2); + let first_crowdloan_period = (1u64, 9u64); + let _second_crowdloan_period = (5u64, 13u64); + + // FIRST CROWDLOAN + Crowdloan::set_crowdloan_allocation(RuntimeOrigin::root(), 500u128).unwrap(); + assert_ok!(Crowdloan::initialize_reward_vec( + RuntimeOrigin::root(), + vec![(AccountId20::from(pairs[0]).into(), Some(ALICE), 500u32.into()),], + )); + assert_ok!(Crowdloan::complete_initialization( + RuntimeOrigin::root(), + first_crowdloan_period.0, + first_crowdloan_period.1 + )); + + // SECOND CROWDLOAN + Crowdloan::set_crowdloan_allocation(RuntimeOrigin::root(), 500u128).unwrap(); + assert_ok!(Crowdloan::initialize_reward_vec( + RuntimeOrigin::root(), + vec![(AccountId20::from(pairs[0]).into(), Some(ALICE), 500u32.into()),], + )); + assert_ok!(Crowdloan::complete_initialization( + RuntimeOrigin::root(), + first_crowdloan_period.0, + first_crowdloan_period.1 + )); + + assert_eq!(Crowdloan::total_contributors_by_id(0), 1); + assert_eq!(Crowdloan::total_contributors_by_id(1), 1); + }); +} + +#[test] +fn update_rewards_address_for_past_crwdloans() { + empty().execute_with(|| { + let pairs = get_ecdsa_pairs(2); + let first_crowdloan_period = (1u64, 9u64); + let _second_crowdloan_period = (5u64, 13u64); + + // FIRST CROWDLOAN + Crowdloan::set_crowdloan_allocation(RuntimeOrigin::root(), 500u128).unwrap(); + assert_ok!(Crowdloan::initialize_reward_vec( + RuntimeOrigin::root(), + vec![(AccountId20::from(pairs[0]).into(), Some(ALICE), 500u32.into()),], + )); + assert_ok!(Crowdloan::complete_initialization( + RuntimeOrigin::root(), + first_crowdloan_period.0, + first_crowdloan_period.1 + )); + + // SECOND CROWDLOAN + Crowdloan::set_crowdloan_allocation(RuntimeOrigin::root(), 500u128).unwrap(); + assert_ok!(Crowdloan::initialize_reward_vec( + RuntimeOrigin::root(), + vec![(AccountId20::from(pairs[0]).into(), Some(BOB), 500u32.into()),], + )); + assert_ok!(Crowdloan::complete_initialization( + RuntimeOrigin::root(), + first_crowdloan_period.0, + first_crowdloan_period.1 + )); + + assert!(Crowdloan::accounts_payable(0, ALICE).is_some(),); + assert!(Crowdloan::accounts_payable(1, BOB).is_some(),); + + assert_err!( + Crowdloan::update_reward_address(RuntimeOrigin::signed(ALICE), ALICE_NEW, Some(1),), + Error::::NoAssociatedClaim + ); + + assert_err!( + Crowdloan::update_reward_address(RuntimeOrigin::signed(BOB), BOB_NEW, Some(0),), + Error::::NoAssociatedClaim + ); + + assert_ok!(Crowdloan::update_reward_address( + RuntimeOrigin::signed(ALICE), + ALICE_NEW, + Some(0), + )); + + assert_ok!(Crowdloan::update_reward_address(RuntimeOrigin::signed(BOB), BOB_NEW, Some(1),)); + + assert!(Crowdloan::accounts_payable(0, ALICE_NEW).is_some(),); + assert!(Crowdloan::accounts_payable(1, BOB_NEW).is_some(),); + }); +} + +#[test] +fn test_claim_previous_crowdloan_rewards_during_initialization_of_another_one() { + empty().execute_with(|| { + let pairs = get_ecdsa_pairs(2); + let first_crowdloan_period = (1u64, 9u64); + let _second_crowdloan_period = (5u64, 13u64); + + // FIRST CROWDLOAN + Crowdloan::set_crowdloan_allocation(RuntimeOrigin::root(), 500u128).unwrap(); + assert_ok!(Crowdloan::initialize_reward_vec( + RuntimeOrigin::root(), + vec![(AccountId20::from(pairs[0]).into(), Some(ALICE), 500u32.into()),], + )); + assert_ok!(Crowdloan::complete_initialization( + RuntimeOrigin::root(), + first_crowdloan_period.0, + first_crowdloan_period.1 + )); + + // SECOND CROWDLOAN + Crowdloan::set_crowdloan_allocation(RuntimeOrigin::root(), 500u128).unwrap(); + assert_ok!(Crowdloan::initialize_reward_vec( + RuntimeOrigin::root(), + vec![(AccountId20::from(pairs[0]).into(), Some(BOB), 500u32.into()),], + )); + + assert_ok!(Crowdloan::claim(RuntimeOrigin::signed(ALICE), Some(0))); + + assert_ok!(Crowdloan::complete_initialization( + RuntimeOrigin::root(), + first_crowdloan_period.0, + first_crowdloan_period.1 + )); + }); +} + +#[test] +fn reproduce_mgx654_bug_report() { + empty().execute_with(|| { + let pairs = get_ecdsa_pairs(3); + const ALICE: u64 = 1u64; + const BOB: u64 = 2u64; + const CHARLIE: u64 = 3u64; + const SINGLE_USER_REWARDS: u128 = 1_000_000u128; + + // FIRST CROWDLOAN + Crowdloan::set_crowdloan_allocation(RuntimeOrigin::root(), 3 * SINGLE_USER_REWARDS) + .unwrap(); + assert_ok!(Crowdloan::initialize_reward_vec( + RuntimeOrigin::root(), + vec![ + (AccountId20::from(pairs[0]).into(), Some(ALICE), SINGLE_USER_REWARDS), + (AccountId20::from(pairs[1]).into(), Some(BOB), SINGLE_USER_REWARDS), + (AccountId20::from(pairs[2]).into(), Some(CHARLIE), SINGLE_USER_REWARDS), + ], + )); + + assert_ok!(Crowdloan::complete_initialization(RuntimeOrigin::root(), 10, 60,)); + + assert_eq!( + orml_tokens::Pallet::::accounts(ALICE, 0), + orml_tokens::AccountData { free: 0u128, reserved: 0u128, frozen: 0u128 } + ); + + assert_eq!( + orml_tokens::Pallet::::accounts(BOB, 0), + orml_tokens::AccountData { free: 0u128, reserved: 0u128, frozen: 0u128 } + ); + + assert_eq!( + orml_tokens::Pallet::::accounts(CHARLIE, 0), + orml_tokens::AccountData { free: 0u128, reserved: 0u128, frozen: 0u128 } + ); + + roll_to(8); + assert_ok!(Crowdloan::claim(RuntimeOrigin::signed(ALICE), Some(0))); + assert_eq!( + orml_tokens::Pallet::::accounts(ALICE, 0), + orml_tokens::AccountData { + free: SINGLE_USER_REWARDS, + reserved: 0u128, + frozen: SINGLE_USER_REWARDS - + (::InitializationPayment::get() * SINGLE_USER_REWARDS) + } + ); + + roll_to(50); + assert_ok!(Crowdloan::claim(RuntimeOrigin::signed(BOB), Some(0))); + assert_eq!( + orml_tokens::Pallet::::accounts(BOB, 0), + orml_tokens::AccountData { + free: SINGLE_USER_REWARDS, + reserved: 0u128, + frozen: (SINGLE_USER_REWARDS - + (::InitializationPayment::get() * SINGLE_USER_REWARDS)) * + 10 / 50 + } + ); + + roll_to(70); + assert_ok!(Crowdloan::claim(RuntimeOrigin::signed(CHARLIE), Some(0))); + assert_eq!( + orml_tokens::Pallet::::accounts(CHARLIE, 0), + orml_tokens::AccountData { free: SINGLE_USER_REWARDS, reserved: 0u128, frozen: 0u128 } + ); + + assert_ok!(pallet_vesting_mangata::Pallet::::vest(RuntimeOrigin::signed(ALICE), 0)); + assert_ok!(pallet_vesting_mangata::Pallet::::vest(RuntimeOrigin::signed(BOB), 0)); + assert_err!( + pallet_vesting_mangata::Pallet::::vest(RuntimeOrigin::signed(CHARLIE), 0), + pallet_vesting_mangata::Error::::NotVesting + ); + + assert_eq!( + orml_tokens::Pallet::::accounts(ALICE, 0), + orml_tokens::AccountData { free: SINGLE_USER_REWARDS, reserved: 0u128, frozen: 0u128 } + ); + + assert_eq!( + orml_tokens::Pallet::::accounts(BOB, 0), + orml_tokens::AccountData { free: SINGLE_USER_REWARDS, reserved: 0u128, frozen: 0u128 } + ); + + assert_eq!( + orml_tokens::Pallet::::accounts(CHARLIE, 0), + orml_tokens::AccountData { free: SINGLE_USER_REWARDS, reserved: 0u128, frozen: 0u128 } + ); + }); +} diff --git a/gasp-node/pallets/crowdloan-rewards/src/weights.rs b/gasp-node/pallets/crowdloan-rewards/src/weights.rs new file mode 100644 index 000000000..c6bc6a69b --- /dev/null +++ b/gasp-node/pallets/crowdloan-rewards/src/weights.rs @@ -0,0 +1,148 @@ +// Copyright 2019-2022 PureStake Inc. +// This file is part of Moonbeam. + +// Moonbeam 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. + +// Moonbeam 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 Moonbeam. If not, see . + +//! Autogenerated weights for pallet_crowdloan_rewards +//! +//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 3.0.0 +//! DATE: 2021-08-01, STEPS: `[32, ]`, REPEAT: 64, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! EXECUTION: Some(Wasm), WASM-EXECUTION: Compiled, CHAIN: Some("dev"), DB CACHE: 128 + +// Executed Command: +// ./target/release/moonbeam +// benchmark +// --chain +// dev +// --execution=wasm +// --wasm-execution=compiled +// --pallet +// pallet_crowdloan_rewards +// --extrinsic +// * +// --steps +// 32 +// --repeat +// 64 +// --raw +// --template=./benchmarking/frame-weight-template.hbs +// --output +// /tmp/ + +#![allow(unused_parens)] +#![allow(unused_imports)] + +use frame_support::{ + traits::Get, + weights::{constants::RocksDbWeight, Weight}, +}; +use sp_std::marker::PhantomData; + +/// Weight functions needed for pallet_crowdloan_rewards. +pub trait WeightInfo { + fn initialize_reward_vec(x: u32) -> Weight; + fn complete_initialization() -> Weight; + fn claim() -> Weight; + fn update_reward_address() -> Weight; + fn associate_native_identity() -> Weight; + fn change_association_with_relay_keys(x: u32) -> Weight; + fn set_crowdloan_allocation() -> Weight; +} + +/// Weights for pallet_crowdloan_rewards using the Substrate node and recommended hardware. +pub struct SubstrateWeight(PhantomData); +impl WeightInfo for SubstrateWeight { + fn initialize_reward_vec(x: u32) -> Weight { + Weight::from_parts(143_109_000, 0) + // Standard Error: 21_000 + .saturating_add((Weight::from_parts(72_298_000, 0)).saturating_mul(x as u64)) + .saturating_add(T::DbWeight::get().reads(8_u64)) + .saturating_add(T::DbWeight::get().reads(4_u64.saturating_mul(x as u64))) + .saturating_add(T::DbWeight::get().writes(5_u64)) + .saturating_add(T::DbWeight::get().writes(3_u64.saturating_mul(x as u64))) + } + fn complete_initialization() -> Weight { + Weight::from_parts(51_047_000, 0) + .saturating_add(T::DbWeight::get().reads(6_u64)) + .saturating_add(T::DbWeight::get().writes(3_u64)) + } + fn set_crowdloan_allocation() -> Weight { + Weight::from_parts(147_000, 0).saturating_add(T::DbWeight::get().writes(1_u64)) + } + fn claim() -> Weight { + Weight::from_parts(101_484_000, 0) + .saturating_add(T::DbWeight::get().reads(11_u64)) + .saturating_add(T::DbWeight::get().writes(5_u64)) + } + fn update_reward_address() -> Weight { + Weight::from_parts(59_051_000, 0) + .saturating_add(T::DbWeight::get().reads(6_u64)) + .saturating_add(T::DbWeight::get().writes(4_u64)) + } + fn associate_native_identity() -> Weight { + Weight::from_parts(152_997_000, 0) + .saturating_add(T::DbWeight::get().reads(9_u64)) + .saturating_add(T::DbWeight::get().writes(7_u64)) + } + fn change_association_with_relay_keys(x: u32) -> Weight { + Weight::from_parts(0, 0) + // Standard Error: 7_000 + .saturating_add((Weight::from_parts(47_373_000, 0)).saturating_mul(x as u64)) + .saturating_add(T::DbWeight::get().reads(6_u64)) + .saturating_add(T::DbWeight::get().writes(4_u64)) + } +} + +// For backwards compatibility and tests +impl WeightInfo for () { + fn initialize_reward_vec(x: u32) -> Weight { + Weight::from_parts(143_109_000, 0) + // Standard Error: 21_000 + .saturating_add((Weight::from_parts(72_298_000, 0)).saturating_mul(x as u64)) + .saturating_add(RocksDbWeight::get().reads(8_u64)) + .saturating_add(RocksDbWeight::get().reads(4_u64.saturating_mul(x as u64))) + .saturating_add(RocksDbWeight::get().writes(5_u64)) + .saturating_add(RocksDbWeight::get().writes(3_u64.saturating_mul(x as u64))) + } + fn set_crowdloan_allocation() -> Weight { + Weight::from_parts(147_000, 0).saturating_add(RocksDbWeight::get().writes(1_u64)) + } + fn complete_initialization() -> Weight { + Weight::from_parts(51_047_000, 0) + .saturating_add(RocksDbWeight::get().reads(6_u64)) + .saturating_add(RocksDbWeight::get().writes(3_u64)) + } + fn claim() -> Weight { + Weight::from_parts(101_484_000, 0) + .saturating_add(RocksDbWeight::get().reads(11_u64)) + .saturating_add(RocksDbWeight::get().writes(5_u64)) + } + fn update_reward_address() -> Weight { + Weight::from_parts(59_051_000, 0) + .saturating_add(RocksDbWeight::get().reads(6_u64)) + .saturating_add(RocksDbWeight::get().writes(4_u64)) + } + fn associate_native_identity() -> Weight { + Weight::from_parts(152_997_000, 0) + .saturating_add(RocksDbWeight::get().reads(9_u64)) + .saturating_add(RocksDbWeight::get().writes(7_u64)) + } + fn change_association_with_relay_keys(x: u32) -> Weight { + Weight::from_parts(0, 0) + // Standard Error: 7_000 + .saturating_add((Weight::from_parts(47_373_000, 0)).saturating_mul(x as u64)) + .saturating_add(RocksDbWeight::get().reads(6_u64)) + .saturating_add(RocksDbWeight::get().writes(4_u64)) + } +} diff --git a/gasp-node/pallets/fee-lock/Cargo.toml b/gasp-node/pallets/fee-lock/Cargo.toml new file mode 100644 index 000000000..8110fc295 --- /dev/null +++ b/gasp-node/pallets/fee-lock/Cargo.toml @@ -0,0 +1,68 @@ +[package] +authors = ["Mangata team"] +edition = "2018" +name = "pallet-fee-lock" +version = "0.1.0" + +[package.metadata.docs.rs] +targets = ["x86_64-unknown-linux-gnu"] + +[dependencies] +codec = { workspace = true, default-features = false } +hex = { workspace = true, default-features = false } +hex-literal = { workspace = true, default-features = false } +log = { workspace = true, default-features = false } +serde = { workspace = true, optional = true } +scale-info = { workspace = true, default-features = false, features = ["derive"] } + +pallet-xyk = { path = "../xyk/", default-features = false } + +frame-support = { workspace = true, default-features = false } +frame-benchmarking = { workspace = true, default-features = false } +frame-system = { workspace = true, default-features = false } +mangata-support = { workspace = true, default-features = false } +mangata-types = { workspace = true, default-features = false } +sp-arithmetic = { workspace = true, default-features = false } +sp-core = { workspace = true, default-features = false } +sp-runtime = { workspace = true, default-features = false } +sp-std = { workspace = true, default-features = false } + +orml-tokens = { workspace = true, default-features = false } + +[dev-dependencies] +env_logger.workspace = true +lazy_static.workspace = true +serial_test.workspace = true +test-case.workspace = true +mockall.workspace = true + +sp-io = { workspace = true, default-features = false } + +orml-traits = { workspace = true, default-features = false } + +[features] +default = ["std"] +enable-trading = [] +std = [ + "codec/std", + "frame-benchmarking/std", + "frame-support/std", + "frame-system/std", + "hex/std", + "mangata-support/std", + "orml-tokens/std", + "serde", + "sp-core/std", + "sp-runtime/std", + "sp-std/std", +] + +runtime-benchmarks = ["frame-benchmarking/runtime-benchmarks"] + +try-runtime = [ + "frame-support/try-runtime", + "frame-system/try-runtime", + "orml-tokens/try-runtime", + "pallet-xyk/try-runtime", + "sp-runtime/try-runtime", +] diff --git a/gasp-node/pallets/fee-lock/src/benchmarking.rs b/gasp-node/pallets/fee-lock/src/benchmarking.rs new file mode 100644 index 000000000..ebdc32665 --- /dev/null +++ b/gasp-node/pallets/fee-lock/src/benchmarking.rs @@ -0,0 +1,195 @@ +// This file is part of Substrate. + +// Copyright (C) 2020-2022 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. + +//! FeeLock pallet benchmarking. + +#![cfg(feature = "runtime-benchmarks")] + +use super::*; + +use frame_benchmarking::{benchmarks, whitelisted_caller}; +use frame_support::{assert_ok, traits::tokens::currency::MultiTokenCurrency}; +use frame_system::RawOrigin; +use orml_tokens::MultiTokenCurrencyExtended; + +use crate::Pallet as FeeLock; + +const MGA_TOKEN_ID: u32 = 0; + +benchmarks! { + + process_fee_lock{ + + let caller: T::AccountId = whitelisted_caller(); + let period_length: BlockNumberFor = 10u32.into(); + let fee_lock_amount: BalanceOf = 1000_u32.into(); + let queue_position = 10u128; + let token_id = MGA_TOKEN_ID.into(); + >::set_block_number(1u32.into()); + let now= >::block_number(); + let mint_amount: BalanceOf = 1_000_000u32.into(); + let swap_value_threshold: BalanceOf = 1000_u32.into(); + + // This should be a while loop + // But this is fine here since token_id is 0 + if ::Tokens::get_next_currency_id() > token_id { + assert_ok!(::Tokens::mint(token_id, &caller.clone(), mint_amount)); + } else { + assert_eq!(::Tokens::create(&caller.clone(), mint_amount).unwrap(), token_id); + } + + assert_ok!(FeeLock::::update_fee_lock_metadata(RawOrigin::Root.into(), Some(period_length), Some(fee_lock_amount), Some(swap_value_threshold), None)); + + FeeLockMetadataQeueuePosition::::insert(caller.clone(), queue_position); + UnlockQueue::::insert(queue_position, caller.clone()); + + + let initial_user_free_balance = ::Tokens::free_balance(token_id, &caller.clone()); + let initial_user_reserved_balance = ::Tokens::reserved_balance(token_id, &caller.clone()); + let initial_user_locked_balance = ::Tokens::locked_balance(token_id, &caller.clone()); + + assert_eq!(FeeLock::::get_account_fee_lock_data(caller.clone()), AccountFeeLockDataInfo{ + total_fee_lock_amount: Default::default(), + last_fee_lock_block: Default::default(), + }); + + } : {FeeLock::::process_fee_lock(&caller)} + verify{ + + assert_eq!(::Tokens::free_balance(token_id, &caller.clone()), + initial_user_free_balance - fee_lock_amount); + assert_eq!(::Tokens::reserved_balance(token_id, &caller.clone()), + initial_user_reserved_balance + fee_lock_amount); + assert_eq!(::Tokens::locked_balance(token_id, &caller.clone()), + initial_user_locked_balance); + + assert_eq!(FeeLock::::get_account_fee_lock_data(caller.clone()), AccountFeeLockDataInfo{ + total_fee_lock_amount: fee_lock_amount, + last_fee_lock_block: now, + }); + } + + get_swap_valuation_for_token { + + let caller: T::AccountId = whitelisted_caller(); + let mint_amount: BalanceOf = 1_000_000u32.into(); + let pool_amount: BalanceOf = 100_000u32.into(); + let token_id = MGA_TOKEN_ID.into(); + + // This should be a while loop + // But this is fine here since token_id is 0 + if ::Tokens::get_next_currency_id() > token_id { + assert_ok!(::Tokens::mint(token_id, &caller.clone(), mint_amount)); + } else { + assert_eq!(::Tokens::create(&caller.clone(), mint_amount).unwrap(), token_id); + } + + let valuating_token_amount: BalanceOf = 1000_u32.into(); + let valuating_token_id = ::Tokens::create(&caller.clone(), mint_amount)?; + + // Order of tokens in the create_pool call below is important + #[cfg(not(test))] + assert_ok!(::Xyk::create_pool(caller, valuating_token_id, pool_amount, token_id, pool_amount.saturating_mul(2u8.into()))); + + let value: BalanceOf = 2000_u32.into(); + let mut valuation: Option> = None; + }: {valuation = FeeLock::::get_swap_valuation_for_token(valuating_token_id, valuating_token_amount);} + verify{ + assert_eq!(valuation, Some(value)); + } + + update_fee_lock_metadata{ + let period_length: BlockNumberFor = 1000u32.into(); + let fee_lock_amount: BalanceOf = 1000_u32.into(); + let swap_value_threshold: BalanceOf = 1000_u32.into(); + let mut whitelisted_tokens: Vec<(CurrencyIdOf, bool)> = Vec::new(); + for i in 0..::MaxCuratedTokens::get() { + whitelisted_tokens.push((i.into(), true)); + } + }: {assert_ok!(FeeLock::::update_fee_lock_metadata(RawOrigin::Root.into(), Some(period_length), Some(fee_lock_amount), Some(swap_value_threshold), Some(whitelisted_tokens)));} + verify{ + assert_eq!(FeeLock::::get_fee_lock_metadata().unwrap().period_length, period_length); + assert_eq!(FeeLock::::get_fee_lock_metadata().unwrap().fee_lock_amount, fee_lock_amount); + assert_eq!(FeeLock::::get_fee_lock_metadata().unwrap().swap_value_threshold, swap_value_threshold); + assert_eq!(FeeLock::::get_fee_lock_metadata().unwrap().whitelisted_tokens.len(), ::MaxCuratedTokens::get() as usize); + } + + unlock_fee{ + + let caller: T::AccountId = whitelisted_caller(); + let period_length: BlockNumberFor = 10u32.into(); + let fee_lock_amount: BalanceOf = 1000_u32.into(); + let swap_value_threshold: BalanceOf = 1000_u32.into(); + + let now= >::block_number(); + let token_id = MGA_TOKEN_ID.into(); + + // This should be a while loop + // But this is fine here since token_id is 0 + if ::Tokens::get_next_currency_id() > token_id { + assert_ok!(::Tokens::mint(token_id, &caller.clone(), 1_000_000u32.into())); + } else { + assert_eq!(::Tokens::create(&caller.clone(), 1_000_000u32.into()).unwrap(), token_id); + } + + let initial_user_free_balance = ::Tokens::free_balance(token_id, &caller.clone()); + let initial_user_reserved_balance = ::Tokens::reserved_balance(token_id, &caller.clone()); + let initial_user_locked_balance = ::Tokens::locked_balance(token_id, &caller.clone()); + + assert_ok!(FeeLock::::update_fee_lock_metadata(RawOrigin::Root.into(), Some(period_length), Some(fee_lock_amount), Some(swap_value_threshold), None)); + + assert_eq!(FeeLock::::get_fee_lock_metadata().unwrap().period_length, period_length); + assert_eq!(FeeLock::::get_fee_lock_metadata().unwrap().fee_lock_amount, fee_lock_amount); + assert_eq!(FeeLock::::get_fee_lock_metadata().unwrap().swap_value_threshold, swap_value_threshold); + assert_eq!(FeeLock::::get_fee_lock_metadata().unwrap().whitelisted_tokens.len(), 0u32 as usize); + + assert_ok!( + as FeeLockTriggerTrait<_,_,_>>::process_fee_lock(&caller) + ); + + assert_eq!(::Tokens::free_balance(token_id, &caller.clone()), + initial_user_free_balance - fee_lock_amount); + assert_eq!(::Tokens::reserved_balance(token_id, &caller.clone()), + initial_user_reserved_balance + fee_lock_amount); + assert_eq!(::Tokens::locked_balance(token_id, &caller.clone()), + initial_user_locked_balance); + + assert_eq!(FeeLock::::get_account_fee_lock_data(caller.clone()), AccountFeeLockDataInfo{ + total_fee_lock_amount: fee_lock_amount, + last_fee_lock_block: now, + }); + + frame_system::Pallet::::set_block_number(now + period_length); + + }: {assert_ok!(FeeLock::::unlock_fee(RawOrigin::Signed(caller.clone().into()).into()));} + verify{ + assert_eq!(::Tokens::free_balance(token_id, &caller.clone()), + initial_user_free_balance); + assert_eq!(::Tokens::reserved_balance(token_id, &caller.clone()), + initial_user_reserved_balance); + assert_eq!(::Tokens::locked_balance(token_id, &caller.clone()), + initial_user_locked_balance); + + assert_eq!(FeeLock::::get_account_fee_lock_data(caller.clone()), AccountFeeLockDataInfo{ + total_fee_lock_amount: BalanceOf::::zero(), + last_fee_lock_block: 0_u32.into(), + }); + } + + + impl_benchmark_test_suite!(FeeLock, crate::mock::new_test_ext(), crate::mock::Test) +} diff --git a/gasp-node/pallets/fee-lock/src/lib.rs b/gasp-node/pallets/fee-lock/src/lib.rs new file mode 100644 index 000000000..e1e8d621f --- /dev/null +++ b/gasp-node/pallets/fee-lock/src/lib.rs @@ -0,0 +1,559 @@ +#![cfg_attr(not(feature = "std"), no_std)] +// #![feature(custom_test_frameworks)] + +use frame_support::{ + dispatch::DispatchResult, + ensure, + pallet_prelude::*, + storage::bounded_btree_set::BoundedBTreeSet, + traits::{Get, MultiTokenCurrency, StorageVersion}, + transactional, +}; +use frame_system::{ensure_signed, pallet_prelude::*}; +use mangata_support::{ + pools::ValuateFor, + traits::{FeeLockTriggerTrait, XykFunctionsTrait}, +}; +use orml_tokens::{MultiTokenCurrencyExtended, MultiTokenReservableCurrency}; + +use sp_runtime::{ + traits::{CheckedAdd, Zero}, + Saturating, +}; +use sp_std::{convert::TryInto, prelude::*}; + +#[cfg(test)] +mod mock; +#[cfg(test)] +mod tests; + +mod benchmarking; + +pub mod weights; +pub use weights::WeightInfo; + +pub(crate) const LOG_TARGET: &'static str = "fee-lock"; + +// syntactic sugar for logging. +#[macro_export] +macro_rules! log { + ($level:tt, $patter:expr $(, $values:expr)* $(,)?) => { + log::$level!( + target: crate::LOG_TARGET, + concat!("[{:?}] 💸 ", $patter), >::block_number() $(, $values)* + ) + }; +} + +pub use pallet::*; + +pub type BalanceOf = <::Tokens as MultiTokenCurrency< + ::AccountId, +>>::Balance; + +pub type CurrencyIdOf = <::Tokens as MultiTokenCurrency< + ::AccountId, +>>::CurrencyId; + +#[frame_support::pallet] +pub mod pallet { + use super::*; + + const STORAGE_VERSION: StorageVersion = StorageVersion::new(0); + + #[pallet::pallet] + #[pallet::storage_version(STORAGE_VERSION)] + pub struct Pallet(PhantomData); + + #[pallet::hooks] + impl Hooks> for Pallet { + fn on_idle(now: BlockNumberFor, remaining_weight: Weight) -> Weight { + let mut consumed_weight: Weight = Default::default(); + + // process only up to 80% or remaining weight + let base_cost = T::DbWeight::get().reads(1) // UnlockQueueBegin + + T::DbWeight::get().reads(1) // UnlockQueueEnd + + T::DbWeight::get().reads(1); // FeeLockMetadata + + let cost_of_single_unlock_iteration = T::WeightInfo::unlock_fee() // cost of unlock action + + T::DbWeight::get().reads(1) // FeeLockMetadataQeueuePosition + + T::DbWeight::get().reads(1) // AccountFeeLockData + + T::DbWeight::get().reads(1) // UnlockQueue + + T::DbWeight::get().writes(1); // UnlockQueueBegin + + if (base_cost + cost_of_single_unlock_iteration).ref_time() > + remaining_weight.ref_time() + { + return Weight::from_parts(0, 0) + } + + let metadata = Self::get_fee_lock_metadata(); + let period_length = metadata.map(|meta| meta.period_length); + let begin = UnlockQueueBegin::::get(); + let end = UnlockQueueEnd::::get(); + consumed_weight += base_cost; + + for i in begin..end { + consumed_weight += T::DbWeight::get().reads(3); // UnlockQueue, AccountFeeLockData FeeLockMetadataQeueuePosition + UnlockQueueBegin::::put(i); + consumed_weight += T::DbWeight::get().writes(1); + let who = UnlockQueue::::get(i); + let queue_pos = + who.as_ref().and_then(|acc| FeeLockMetadataQeueuePosition::::get(acc)); + + if matches!(queue_pos, Some(id) if id == i) { + let lock_info = who.and_then(|who| { + AccountFeeLockData::::try_get(&who).map(|lock| (who.clone(), lock)).ok() + }); + + match (period_length, lock_info) { + (Some(period_length), Some((who, lock))) => { + let unlock_block = lock.last_fee_lock_block.checked_add(&period_length); + + if matches!(unlock_block, Some(unlock) if unlock <= now) { + UnlockQueueBegin::::put(i + 1); + consumed_weight += T::WeightInfo::unlock_fee(); + let _ = , + CurrencyIdOf, + >>::unlock_fee(&who); + } else { + break + } + }, + _ => break, + }; + } else { + UnlockQueueBegin::::put(i + 1); + } + + if cost_of_single_unlock_iteration.ref_time() > + (remaining_weight.ref_time() - consumed_weight.ref_time()) + { + break + } + } + consumed_weight + } + } + + #[derive(Eq, PartialEq, RuntimeDebug, Clone, Encode, Decode, MaxEncodedLen, TypeInfo)] + #[codec(mel_bound(T: Config))] + #[scale_info(skip_type_params(T))] + pub struct FeeLockMetadataInfo { + pub period_length: BlockNumberFor, + pub fee_lock_amount: BalanceOf, + pub swap_value_threshold: BalanceOf, + pub whitelisted_tokens: BoundedBTreeSet, T::MaxCuratedTokens>, + } + + impl FeeLockMetadataInfo { + pub fn is_whitelisted(&self, token_id: CurrencyIdOf) -> bool { + if T::NativeTokenId::get() == token_id { + return true + } + self.whitelisted_tokens.contains(&token_id) + } + } + + #[pallet::storage] + #[pallet::getter(fn get_fee_lock_metadata)] + pub type FeeLockMetadata = StorageValue<_, FeeLockMetadataInfo, OptionQuery>; + + #[pallet::storage] + pub type FeeLockMetadataQeueuePosition = + StorageMap<_, Twox64Concat, T::AccountId, u128, OptionQuery>; + + #[pallet::storage] + pub type UnlockQueue = StorageMap<_, Twox64Concat, u128, T::AccountId, OptionQuery>; + + #[pallet::storage] + pub type UnlockQueueBegin = StorageValue<_, u128, ValueQuery>; + + #[pallet::storage] + pub type UnlockQueueEnd = StorageValue<_, u128, ValueQuery>; + + #[derive( + Eq, PartialEq, Clone, Encode, Decode, RuntimeDebug, MaxEncodedLen, TypeInfo, Default, + )] + pub struct AccountFeeLockDataInfo { + pub total_fee_lock_amount: Balance, + pub last_fee_lock_block: BlockNumber, + } + + #[pallet::storage] + #[pallet::getter(fn get_account_fee_lock_data)] + pub type AccountFeeLockData = StorageMap< + _, + Twox64Concat, + T::AccountId, + AccountFeeLockDataInfo, BalanceOf>, + ValueQuery, + >; + + #[pallet::event] + #[pallet::generate_deposit(pub(super) fn deposit_event)] + pub enum Event { + FeeLockMetadataUpdated, + FeeLockUnlocked(T::AccountId, BalanceOf), + FeeLocked { who: T::AccountId, lock_amount: BalanceOf, total_locked: BalanceOf }, + } + + #[pallet::error] + /// Errors + pub enum Error { + /// Locks were incorrectly initialized + FeeLocksIncorrectlyInitialzed, + /// Lock metadata is invalid + InvalidFeeLockMetadata, + /// Locks have not been initialzed + FeeLocksNotInitialized, + /// No tokens of the user are fee-locked + NotFeeLocked, + /// The lock cannot be unlocked yet + CantUnlockFeeYet, + /// The limit on the maximum curated tokens for which there is a swap threshold is exceeded + MaxCuratedTokensLimitExceeded, + /// An unexpected failure has occured + UnexpectedFailure, + } + + #[pallet::config] + pub trait Config: frame_system::Config { + type RuntimeEvent: From> + IsType<::RuntimeEvent>; + #[pallet::constant] + type MaxCuratedTokens: Get; + type Tokens: MultiTokenCurrencyExtended + + MultiTokenReservableCurrency; + type ValuateForNative: ValuateFor< + Self::NativeTokenId, + Balance = BalanceOf, + CurrencyId = CurrencyIdOf, + >; + #[pallet::constant] + type NativeTokenId: Get>; + type WeightInfo: WeightInfo; + #[cfg(all(feature = "runtime-benchmarks", not(test)))] + type Xyk: XykFunctionsTrait, CurrencyIdOf>; + } + + #[pallet::genesis_config] + pub struct GenesisConfig { + pub period_length: Option>, + pub fee_lock_amount: Option>, + pub swap_value_threshold: Option>, + pub whitelisted_tokens: Vec>, + } + + impl Default for GenesisConfig { + fn default() -> Self { + GenesisConfig { + period_length: Default::default(), + fee_lock_amount: Default::default(), + swap_value_threshold: Default::default(), + whitelisted_tokens: Default::default(), + } + } + } + + #[pallet::genesis_build] + impl BuildGenesisConfig for GenesisConfig { + fn build(&self) { + match (self.period_length, self.fee_lock_amount, self.swap_value_threshold) { + (Some(period), Some(amount), Some(threshold)) => { + let mut tokens: BoundedBTreeSet, T::MaxCuratedTokens> = + Default::default(); + for t in self.whitelisted_tokens.iter() { + tokens + .try_insert(*t) + .expect("list of tokens is <= than T::MaxCuratedTokens"); + } + + FeeLockMetadata::::put(FeeLockMetadataInfo { + period_length: period, + fee_lock_amount: amount, + swap_value_threshold: threshold, + whitelisted_tokens: tokens, + }); + }, + (None, None, None) => {}, + _ => { + panic!("either all or non config parameters should be set"); + }, + }; + } + } + + #[pallet::call] + impl Pallet { + // The weight is calculated using MaxCuratedTokens so it is the worst case weight + #[pallet::call_index(0)] + #[transactional] + #[pallet::weight(T::WeightInfo::update_fee_lock_metadata())] + pub fn update_fee_lock_metadata( + origin: OriginFor, + period_length: Option>, + fee_lock_amount: Option>, + swap_value_threshold: Option>, + should_be_whitelisted: Option, bool)>>, + ) -> DispatchResultWithPostInfo { + ensure_root(origin)?; + + let mut fee_lock_metadata = + Self::get_fee_lock_metadata().unwrap_or(FeeLockMetadataInfo { + period_length: Default::default(), + fee_lock_amount: Default::default(), + swap_value_threshold: Default::default(), + whitelisted_tokens: Default::default(), + }); + + fee_lock_metadata.period_length = + period_length.unwrap_or(fee_lock_metadata.period_length); + fee_lock_metadata.fee_lock_amount = + fee_lock_amount.unwrap_or(fee_lock_metadata.fee_lock_amount); + fee_lock_metadata.swap_value_threshold = + swap_value_threshold.unwrap_or(fee_lock_metadata.swap_value_threshold); + + ensure!( + !fee_lock_metadata.fee_lock_amount.is_zero(), + Error::::InvalidFeeLockMetadata + ); + ensure!(!fee_lock_metadata.period_length.is_zero(), Error::::InvalidFeeLockMetadata); + ensure!( + !fee_lock_metadata.swap_value_threshold.is_zero(), + Error::::InvalidFeeLockMetadata + ); + + if let Some(should_be_whitelisted) = should_be_whitelisted { + for (token_id, should_be_whitelisted) in should_be_whitelisted.iter() { + match should_be_whitelisted { + true => { + let _ = fee_lock_metadata + .whitelisted_tokens + .try_insert(*token_id) + .map_err(|_| Error::::MaxCuratedTokensLimitExceeded)?; + }, + false => { + let _ = fee_lock_metadata.whitelisted_tokens.remove(token_id); + }, + } + } + } + + FeeLockMetadata::::put(fee_lock_metadata); + + Pallet::::deposit_event(Event::FeeLockMetadataUpdated); + + Ok(().into()) + } + + #[pallet::call_index(1)] + #[transactional] + #[pallet::weight(T::WeightInfo::unlock_fee())] + pub fn unlock_fee(origin: OriginFor) -> DispatchResultWithPostInfo { + let who = ensure_signed(origin)?; + + Ok(, CurrencyIdOf>>::unlock_fee(&who)?.into()) + } + } +} +impl Pallet { + pub(crate) fn push_to_the_end_of_unlock_queue(who: &T::AccountId) { + let mut id = Default::default(); + let id_ref = &mut id; + UnlockQueueEnd::::mutate(|id| { + *id_ref = *id; + *id = *id + 1 + }); + UnlockQueue::::insert(id, who); + FeeLockMetadataQeueuePosition::::set(who, Some(id)); + } + + pub(crate) fn move_to_the_end_of_unlock_queue(who: &T::AccountId) { + if let Ok(id) = FeeLockMetadataQeueuePosition::::try_get(who) { + UnlockQueue::::take(id); + Self::push_to_the_end_of_unlock_queue(who); + } else { + Self::push_to_the_end_of_unlock_queue(who); + } + } +} + +impl FeeLockTriggerTrait, CurrencyIdOf> for Pallet { + fn is_whitelisted(token_id: CurrencyIdOf) -> bool { + if let Some(fee_lock_metadata) = Self::get_fee_lock_metadata() { + fee_lock_metadata.is_whitelisted(token_id) + } else { + false + } + } + + fn get_swap_valuation_for_token( + valuating_token_id: CurrencyIdOf, + valuating_token_amount: BalanceOf, + ) -> Option> { + if T::NativeTokenId::get() == valuating_token_id { + return Some(valuating_token_amount) + } + let value = + T::ValuateForNative::find_valuation_for(valuating_token_id, valuating_token_amount) + .ok()?; + Some(value) + } + + fn process_fee_lock(who: &T::AccountId) -> DispatchResult { + let fee_lock_metadata = + Self::get_fee_lock_metadata().ok_or(Error::::FeeLocksNotInitialized)?; + let mut account_fee_lock_data = Self::get_account_fee_lock_data(who); + let now = >::block_number(); + + // This is cause now >= last_fee_lock_block + ensure!(now >= account_fee_lock_data.last_fee_lock_block, Error::::UnexpectedFailure); + + if now < + account_fee_lock_data + .last_fee_lock_block + .saturating_add(fee_lock_metadata.period_length) + { + // First storage edit + // Cannot fail beyond this point + // Rerserve additional fee_lock_amount + ::Tokens::reserve( + ::NativeTokenId::get().into(), + who, + fee_lock_metadata.fee_lock_amount, + )?; + + // Insert updated account_lock_info into storage + // This is not expected to fail + account_fee_lock_data.total_fee_lock_amount = account_fee_lock_data + .total_fee_lock_amount + .saturating_add(fee_lock_metadata.fee_lock_amount); + account_fee_lock_data.last_fee_lock_block = now; + AccountFeeLockData::::insert(who.clone(), account_fee_lock_data.clone()); + Self::move_to_the_end_of_unlock_queue(who); + Self::deposit_event(Event::FeeLocked { + who: who.clone(), + lock_amount: fee_lock_metadata.fee_lock_amount, + total_locked: account_fee_lock_data.total_fee_lock_amount, + }); + } else { + // We must either reserve more or unreserve + match (fee_lock_metadata.fee_lock_amount, account_fee_lock_data.total_fee_lock_amount) { + (x, y) if x > y => ::Tokens::reserve( + ::NativeTokenId::get().into(), + who, + x.saturating_sub(y), + )?, + (x, y) if x < y => { + let unreserve_result = ::Tokens::unreserve( + ::NativeTokenId::get().into(), + who, + y.saturating_sub(x), + ); + if !unreserve_result.is_zero() { + log::warn!( + "Process fee lock unreserve resulted in non-zero unreserve_result {:?}", + unreserve_result + ); + } + }, + _ => {}, + } + // Insert updated account_lock_info into storage + // This is not expected to fail + account_fee_lock_data.total_fee_lock_amount = fee_lock_metadata.fee_lock_amount; + account_fee_lock_data.last_fee_lock_block = now; + AccountFeeLockData::::insert(who.clone(), account_fee_lock_data.clone()); + Self::move_to_the_end_of_unlock_queue(who); + Self::deposit_event(Event::FeeLocked { + who: who.clone(), + lock_amount: fee_lock_metadata.fee_lock_amount, + total_locked: account_fee_lock_data.total_fee_lock_amount, + }); + } + + Ok(()) + } + + fn can_unlock_fee(who: &T::AccountId) -> DispatchResult { + // Check if total_fee_lock_amount is non-zero + // THEN Check is period is greater than last + + let account_fee_lock_data = Self::get_account_fee_lock_data(&who); + + ensure!(!account_fee_lock_data.total_fee_lock_amount.is_zero(), Error::::NotFeeLocked); + + let fee_lock_metadata = + Self::get_fee_lock_metadata().ok_or(Error::::FeeLocksNotInitialized)?; + + let now = >::block_number(); + + ensure!( + now >= account_fee_lock_data + .last_fee_lock_block + .saturating_add(fee_lock_metadata.period_length), + Error::::CantUnlockFeeYet + ); + + Ok(()) + } + + fn unlock_fee(who: &T::AccountId) -> DispatchResult { + // Check if total_fee_lock_amount is non-zero + // THEN Check is period is greater than last + + let account_fee_lock_data = Self::get_account_fee_lock_data(&who); + + ensure!(!account_fee_lock_data.total_fee_lock_amount.is_zero(), Error::::NotFeeLocked); + + let fee_lock_metadata = + Self::get_fee_lock_metadata().ok_or(Error::::FeeLocksNotInitialized)?; + + let now = >::block_number(); + + ensure!( + now >= account_fee_lock_data + .last_fee_lock_block + .saturating_add(fee_lock_metadata.period_length), + Error::::CantUnlockFeeYet + ); + + let unreserve_result = ::Tokens::unreserve( + ::NativeTokenId::get().into(), + &who, + account_fee_lock_data.total_fee_lock_amount, + ); + if !unreserve_result.is_zero() { + log::warn!( + "Unlock lock unreserve resulted in non-zero unreserve_result {:?}", + unreserve_result + ); + } + + if let Some(pos) = FeeLockMetadataQeueuePosition::::take(&who) { + UnlockQueue::::take(pos); + } + AccountFeeLockData::::remove(&who); + + Self::deposit_event(Event::FeeLockUnlocked( + who.clone(), + account_fee_lock_data.total_fee_lock_amount, + )); + + Ok(()) + } +} + +pub struct FeeLockWeightProvider(PhantomData); + +impl Get for FeeLockWeightProvider { + fn get() -> Weight { + // We assume that process_fee_lock is heavier than unlock_fee + // The FeeLockMetadata read is not accounted for since it is called no matter the extrinsic and hence would be accounted for in the ExtrinsicBaseWeight + T::WeightInfo::process_fee_lock() + .saturating_add(T::WeightInfo::get_swap_valuation_for_token()) + .saturating_add(Weight::from_parts(40_000_000, 0)) + } +} diff --git a/gasp-node/pallets/fee-lock/src/mock.rs b/gasp-node/pallets/fee-lock/src/mock.rs new file mode 100644 index 000000000..46cf215ae --- /dev/null +++ b/gasp-node/pallets/fee-lock/src/mock.rs @@ -0,0 +1,200 @@ +// Copyright (C) 2020 Mangata team + +use super::*; + +use crate as pallet_fee_lock; +use frame_support::{ + construct_runtime, derive_impl, parameter_types, + traits::{Contains, Nothing}, + weights::constants::RocksDbWeight, + PalletId, +}; +use frame_system as system; +use mangata_support::pools::{PoolInfo, Valuate, ValuateFor}; +use orml_traits::parameter_type_with_key; +use sp_runtime::{traits::AccountIdConversion, BuildStorage}; +use sp_std::convert::TryFrom; + +pub const NATIVE_CURRENCY_ID: u32 = 0; +pub(crate) type AccountId = u64; +pub(crate) type Balance = u128; +pub(crate) type TokenId = u32; +pub(crate) type Amount = i128; + +type Block = frame_system::mocking::MockBlock; + +construct_runtime!( + pub enum Test { + System: frame_system, + Tokens: orml_tokens, + FeeLock: pallet_fee_lock, + } +); + +#[derive_impl(frame_system::config_preludes::TestDefaultConfig as frame_system::DefaultConfig)] +impl frame_system::Config for Test { + type Block = Block; + type DbWeight = RocksDbWeight; +} + +parameter_type_with_key! { + pub ExistentialDeposits: |currency_id: TokenId| -> Balance { + match currency_id { + _ => 0, + } + }; +} + +pub struct DustRemovalWhitelist; +impl Contains for DustRemovalWhitelist { + fn contains(a: &AccountId) -> bool { + *a == TreasuryAccount::get() + } +} + +parameter_types! { + pub TreasuryAccount: AccountId = TreasuryPalletId::get().into_account_truncating(); + pub const MaxLocks: u32 = 50; +} + +impl orml_tokens::Config for Test { + type RuntimeEvent = RuntimeEvent; + type Balance = Balance; + type Amount = Amount; + type CurrencyId = TokenId; + type WeightInfo = (); + type ExistentialDeposits = ExistentialDeposits; + type MaxLocks = MaxLocks; + type DustRemovalWhitelist = DustRemovalWhitelist; + type MaxReserves = (); + type ReserveIdentifier = [u8; 8]; + type CurrencyHooks = (); + type NontransferableTokens = Nothing; + type NontransferableTokensAllowList = Nothing; +} + +parameter_types! { + pub const NativeCurrencyId: u32 = NATIVE_CURRENCY_ID; + pub const TreasuryPalletId: PalletId = PalletId(*b"py/trsry"); + pub const BnbTreasurySubAccDerive: [u8; 4] = *b"bnbt"; +} + +pub struct MockValuateForNative {} +impl ValuateFor for MockValuateForNative {} +impl Valuate for MockValuateForNative { + type Balance = Balance; + type CurrencyId = TokenId; + + fn find_paired_pool( + base_id: Self::CurrencyId, + asset_id: Self::CurrencyId, + ) -> Result, DispatchError> { + unimplemented!() + } + + fn check_can_valuate(_: Self::CurrencyId, _: Self::CurrencyId) -> Result<(), DispatchError> { + unimplemented!() + } + + fn check_pool_exist(pool_id: Self::CurrencyId) -> Result<(), DispatchError> { + unimplemented!() + } + + fn get_reserve_and_lp_supply( + _: Self::CurrencyId, + pool_id: Self::CurrencyId, + ) -> Option<(Self::Balance, Self::Balance)> { + unimplemented!() + } + + fn get_valuation_for_paired( + _: Self::CurrencyId, + _: Self::CurrencyId, + amount: Self::Balance, + ) -> Self::Balance { + unimplemented!() + } + + fn find_valuation( + _: Self::CurrencyId, + _: Self::CurrencyId, + _: Self::Balance, + ) -> Result { + Ok(2000_u128) + } +} + +parameter_types! { + #[derive(PartialEq)] + pub const MaxCuratedTokens: u32 = 100; +} + +impl pallet_fee_lock::Config for Test { + type RuntimeEvent = RuntimeEvent; + type MaxCuratedTokens = MaxCuratedTokens; + type Tokens = orml_tokens::MultiTokenCurrencyAdapter; + type ValuateForNative = MockValuateForNative; + type NativeTokenId = NativeCurrencyId; + type WeightInfo = (); +} + +// This function basically just builds a genesis storage key/value store according to +// our desired mockup. +pub fn new_test_ext() -> sp_io::TestExternalities { + system::GenesisConfig::::default().build_storage().unwrap().into() +} + +pub struct ExtBuilder { + ext: sp_io::TestExternalities, +} + +impl ExtBuilder { + pub fn new() -> Self { + let t = frame_system::GenesisConfig::::default() + .build_storage() + .expect("Frame system builds valid default genesis config"); + + let ext = sp_io::TestExternalities::new(t); + Self { ext } + } + + pub fn create_token(mut self, token_id: TokenId) -> Self { + self.ext.execute_with(|| { + while token_id >= Tokens::next_asset_id() { + Tokens::create(RuntimeOrigin::root(), 0, 0).unwrap(); + } + }); + return self + } + + pub fn mint(mut self, who: AccountId, token_id: TokenId, balance: Balance) -> Self { + self.ext + .execute_with(|| Tokens::mint(RuntimeOrigin::root(), token_id, who, balance).unwrap()); + return self + } + + pub fn initialize_fee_locks(mut self, period: u64, lock_amount: u128, threshold: u128) -> Self { + self.ext.execute_with(|| { + FeeLock::update_fee_lock_metadata( + RuntimeOrigin::root(), + Some(period), + Some(lock_amount), + Some(threshold), + None, + ) + .unwrap() + }); + return self + } + + pub fn build(self) -> sp_io::TestExternalities { + self.ext + } +} + +pub fn fast_forward_blocks(count: u64) { + let now = System::block_number(); + for i in 0..count { + System::set_block_number(now + i + 1); + } +} diff --git a/gasp-node/pallets/fee-lock/src/tests.rs b/gasp-node/pallets/fee-lock/src/tests.rs new file mode 100644 index 000000000..b51943b7e --- /dev/null +++ b/gasp-node/pallets/fee-lock/src/tests.rs @@ -0,0 +1,1097 @@ +use super::*; +use crate::mock::*; +use frame_support::{assert_noop, assert_ok}; +use sp_std::convert::TryFrom; +use test_case::test_case; + +use orml_tokens::AccountData; +use sp_std::collections::btree_set::BTreeSet; + +#[test] +fn update_fee_lock_metadata_works() { + new_test_ext().execute_with(|| { + assert_noop!( + FeeLock::update_fee_lock_metadata( + RuntimeOrigin::root(), + Some(500), + Some(0), + Some(1000), + None, + ), + Error::::InvalidFeeLockMetadata + ); + + assert_noop!( + FeeLock::update_fee_lock_metadata( + RuntimeOrigin::root(), + Some(500), + None, + Some(1000), + None, + ), + Error::::InvalidFeeLockMetadata + ); + + assert_noop!( + FeeLock::update_fee_lock_metadata( + RuntimeOrigin::root(), + Some(0), + Some(500), + Some(1000), + None, + ), + Error::::InvalidFeeLockMetadata + ); + + assert_noop!( + FeeLock::update_fee_lock_metadata( + RuntimeOrigin::root(), + None, + Some(500), + Some(1000), + None, + ), + Error::::InvalidFeeLockMetadata + ); + + assert_ok!(FeeLock::update_fee_lock_metadata( + RuntimeOrigin::root(), + Some(1000), + Some(500), + Some(1000), + Some(vec![(0, true), (1, true)]), + )); + assert_eq!( + FeeLock::get_fee_lock_metadata(), + Some(FeeLockMetadataInfo { + period_length: 1000, + fee_lock_amount: 500, + swap_value_threshold: 1000, + whitelisted_tokens: { + BoundedBTreeSet::::try_from( + vec![0, 1].into_iter().collect::>(), + ) + .unwrap() + } + }) + ); + + assert_ok!(FeeLock::update_fee_lock_metadata( + RuntimeOrigin::root(), + Some(2000), + Some(2500), + Some(3000), + Some(vec![(0, false), (1, true), (2, true)]), + )); + assert_eq!( + FeeLock::get_fee_lock_metadata(), + Some(FeeLockMetadataInfo { + period_length: 2000, + fee_lock_amount: 2500, + swap_value_threshold: 3000, + whitelisted_tokens: { + BoundedBTreeSet::::try_from( + vec![1, 2].into_iter().collect::>(), + ) + .unwrap() + } + }) + ); + + assert_noop!( + FeeLock::update_fee_lock_metadata(RuntimeOrigin::root(), None, Some(0), None, None,), + Error::::InvalidFeeLockMetadata + ); + + assert_noop!( + FeeLock::update_fee_lock_metadata(RuntimeOrigin::root(), Some(0), None, None, None,), + Error::::InvalidFeeLockMetadata + ); + + assert_ok!(FeeLock::update_fee_lock_metadata( + RuntimeOrigin::root(), + None, + Some(8000), + None, + None + )); + assert_eq!( + FeeLock::get_fee_lock_metadata(), + Some(FeeLockMetadataInfo { + period_length: 2000, + fee_lock_amount: 8000, + swap_value_threshold: 3000, + whitelisted_tokens: { + BoundedBTreeSet::::try_from( + vec![1, 2].into_iter().collect::>(), + ) + .unwrap() + } + }) + ); + }) +} + +#[test] +fn process_fee_lock_trigger_works() { + new_test_ext().execute_with(|| { + let period_length = 10; + let fee_lock_amount = 1000; + let swap_value_threshold = 1000; + + let caller = 0u64; + let initial_amount: Balance = 2_000_000__u128; + let token_id: TokenId = + ::Tokens::create(&caller, initial_amount.into()).unwrap().into(); + + // The Native token id + assert_eq!(token_id, NATIVE_CURRENCY_ID); + + // We initaite the threshold map as empty as it is not required here + assert_ok!(FeeLock::update_fee_lock_metadata( + RuntimeOrigin::root(), + Some(period_length), + Some(fee_lock_amount), + Some(swap_value_threshold), + None, + )); + assert_eq!( + FeeLock::get_fee_lock_metadata(), + Some(FeeLockMetadataInfo { + period_length, + fee_lock_amount, + swap_value_threshold, + whitelisted_tokens: { + BoundedBTreeSet::::try_from( + vec![].into_iter().collect::>(), + ) + .unwrap() + } + }) + ); + + assert_eq!( + Tokens::accounts(0u64, token_id), + AccountData { free: 2_000_000__u128, reserved: 0__u128, frozen: 0__u128 } + ); + + let now = System::block_number(); + + assert_eq!(now, 0); + + // First timeout on empty user state + assert_ok!(>::process_fee_lock(&0u64)); + + assert_eq!( + Tokens::accounts(0u64, token_id), + AccountData { + free: 2_000_000__u128 - fee_lock_amount, + reserved: fee_lock_amount, + frozen: 0__u128, + } + ); + + assert_eq!( + FeeLock::get_account_fee_lock_data(0u64), + AccountFeeLockDataInfo { + total_fee_lock_amount: fee_lock_amount, + last_fee_lock_block: now, + } + ); + + // Second timeout on user state + assert_ok!(>::process_fee_lock(&0u64)); + + assert_eq!( + Tokens::accounts(0u64, token_id), + AccountData { + free: 2_000_000__u128 - 2 * fee_lock_amount, + reserved: 2 * fee_lock_amount, + frozen: 0__u128, + } + ); + + assert_eq!( + FeeLock::get_account_fee_lock_data(0u64), + AccountFeeLockDataInfo { + total_fee_lock_amount: 2 * fee_lock_amount, + last_fee_lock_block: now, + } + ); + + // Third timeout on user state + assert_ok!(>::process_fee_lock(&0u64)); + + assert_eq!( + Tokens::accounts(0u64, token_id), + AccountData { + free: 2_000_000__u128 - 3 * fee_lock_amount, + reserved: 3 * fee_lock_amount, + frozen: 0__u128, + } + ); + + assert_eq!( + FeeLock::get_account_fee_lock_data(0u64), + AccountFeeLockDataInfo { + total_fee_lock_amount: 3 * fee_lock_amount, + last_fee_lock_block: now, + } + ); + + // Into the next period + System::set_block_number(12); + + let now = System::block_number(); + + // First timeout in current period on Thrice timedout user state + assert_ok!(>::process_fee_lock(&0u64)); + + assert_eq!( + Tokens::accounts(0u64, token_id), + AccountData { + free: 2_000_000__u128 - 1 * fee_lock_amount, + reserved: 1 * fee_lock_amount, + frozen: 0__u128, + } + ); + + assert_eq!( + FeeLock::get_account_fee_lock_data(0u64), + AccountFeeLockDataInfo { + total_fee_lock_amount: 1 * fee_lock_amount, + last_fee_lock_block: now, + } + ); + + // Into the next period + System::set_block_number(22); + + let now = System::block_number(); + + // First timeout in current period on Once timedout user state + assert_ok!(>::process_fee_lock(&0u64)); + + assert_eq!( + Tokens::accounts(0u64, token_id), + AccountData { + free: 2_000_000__u128 - 1 * fee_lock_amount, + reserved: 1 * fee_lock_amount, + frozen: 0__u128, + } + ); + + assert_eq!( + FeeLock::get_account_fee_lock_data(0u64), + AccountFeeLockDataInfo { + total_fee_lock_amount: 1 * fee_lock_amount, + last_fee_lock_block: now, + } + ); + + // Second timeout + assert_ok!(>::process_fee_lock(&0u64)); + + assert_eq!( + Tokens::accounts(0u64, token_id), + AccountData { + free: 2_000_000__u128 - 2 * fee_lock_amount, + reserved: 2 * fee_lock_amount, + frozen: 0__u128, + } + ); + + assert_eq!( + FeeLock::get_account_fee_lock_data(0u64), + AccountFeeLockDataInfo { + total_fee_lock_amount: 2 * fee_lock_amount, + last_fee_lock_block: now, + } + ); + + // Into a far off period + System::set_block_number(22214); + + let now = System::block_number(); + + assert_ok!(>::process_fee_lock(&0u64)); + + assert_eq!( + Tokens::accounts(0u64, token_id), + AccountData { + free: 2_000_000__u128 - 1 * fee_lock_amount, + reserved: 1 * fee_lock_amount, + frozen: 0__u128, + } + ); + + assert_eq!( + FeeLock::get_account_fee_lock_data(0u64), + AccountFeeLockDataInfo { + total_fee_lock_amount: 1 * fee_lock_amount, + last_fee_lock_block: now, + } + ); + }) +} + +#[test] +fn unlock_fee_works() { + new_test_ext().execute_with(|| { + let period_length = 10; + let fee_lock_amount = 1000; + let swap_value_threshold = 1000; + + let caller = 0u64; + let initial_amount: Balance = 2_000_000__u128; + let token_id: TokenId = + ::Tokens::create(&caller, initial_amount.into()).unwrap().into(); + + // The Native token id + assert_eq!(token_id, NATIVE_CURRENCY_ID); + + // We initaite the threshold map as empty as it is not required here + assert_ok!(FeeLock::update_fee_lock_metadata( + RuntimeOrigin::root(), + Some(period_length), + Some(fee_lock_amount), + Some(swap_value_threshold), + None, + )); + assert_eq!( + FeeLock::get_fee_lock_metadata(), + Some(FeeLockMetadataInfo { + period_length, + fee_lock_amount, + swap_value_threshold, + whitelisted_tokens: { + BoundedBTreeSet::::try_from( + vec![].into_iter().collect::>(), + ) + .unwrap() + } + }) + ); + + assert_eq!( + Tokens::accounts(0u64, token_id), + AccountData { free: 2_000_000__u128, reserved: 0__u128, frozen: 0__u128 } + ); + + let now = System::block_number(); + + assert_eq!(now, 0); + + assert_noop!( + >::can_unlock_fee(&0u64), + Error::::NotFeeLocked + ); + assert_noop!( + FeeLock::unlock_fee(RuntimeOrigin::signed(0u64).into()), + Error::::NotFeeLocked + ); + + // First timeout on empty user state + assert_ok!(>::process_fee_lock(&0u64)); + + assert_eq!( + Tokens::accounts(0u64, token_id), + AccountData { + free: 2_000_000__u128 - fee_lock_amount, + reserved: fee_lock_amount, + frozen: 0__u128, + } + ); + + assert_eq!( + FeeLock::get_account_fee_lock_data(0u64), + AccountFeeLockDataInfo { + total_fee_lock_amount: fee_lock_amount, + last_fee_lock_block: now, + } + ); + + // Second timeout on user state + assert_ok!(>::process_fee_lock(&0u64)); + + assert_eq!( + Tokens::accounts(0u64, token_id), + AccountData { + free: 2_000_000__u128 - 2 * fee_lock_amount, + reserved: 2 * fee_lock_amount, + frozen: 0__u128, + } + ); + + assert_eq!( + FeeLock::get_account_fee_lock_data(0u64), + AccountFeeLockDataInfo { + total_fee_lock_amount: 2 * fee_lock_amount, + last_fee_lock_block: now, + } + ); + + // Third timeout on user state + assert_ok!(>::process_fee_lock(&0u64)); + + assert_eq!( + Tokens::accounts(0u64, token_id), + AccountData { + free: 2_000_000__u128 - 3 * fee_lock_amount, + reserved: 3 * fee_lock_amount, + frozen: 0__u128, + } + ); + + assert_eq!( + FeeLock::get_account_fee_lock_data(0u64), + AccountFeeLockDataInfo { + total_fee_lock_amount: 3 * fee_lock_amount, + last_fee_lock_block: now, + } + ); + + assert_noop!( + >::can_unlock_fee(&0u64), + Error::::CantUnlockFeeYet + ); + assert_noop!( + FeeLock::unlock_fee(RuntimeOrigin::signed(0u64).into()), + Error::::CantUnlockFeeYet + ); + + // Into the next period + System::set_block_number(12); + + let now = System::block_number(); + + assert_ok!(>::can_unlock_fee(&0u64)); + assert_ok!(FeeLock::unlock_fee(RuntimeOrigin::signed(0u64).into())); + + assert_eq!( + Tokens::accounts(0u64, token_id), + AccountData { + free: 2_000_000__u128 - 0 * fee_lock_amount, + reserved: 0 * fee_lock_amount, + frozen: 0__u128, + } + ); + + assert_eq!( + FeeLock::get_account_fee_lock_data(0u64), + AccountFeeLockDataInfo { + total_fee_lock_amount: 0 * fee_lock_amount, + last_fee_lock_block: 0 * now, + } + ); + + assert_noop!( + >::can_unlock_fee(&0u64), + Error::::NotFeeLocked + ); + assert_noop!( + FeeLock::unlock_fee(RuntimeOrigin::signed(0u64).into()), + Error::::NotFeeLocked + ); + + // First timeout in current period on Thrice timedout user state + assert_ok!(>::process_fee_lock(&0u64)); + + assert_eq!( + Tokens::accounts(0u64, token_id), + AccountData { + free: 2_000_000__u128 - 1 * fee_lock_amount, + reserved: 1 * fee_lock_amount, + frozen: 0__u128, + } + ); + + assert_eq!( + FeeLock::get_account_fee_lock_data(0u64), + AccountFeeLockDataInfo { + total_fee_lock_amount: 1 * fee_lock_amount, + last_fee_lock_block: now, + } + ); + + assert_noop!( + >::can_unlock_fee(&0u64), + Error::::CantUnlockFeeYet + ); + assert_noop!( + FeeLock::unlock_fee(RuntimeOrigin::signed(0u64).into()), + Error::::CantUnlockFeeYet + ); + + // Into the next period + System::set_block_number(22); + + let now = System::block_number(); + + // First timeout in current period on Once timedout user state + assert_ok!(>::process_fee_lock(&0u64)); + + assert_eq!( + Tokens::accounts(0u64, token_id), + AccountData { + free: 2_000_000__u128 - 1 * fee_lock_amount, + reserved: 1 * fee_lock_amount, + frozen: 0__u128, + } + ); + + assert_eq!( + FeeLock::get_account_fee_lock_data(0u64), + AccountFeeLockDataInfo { + total_fee_lock_amount: 1 * fee_lock_amount, + last_fee_lock_block: now, + } + ); + + assert_noop!( + >::can_unlock_fee(&0u64), + Error::::CantUnlockFeeYet + ); + assert_noop!( + FeeLock::unlock_fee(RuntimeOrigin::signed(0u64).into()), + Error::::CantUnlockFeeYet + ); + + // Second timeout + assert_ok!(>::process_fee_lock(&0u64)); + + assert_eq!( + Tokens::accounts(0u64, token_id), + AccountData { + free: 2_000_000__u128 - 2 * fee_lock_amount, + reserved: 2 * fee_lock_amount, + frozen: 0__u128, + } + ); + + assert_eq!( + FeeLock::get_account_fee_lock_data(0u64), + AccountFeeLockDataInfo { + total_fee_lock_amount: 2 * fee_lock_amount, + last_fee_lock_block: now, + } + ); + + assert_noop!( + >::can_unlock_fee(&0u64), + Error::::CantUnlockFeeYet + ); + assert_noop!( + FeeLock::unlock_fee(RuntimeOrigin::signed(0u64).into()), + Error::::CantUnlockFeeYet + ); + + // Into a far off period + System::set_block_number(22214); + + let now = System::block_number(); + + assert_ok!(>::can_unlock_fee(&0u64)); + assert_ok!(FeeLock::unlock_fee(RuntimeOrigin::signed(0u64).into())); + + assert_eq!( + Tokens::accounts(0u64, token_id), + AccountData { + free: 2_000_000__u128 - 0 * fee_lock_amount, + reserved: 0 * fee_lock_amount, + frozen: 0__u128, + } + ); + + assert_eq!( + FeeLock::get_account_fee_lock_data(0u64), + AccountFeeLockDataInfo { + total_fee_lock_amount: 0 * fee_lock_amount, + last_fee_lock_block: 0 * now, + } + ); + + assert_noop!( + >::can_unlock_fee(&0u64), + Error::::NotFeeLocked + ); + assert_noop!( + FeeLock::unlock_fee(RuntimeOrigin::signed(0u64).into()), + Error::::NotFeeLocked + ); + }) +} + +#[test] +// valuation removed, we are not testing mocks & external apis +fn whitelist_and_valuation_works() { + new_test_ext().execute_with(|| { + assert_ok!(FeeLock::update_fee_lock_metadata( + RuntimeOrigin::root(), + Some(1000), + Some(500), + Some(1000), + Some(vec![(1, true), (2, true)]), + )); + assert_eq!( + FeeLock::get_fee_lock_metadata(), + Some(FeeLockMetadataInfo { + period_length: 1000, + fee_lock_amount: 500, + swap_value_threshold: 1000, + whitelisted_tokens: { + BoundedBTreeSet::::try_from( + vec![1, 2].into_iter().collect::>(), + ) + .unwrap() + } + }) + ); + + // Native is always whitelisted + assert!(>::is_whitelisted(0)); + + assert!(>::is_whitelisted(1)); + assert!(>::is_whitelisted(2)); + + assert!(!>::is_whitelisted(3)); + }) +} + +const PERIOD_LENGTH: u64 = 10; +const FEE_LOCK_AMOUNT: u128 = 1000; +const SWAP_VALUE_THRESHOLD: u128 = 1000; +const ALICE: u64 = 0; +const BOB: u64 = 1; +const CHARLIE: u64 = 2; +const INITIAL_AMOUNT: Balance = 2_000_000__u128; +const UNLIMITED_WEIGHT: Weight = Weight::from_parts(u64::MAX, 0); +const ACCOUNT_WITHOUT_LOCKED_TOKENS: orml_tokens::AccountData = AccountData { + free: INITIAL_AMOUNT - 0 * FEE_LOCK_AMOUNT, + reserved: 0 * FEE_LOCK_AMOUNT, + frozen: 0u128, +}; +const ACCOUNT_WITH_LOCKED_TOKENS: orml_tokens::AccountData = AccountData { + free: INITIAL_AMOUNT - 1 * FEE_LOCK_AMOUNT, + reserved: 1 * FEE_LOCK_AMOUNT, + frozen: 0u128, +}; + +fn calculate_estimated_weight(unlock_fee_calls: u64, reads: u64, writes: u64) -> Weight { + ::DbWeight::get().reads(reads) + + ::DbWeight::get().writes(writes) + + (::WeightInfo::unlock_fee() * unlock_fee_calls) +} + +#[test_case( + UNLIMITED_WEIGHT, + calculate_estimated_weight(1, 6, 1), + ACCOUNT_WITHOUT_LOCKED_TOKENS; "unlocks tokens for an user")] +#[test_case( + Weight::from_parts(0, 0), + Weight::from_parts(0, 0), + ACCOUNT_WITH_LOCKED_TOKENS; "does not unlock tokens when weigh is zero")] +#[test_case( + calculate_estimated_weight(1, 6, 1), + calculate_estimated_weight(1, 6, 1), + ACCOUNT_WITHOUT_LOCKED_TOKENS; "unlock tokens using exact amount of weight required")] +#[test_case( + calculate_estimated_weight(1, 4, 1), + Weight::from_parts(0, 0), + ACCOUNT_WITH_LOCKED_TOKENS; "unlock tokens using a too small weight that required")] +#[test_case( + calculate_estimated_weight(1, 7, 1), + calculate_estimated_weight(1, 6, 1), + ACCOUNT_WITHOUT_LOCKED_TOKENS; "unlock tokens using a bit more weight that required")] +fn test_on_idle_unlock_for_single_user( + availabe_weight: Weight, + consumed_weight: Weight, + expected_account_data: AccountData, +) { + ExtBuilder::new() + .create_token(NativeCurrencyId::get()) + .mint(ALICE, NativeCurrencyId::get(), INITIAL_AMOUNT) + .initialize_fee_locks(PERIOD_LENGTH, FEE_LOCK_AMOUNT, SWAP_VALUE_THRESHOLD) + .build() + .execute_with(|| { + >::process_fee_lock(&ALICE).unwrap(); + fast_forward_blocks(PERIOD_LENGTH); + + // assert + assert_ok!(>::can_unlock_fee(&ALICE)); + assert_eq!( + Tokens::accounts(ALICE, NativeCurrencyId::get()), + AccountData { + free: INITIAL_AMOUNT - FEE_LOCK_AMOUNT, + reserved: 1 * FEE_LOCK_AMOUNT, + frozen: 0__u128, + } + ); + + assert_eq!(FeeLock::on_idle(System::block_number(), availabe_weight), consumed_weight); + + assert_eq!(Tokens::accounts(ALICE, NativeCurrencyId::get()), expected_account_data); + }); +} + +#[test_case( + Weight::from_parts(u64::MAX, 0), + calculate_estimated_weight(2, 9, 2), + vec![ + (ALICE, ACCOUNT_WITHOUT_LOCKED_TOKENS), + (BOB, ACCOUNT_WITHOUT_LOCKED_TOKENS), + ]; "unlocks tokens for both users with unlimited input weight")] +#[test_case( + calculate_estimated_weight(2, 9, 2), + calculate_estimated_weight(2, 9, 2), + vec![ + (ALICE, ACCOUNT_WITHOUT_LOCKED_TOKENS), + (BOB, ACCOUNT_WITHOUT_LOCKED_TOKENS), + ]; "unlocks tokens for both users using exact required weight ")] +#[test_case( + calculate_estimated_weight(1, 6, 1), + calculate_estimated_weight(1, 6, 1), + vec![ + (ALICE, ACCOUNT_WITHOUT_LOCKED_TOKENS), + (BOB, ACCOUNT_WITH_LOCKED_TOKENS), + ]; "unlocks tokens for single account only with limited weight")] +fn test_on_idle_unlock_multiple_users( + availabe_weight: Weight, + consumed_weight: Weight, + expected_account_data: Vec<(::AccountId, AccountData)>, +) { + ExtBuilder::new() + .create_token(NativeCurrencyId::get()) + .mint(ALICE, NativeCurrencyId::get(), INITIAL_AMOUNT) + .mint(BOB, NativeCurrencyId::get(), INITIAL_AMOUNT) + .initialize_fee_locks(PERIOD_LENGTH, FEE_LOCK_AMOUNT, SWAP_VALUE_THRESHOLD) + .build() + .execute_with(|| { + for (account, _) in expected_account_data.iter() { + >::process_fee_lock(account).unwrap(); + } + fast_forward_blocks(PERIOD_LENGTH); + + let consumed = FeeLock::on_idle(System::block_number(), availabe_weight); + + for data in expected_account_data { + assert_eq!(Tokens::accounts(data.0, NativeCurrencyId::get()), data.1); + } + + assert_eq!(consumed_weight, consumed); + }); +} + +#[test] +fn test_unlock_happens_not_sooner_but_after_period() { + ExtBuilder::new() + .create_token(NativeCurrencyId::get()) + .mint(ALICE, NativeCurrencyId::get(), INITIAL_AMOUNT) + .initialize_fee_locks(PERIOD_LENGTH, FEE_LOCK_AMOUNT, SWAP_VALUE_THRESHOLD) + .build() + .execute_with(|| { + // lets move to some block that is not aligned with period start + fast_forward_blocks(7); + >::process_fee_lock(&ALICE).unwrap(); + + for _ in 0..PERIOD_LENGTH - 1 { + fast_forward_blocks(1); + FeeLock::on_idle(System::block_number(), UNLIMITED_WEIGHT); + assert_eq!( + Tokens::accounts(ALICE, NativeCurrencyId::get()), + ACCOUNT_WITH_LOCKED_TOKENS + ); + } + + // lock period ends now + fast_forward_blocks(1); + + FeeLock::on_idle(System::block_number(), UNLIMITED_WEIGHT); + assert_eq!( + Tokens::accounts(ALICE, NativeCurrencyId::get()), + ACCOUNT_WITHOUT_LOCKED_TOKENS + ); + }); +} + +#[test] +fn test_unlock_stops_after_single_iteration_without_consuming_unnecessary_weight() { + ExtBuilder::new() + .create_token(NativeCurrencyId::get()) + .mint(ALICE, NativeCurrencyId::get(), INITIAL_AMOUNT) + .mint(BOB, NativeCurrencyId::get(), INITIAL_AMOUNT) + .mint(CHARLIE, NativeCurrencyId::get(), INITIAL_AMOUNT) + .initialize_fee_locks(PERIOD_LENGTH, FEE_LOCK_AMOUNT, SWAP_VALUE_THRESHOLD) + .build() + .execute_with(|| { + // lets move to some block that is not aligned with period start + fast_forward_blocks(3); + >::process_fee_lock(&ALICE).unwrap(); + >::process_fee_lock(&BOB).unwrap(); + >::process_fee_lock(&CHARLIE).unwrap(); + + fast_forward_blocks(3); + let consumed_weight = FeeLock::on_idle(System::block_number(), UNLIMITED_WEIGHT); + assert_eq!(consumed_weight, calculate_estimated_weight(0, 6, 1)); + + assert_eq!( + Tokens::accounts(ALICE, NativeCurrencyId::get()), + ACCOUNT_WITH_LOCKED_TOKENS + ); + }); +} + +#[test] +fn test_autounlock_on_empty_unlock_queue() { + ExtBuilder::new() + .initialize_fee_locks(PERIOD_LENGTH, FEE_LOCK_AMOUNT, SWAP_VALUE_THRESHOLD) + .build() + .execute_with(|| { + FeeLock::on_idle(System::block_number(), UNLIMITED_WEIGHT); + }); +} + +#[test] +fn test_maintain_queue_with_subsequent_fee_locks_on_single_account() { + ExtBuilder::new() + .create_token(NativeCurrencyId::get()) + .mint(ALICE, NativeCurrencyId::get(), INITIAL_AMOUNT) + .mint(BOB, NativeCurrencyId::get(), INITIAL_AMOUNT) + .mint(CHARLIE, NativeCurrencyId::get(), INITIAL_AMOUNT) + .initialize_fee_locks(PERIOD_LENGTH, FEE_LOCK_AMOUNT, SWAP_VALUE_THRESHOLD) + .build() + .execute_with(|| { + fast_forward_blocks(3); + assert_eq!(UnlockQueue::::get(0), None); + assert_eq!(UnlockQueue::::get(1), None); + assert_eq!(UnlockQueue::::get(2), None); + assert_eq!(FeeLockMetadataQeueuePosition::::get(ALICE), None); + assert_eq!(FeeLockMetadataQeueuePosition::::get(BOB), None); + assert_eq!(UnlockQueueBegin::::get(), 0); + assert_eq!(UnlockQueueEnd::::get(), 0); + >::process_fee_lock(&ALICE).unwrap(); + + assert_eq!(UnlockQueue::::get(0), Some(ALICE)); + assert_eq!(UnlockQueue::::get(1), None); + assert_eq!(UnlockQueue::::get(2), None); + assert_eq!(FeeLockMetadataQeueuePosition::::get(ALICE), Some(0)); + assert_eq!(FeeLockMetadataQeueuePosition::::get(BOB), None); + assert_eq!(UnlockQueueBegin::::get(), 0); + assert_eq!(UnlockQueueEnd::::get(), 1); + + fast_forward_blocks(1); + >::process_fee_lock(&BOB).unwrap(); + assert_eq!(UnlockQueue::::get(0), Some(ALICE)); + assert_eq!(UnlockQueue::::get(1), Some(BOB)); + assert_eq!(UnlockQueue::::get(2), None); + assert_eq!(FeeLockMetadataQeueuePosition::::get(ALICE), Some(0)); + assert_eq!(FeeLockMetadataQeueuePosition::::get(BOB), Some(1)); + assert_eq!(UnlockQueueBegin::::get(), 0); + assert_eq!(UnlockQueueEnd::::get(), 2); + + fast_forward_blocks(1); + >::process_fee_lock(&ALICE).unwrap(); + assert_eq!(UnlockQueue::::get(0), None); + assert_eq!(UnlockQueue::::get(1), Some(BOB)); + assert_eq!(UnlockQueue::::get(2), Some(ALICE)); + assert_eq!(FeeLockMetadataQeueuePosition::::get(ALICE), Some(2)); + assert_eq!(FeeLockMetadataQeueuePosition::::get(BOB), Some(1)); + assert_eq!(UnlockQueueBegin::::get(), 0); + assert_eq!(UnlockQueueEnd::::get(), 3); + }); +} + +#[test] +fn test_process_queue_and_ignore_outdated_items_in_unlock_queue_because_of_subsequent_process_fee_lock_calls( +) { + ExtBuilder::new() + .create_token(NativeCurrencyId::get()) + .mint(ALICE, NativeCurrencyId::get(), INITIAL_AMOUNT) + .mint(BOB, NativeCurrencyId::get(), INITIAL_AMOUNT) + .mint(CHARLIE, NativeCurrencyId::get(), INITIAL_AMOUNT) + .initialize_fee_locks(PERIOD_LENGTH, FEE_LOCK_AMOUNT, SWAP_VALUE_THRESHOLD) + .build() + .execute_with(|| { + fast_forward_blocks(3); + assert_eq!(UnlockQueueBegin::::get(), 0); + assert_eq!(UnlockQueueEnd::::get(), 0); + + >::process_fee_lock(&ALICE).unwrap(); + assert_eq!(UnlockQueueBegin::::get(), 0); + assert_eq!(UnlockQueueEnd::::get(), 1); + fast_forward_blocks(1); + + >::process_fee_lock(&BOB).unwrap(); + assert_eq!(UnlockQueueBegin::::get(), 0); + assert_eq!(UnlockQueueEnd::::get(), 2); + fast_forward_blocks(1); + + FeeLock::on_idle(System::block_number(), UNLIMITED_WEIGHT); + assert_eq!(UnlockQueueBegin::::get(), 0); + assert_eq!(UnlockQueueEnd::::get(), 2); + + >::process_fee_lock(&ALICE).unwrap(); + assert_eq!(UnlockQueueBegin::::get(), 0); + assert_eq!(UnlockQueueEnd::::get(), 3); + + // outdated queue item was consumed + FeeLock::on_idle(System::block_number(), UNLIMITED_WEIGHT); + assert_eq!(UnlockQueueBegin::::get(), 1); + assert_eq!(UnlockQueueEnd::::get(), 3); + }); +} + +#[test] +fn test_process_queue_and_ignore_outdated_items_in_unlock_queue_because_of_manual_unlock() { + ExtBuilder::new() + .create_token(NativeCurrencyId::get()) + .mint(ALICE, NativeCurrencyId::get(), INITIAL_AMOUNT) + .mint(BOB, NativeCurrencyId::get(), INITIAL_AMOUNT) + .mint(CHARLIE, NativeCurrencyId::get(), INITIAL_AMOUNT) + .initialize_fee_locks(PERIOD_LENGTH, FEE_LOCK_AMOUNT, SWAP_VALUE_THRESHOLD) + .build() + .execute_with(|| { + fast_forward_blocks(3); + assert_eq!(UnlockQueueBegin::::get(), 0); + assert_eq!(UnlockQueueEnd::::get(), 0); + + >::process_fee_lock(&ALICE).unwrap(); + assert_eq!(UnlockQueueBegin::::get(), 0); + assert_eq!(UnlockQueueEnd::::get(), 1); + fast_forward_blocks(PERIOD_LENGTH / 2); + + >::process_fee_lock(&BOB).unwrap(); + assert_eq!(UnlockQueueBegin::::get(), 0); + assert_eq!(UnlockQueueEnd::::get(), 2); + + FeeLock::on_idle(System::block_number(), UNLIMITED_WEIGHT); + assert_eq!(UnlockQueueBegin::::get(), 0); + assert_eq!(UnlockQueueEnd::::get(), 2); + + fast_forward_blocks(PERIOD_LENGTH / 2); + FeeLock::unlock_fee(RuntimeOrigin::signed(ALICE).into()).unwrap(); + assert_eq!(UnlockQueueBegin::::get(), 0); + assert_eq!(UnlockQueueEnd::::get(), 2); + assert_eq!( + Tokens::accounts(ALICE, NativeCurrencyId::get()), + ACCOUNT_WITHOUT_LOCKED_TOKENS + ); + + // nothing to unlock, but increment UnlockQueueBegin counter + FeeLock::on_idle(System::block_number(), UNLIMITED_WEIGHT); + assert_eq!(UnlockQueueBegin::::get(), 1); + assert_eq!(UnlockQueueEnd::::get(), 2); + + assert_eq!(Tokens::accounts(BOB, NativeCurrencyId::get()), ACCOUNT_WITH_LOCKED_TOKENS); + }); +} + +#[test] +fn test_unlock_happens_in_order() { + ExtBuilder::new() + .create_token(NativeCurrencyId::get()) + .mint(ALICE, NativeCurrencyId::get(), INITIAL_AMOUNT) + .mint(BOB, NativeCurrencyId::get(), INITIAL_AMOUNT) + .mint(CHARLIE, NativeCurrencyId::get(), INITIAL_AMOUNT) + .initialize_fee_locks(PERIOD_LENGTH, FEE_LOCK_AMOUNT, SWAP_VALUE_THRESHOLD) + .build() + .execute_with(|| { + let weight_for_single_unlock: Weight = calculate_estimated_weight(1, 6, 1); + + >::process_fee_lock(&ALICE).unwrap(); + >::process_fee_lock(&BOB).unwrap(); + >::process_fee_lock(&CHARLIE).unwrap(); + assert_eq!( + Tokens::accounts(ALICE, NativeCurrencyId::get()), + ACCOUNT_WITH_LOCKED_TOKENS + ); + assert_eq!(Tokens::accounts(BOB, NativeCurrencyId::get()), ACCOUNT_WITH_LOCKED_TOKENS); + assert_eq!( + Tokens::accounts(CHARLIE, NativeCurrencyId::get()), + ACCOUNT_WITH_LOCKED_TOKENS + ); + + fast_forward_blocks(PERIOD_LENGTH); + FeeLock::on_idle(System::block_number(), weight_for_single_unlock); + + assert_eq!( + Tokens::accounts(ALICE, NativeCurrencyId::get()), + ACCOUNT_WITHOUT_LOCKED_TOKENS + ); + assert_eq!(Tokens::accounts(BOB, NativeCurrencyId::get()), ACCOUNT_WITH_LOCKED_TOKENS); + assert_eq!( + Tokens::accounts(CHARLIE, NativeCurrencyId::get()), + ACCOUNT_WITH_LOCKED_TOKENS + ); + + FeeLock::on_idle(System::block_number(), weight_for_single_unlock); + assert_eq!( + Tokens::accounts(ALICE, NativeCurrencyId::get()), + ACCOUNT_WITHOUT_LOCKED_TOKENS + ); + assert_eq!( + Tokens::accounts(BOB, NativeCurrencyId::get()), + ACCOUNT_WITHOUT_LOCKED_TOKENS + ); + assert_eq!( + Tokens::accounts(CHARLIE, NativeCurrencyId::get()), + ACCOUNT_WITH_LOCKED_TOKENS + ); + + FeeLock::on_idle(System::block_number(), weight_for_single_unlock); + assert_eq!( + Tokens::accounts(ALICE, NativeCurrencyId::get()), + ACCOUNT_WITHOUT_LOCKED_TOKENS + ); + assert_eq!( + Tokens::accounts(BOB, NativeCurrencyId::get()), + ACCOUNT_WITHOUT_LOCKED_TOKENS + ); + assert_eq!( + Tokens::accounts(CHARLIE, NativeCurrencyId::get()), + ACCOUNT_WITHOUT_LOCKED_TOKENS + ); + }); +} + +#[test] +fn test_queue_storage_is_cleaned_up() { + ExtBuilder::new() + .create_token(NativeCurrencyId::get()) + .mint(ALICE, NativeCurrencyId::get(), INITIAL_AMOUNT) + .mint(BOB, NativeCurrencyId::get(), INITIAL_AMOUNT) + .initialize_fee_locks(PERIOD_LENGTH, FEE_LOCK_AMOUNT, SWAP_VALUE_THRESHOLD) + .build() + .execute_with(|| { + assert_eq!(UnlockQueue::::get(0), None); + assert_eq!(UnlockQueue::::get(1), None); + + >::process_fee_lock(&ALICE).unwrap(); + assert_eq!(UnlockQueue::::get(0), Some(ALICE)); + assert_eq!(UnlockQueue::::get(1), None); + assert_eq!( + Tokens::accounts(ALICE, NativeCurrencyId::get()), + ACCOUNT_WITH_LOCKED_TOKENS + ); + + >::process_fee_lock(&BOB).unwrap(); + assert_eq!(UnlockQueue::::get(0), Some(ALICE)); + assert_eq!(UnlockQueue::::get(1), Some(BOB)); + assert_eq!(Tokens::accounts(BOB, NativeCurrencyId::get()), ACCOUNT_WITH_LOCKED_TOKENS); + + fast_forward_blocks(PERIOD_LENGTH); + FeeLock::on_idle(System::block_number(), Weight::from_parts(u64::MAX, 0)); + + assert_eq!( + Tokens::accounts(ALICE, NativeCurrencyId::get()), + ACCOUNT_WITHOUT_LOCKED_TOKENS + ); + assert_eq!( + Tokens::accounts(BOB, NativeCurrencyId::get()), + ACCOUNT_WITHOUT_LOCKED_TOKENS + ); + + assert_eq!(UnlockQueue::::get(0), None); + assert_eq!(UnlockQueue::::get(1), None); + }); +} diff --git a/gasp-node/pallets/fee-lock/src/weights.rs b/gasp-node/pallets/fee-lock/src/weights.rs new file mode 100644 index 000000000..e236d40b7 --- /dev/null +++ b/gasp-node/pallets/fee-lock/src/weights.rs @@ -0,0 +1,111 @@ +// This file is part of Mangata. + +// Copyright (C) 2020-2022 Mangata Foundation. +// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 + +// This program 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. + +// This program 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 this program. If not, see . + +//! Autogenerated weights for pallet_fee_lock +//! +//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 32.0.0 +//! DATE: 2024-11-07, STEPS: `2`, REPEAT: 2, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! EXECUTION: , WASM-EXECUTION: Compiled, CHAIN: Some("rollup-local"), DB CACHE: 1024 + +// Executed Command: +// /home/striker/work/mangata-ws/mangata-node/scripts/..//target/release/rollup-node +// benchmark +// pallet +// --chain +// rollup-local +// --execution +// wasm +// --wasm-execution +// compiled +// --pallet +// pallet_fee_lock +// --extrinsic +// * +// --steps +// 2 +// --repeat +// 2 +// --output +// ./benchmarks/pallet_fee_lock_weights.rs +// --template +// ./templates/module-weight-template.hbs + +#![cfg_attr(rustfmt, rustfmt_skip)] +#![allow(unused_parens)] +#![allow(unused_imports)] +#![allow(clippy::unnecessary_cast)] + +use frame_support::{traits::Get, weights::{Weight, constants::RocksDbWeight}}; +use sp_std::marker::PhantomData; + +/// Weight functions needed for pallet_fee_lock. +pub trait WeightInfo { + fn process_fee_lock() -> Weight; + fn get_swap_valuation_for_token() -> Weight; + fn update_fee_lock_metadata() -> Weight; + fn unlock_fee() -> Weight; +} + +// For backwards compatibility and tests +impl WeightInfo for () { + // Storage: `FeeLock::FeeLockMetadata` (r:1 w:0) + // Proof: `FeeLock::FeeLockMetadata` (`max_values`: Some(1), `max_size`: Some(438), added: 933, mode: `MaxEncodedLen`) + // Storage: `FeeLock::AccountFeeLockData` (r:1 w:1) + // Proof: `FeeLock::AccountFeeLockData` (`max_values`: None, `max_size`: Some(48), added: 2523, mode: `MaxEncodedLen`) + // Storage: `Tokens::Accounts` (r:1 w:1) + // Proof: `Tokens::Accounts` (`max_values`: None, `max_size`: Some(96), added: 2571, mode: `MaxEncodedLen`) + // Storage: `FeeLock::FeeLockMetadataQeueuePosition` (r:1 w:1) + // Proof: `FeeLock::FeeLockMetadataQeueuePosition` (`max_values`: None, `max_size`: Some(44), added: 2519, mode: `MaxEncodedLen`) + // Storage: `FeeLock::UnlockQueue` (r:1 w:2) + // Proof: `FeeLock::UnlockQueue` (`max_values`: None, `max_size`: Some(44), added: 2519, mode: `MaxEncodedLen`) + // Storage: `FeeLock::UnlockQueueEnd` (r:1 w:1) + // Proof: `FeeLock::UnlockQueueEnd` (`max_values`: Some(1), `max_size`: Some(16), added: 511, mode: `MaxEncodedLen`) + fn process_fee_lock() -> Weight { + (Weight::from_parts(39_182_000, 0)) + .saturating_add(RocksDbWeight::get().reads(6 as u64)) + .saturating_add(RocksDbWeight::get().writes(6 as u64)) + } + // Storage: `Xyk::Pools` (r:2 w:0) + // Proof: `Xyk::Pools` (`max_values`: None, `max_size`: Some(56), added: 2531, mode: `MaxEncodedLen`) + fn get_swap_valuation_for_token() -> Weight { + (Weight::from_parts(10_686_000, 0)) + .saturating_add(RocksDbWeight::get().reads(2 as u64)) + } + // Storage: `FeeLock::FeeLockMetadata` (r:1 w:1) + // Proof: `FeeLock::FeeLockMetadata` (`max_values`: Some(1), `max_size`: Some(438), added: 933, mode: `MaxEncodedLen`) + fn update_fee_lock_metadata() -> Weight { + (Weight::from_parts(22_559_000, 0)) + .saturating_add(RocksDbWeight::get().reads(1 as u64)) + .saturating_add(RocksDbWeight::get().writes(1 as u64)) + } + // Storage: `FeeLock::AccountFeeLockData` (r:1 w:1) + // Proof: `FeeLock::AccountFeeLockData` (`max_values`: None, `max_size`: Some(48), added: 2523, mode: `MaxEncodedLen`) + // Storage: `FeeLock::FeeLockMetadata` (r:1 w:0) + // Proof: `FeeLock::FeeLockMetadata` (`max_values`: Some(1), `max_size`: Some(438), added: 933, mode: `MaxEncodedLen`) + // Storage: `Tokens::Accounts` (r:1 w:1) + // Proof: `Tokens::Accounts` (`max_values`: None, `max_size`: Some(96), added: 2571, mode: `MaxEncodedLen`) + // Storage: `FeeLock::FeeLockMetadataQeueuePosition` (r:1 w:1) + // Proof: `FeeLock::FeeLockMetadataQeueuePosition` (`max_values`: None, `max_size`: Some(44), added: 2519, mode: `MaxEncodedLen`) + // Storage: `FeeLock::UnlockQueue` (r:1 w:1) + // Proof: `FeeLock::UnlockQueue` (`max_values`: None, `max_size`: Some(44), added: 2519, mode: `MaxEncodedLen`) + fn unlock_fee() -> Weight { + (Weight::from_parts(39_182_000, 0)) + .saturating_add(RocksDbWeight::get().reads(5 as u64)) + .saturating_add(RocksDbWeight::get().writes(4 as u64)) + } +} diff --git a/gasp-node/pallets/issuance/Cargo.toml b/gasp-node/pallets/issuance/Cargo.toml new file mode 100644 index 000000000..3eebce11b --- /dev/null +++ b/gasp-node/pallets/issuance/Cargo.toml @@ -0,0 +1,67 @@ +[package] +authors = ["Mangata Team"] +edition = "2018" +license = "Unlicense" +name = "pallet-issuance" +version = "2.0.0" + +[package.metadata.docs.rs] +targets = ["x86_64-unknown-linux-gnu"] + +[dependencies] +cfg-if = "1.0.0" +codec = { workspace = true, default-features = false } +log = { workspace = true, default-features = false } +serde = { workspace = true, optional = true } +scale-info = { workspace = true, default-features = false, features = ["derive"] } + +frame-benchmarking = { workspace = true, default-features = false, optional = true } +frame-executive = { workspace = true, default-features = false } +frame-support = { workspace = true, default-features = false } +frame-system = { workspace = true, default-features = false } +frame-try-runtime = { workspace = true, default-features = false, optional = true } +mangata-support = { workspace = true, default-features = false } +mangata-types = { workspace = true, default-features = false } +pallet-vesting-mangata = { workspace = true, default-features = false } +sp-std = { workspace = true, default-features = false } +sp-runtime = { workspace = true, default-features = false } +sp-core = { workspace = true, default-features = false } + +orml-tokens = { workspace = true, default-features = false } + +[dev-dependencies] +lazy_static.workspace = true + +sp-io = { workspace = true, default-features = false } + +orml-traits = { workspace = true, default-features = false } + +[features] +default = ["std"] +std = [ + "codec/std", + "frame-support/std", + "frame-system/std", + "frame-benchmarking/std", + "mangata-support/std", + "orml-tokens/std", + "pallet-vesting-mangata/std", + "serde", + "sp-core/std", + "sp-runtime/std", + "sp-std/std", +] +runtime-benchmarks = [ + "frame-benchmarking/runtime-benchmarks", + "frame-support/runtime-benchmarks", + "frame-system/runtime-benchmarks", +] +try-runtime = [ + "frame-executive/try-runtime", + "frame-support/try-runtime", + "frame-system/try-runtime", + "frame-try-runtime", + "orml-tokens/try-runtime", + "pallet-vesting-mangata/try-runtime", + "sp-runtime/try-runtime", +] diff --git a/gasp-node/pallets/issuance/src/benchmarking.rs b/gasp-node/pallets/issuance/src/benchmarking.rs new file mode 100644 index 000000000..271213b5d --- /dev/null +++ b/gasp-node/pallets/issuance/src/benchmarking.rs @@ -0,0 +1,120 @@ +// This file is part of Substrate. + +// Copyright (C) 2020-2022 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(feature = "runtime-benchmarks")] + +use super::*; + +use frame_benchmarking::{account, benchmarks}; +use frame_support::assert_ok; +use frame_system::RawOrigin; +use orml_tokens::MultiTokenCurrencyExtended; + +use crate::{Pallet as Issuance, TgeInfo}; + +const SEED: u32 = u32::max_value(); +const TGE_AMOUNT: u32 = 1_000_000; + +fn create_tge_infos(x: u32) -> Vec>> { + let mut tge_infos: Vec>> = Vec::new(); + for i in 0..x { + tge_infos.push(TgeInfo { who: account("who", 0, SEED - i), amount: TGE_AMOUNT.into() }); + } + tge_infos +} + +benchmarks! { + init_issuance_config { + assert!(!IsTGEFinalized::::get()); + assert_ok!(Issuance::::finalize_tge(RawOrigin::Root.into())); + assert!( + IssuanceConfigStore::::get().is_none() + ); + + }: _(RawOrigin::Root) + verify { + + assert!( + IssuanceConfigStore::::get().is_some() + ); + + } + + finalize_tge { + assert!(!IsTGEFinalized::::get()); + assert!( + IssuanceConfigStore::::get().is_none() + ); + }: _(RawOrigin::Root) + verify { + assert!(IsTGEFinalized::::get()); + assert!( + IssuanceConfigStore::::get().is_none() + ); + } + + execute_tge { + let x in 3..100; + + assert!(!IsTGEFinalized::::get()); + assert!( + IssuanceConfigStore::::get().is_none() + ); + + let tge_infos = create_tge_infos::(x); + + }: _(RawOrigin::Root, tge_infos.clone()) + verify { + assert!(!IsTGEFinalized::::get()); + assert!( + IssuanceConfigStore::::get().is_none() + ); + + assert_eq!(TGETotal::::get(), tge_infos.iter().fold(BalanceOf::::zero(), |acc: BalanceOf, tge_info| acc.saturating_add(tge_info.amount))); + + let lock_percent = Percent::from_percent(100) + .checked_sub(&T::ImmediateTGEReleasePercent::get()).unwrap(); + + for tge_info in tge_infos{ + + if lock_percent.is_zero(){ + assert_eq!(T::Tokens::free_balance(T::NativeCurrencyId::get(), &tge_info.who), tge_info.amount); + }else{ + assert_eq!(T::VestingProvider::vesting_balance(&tge_info.who, T::NativeCurrencyId::get()).unwrap(), lock_percent * tge_info.amount); + assert_eq!(T::Tokens::free_balance(T::NativeCurrencyId::get(), &tge_info.who), tge_info.amount); + assert_eq!(>::locked_balance(T::NativeCurrencyId::get(), &tge_info.who), lock_percent * tge_info.amount); + } + } + + } + + set_issuance_config { + let new_linear_issuance_amount: BalanceOf = 10_000_000_000u128.try_into().ok().expect("balance"); + + assert_ok!(Issuance::::finalize_tge(RawOrigin::Root.into())); + assert_ok!(Issuance::::init_issuance_config(RawOrigin::Root.into())); + + assert!(IssuanceConfigStore::::get().unwrap().linear_issuance_amount != new_linear_issuance_amount); + + }: _(RawOrigin::Root, Some(new_linear_issuance_amount), None, None, None, None) + verify { + assert_eq!(IssuanceConfigStore::::get().unwrap().linear_issuance_amount, new_linear_issuance_amount); + } + + + impl_benchmark_test_suite!(Issuance, crate::mock::new_test_ext_without_issuance_config(), crate::mock::Test) +} diff --git a/gasp-node/pallets/issuance/src/lib.rs b/gasp-node/pallets/issuance/src/lib.rs new file mode 100644 index 000000000..7abc1eb85 --- /dev/null +++ b/gasp-node/pallets/issuance/src/lib.rs @@ -0,0 +1,504 @@ +// Copyright (C) 2020 Mangata team + +#![cfg_attr(not(feature = "std"), no_std)] + +use codec::{Decode, Encode}; +use frame_support::{ + pallet_prelude::*, + traits::{tokens::currency::MultiTokenCurrency, Get, Imbalance, MultiTokenVestingSchedule}, +}; +use frame_system::pallet_prelude::*; +use mangata_support::traits::{ComputeIssuance, GetIssuance, LiquidityMiningApi}; +use orml_tokens::MultiTokenCurrencyExtended; +use scale_info::TypeInfo; +use sp_runtime::{ + traits::{CheckedAdd, CheckedDiv, CheckedSub, One, Saturating, Zero}, + Perbill, Percent, RuntimeDebug, +}; +use sp_std::{convert::TryInto, prelude::*}; + +#[cfg(test)] +mod mock; + +#[cfg(test)] +mod tests; + +mod benchmarking; + +#[derive(Encode, Decode, Clone, Default, RuntimeDebug, PartialEq, Eq, TypeInfo)] +pub struct IssuanceInfo { + // Max number of MGA to target + pub linear_issuance_amount: Balance, + // MGA created at token generation event + // We aasume that there is only one tge + pub issuance_at_init: Balance, + // Time between which the total issuance of MGA grows to cap, as number of blocks + pub linear_issuance_blocks: u32, + // The split of issuance assgined to liquidity_mining + pub liquidity_mining_split: Perbill, + // The split of issuance assgined to staking/collators + pub staking_split: Perbill, + // The split of issuance assgined to sequencers + pub sequencers_split: Perbill, + // The total mga allocated to crowdloan rewards + pub total_crowdloan_allocation: Balance, +} + +#[derive(Encode, Decode, Clone, Default, RuntimeDebug, PartialEq, Eq, TypeInfo)] +pub struct TgeInfo { + // The tge target + pub who: AccountId, + // Amount distributed at tge + pub amount: Balance, +} + +pub mod weights; +pub use pallet::*; +pub use weights::WeightInfo; + +type BalanceOf = + <::Tokens as MultiTokenCurrency<::AccountId>>::Balance; + +type CurrencyIdOf = <::Tokens as MultiTokenCurrency< + ::AccountId, +>>::CurrencyId; + +#[frame_support::pallet] +pub mod pallet { + + use super::*; + + #[pallet::pallet] + #[pallet::without_storage_info] + pub struct Pallet(PhantomData); + + #[pallet::hooks] + impl Hooks> for Pallet {} + + /// Configure the pallet by specifying the parameters and types on which it depends. + #[pallet::config] + pub trait Config: frame_system::Config { + /// Because this pallet emits events, it depends on the runtime's definition of an event. + type RuntimeEvent: From> + IsType<::RuntimeEvent>; + /// MGA currency to check total_issuance + type NativeCurrencyId: Get>; + /// Tokens + type Tokens: MultiTokenCurrencyExtended; + /// Number of blocks per session/round + #[pallet::constant] + type BlocksPerRound: Get; + /// Number of sessions to store issuance history for + #[pallet::constant] + type HistoryLimit: Get; + #[pallet::constant] + /// The account id that holds the liquidity mining issuance + type LiquidityMiningIssuanceVault: Get; + #[pallet::constant] + /// The account id that holds the staking issuance + type StakingIssuanceVault: Get; + #[pallet::constant] + /// The account id that holds the sequencers issuance + type SequencersIssuanceVault: Get; + #[pallet::constant] + /// The total mga allocated for crowdloans + type TotalCrowdloanAllocation: Get>; + #[pallet::constant] + /// The maximum amount of Mangata tokens + type ImmediateTGEReleasePercent: Get; + #[pallet::constant] + /// The maximum amount of Mangata tokens + type LinearIssuanceAmount: Get>; + #[pallet::constant] + /// The number of blocks the issuance is linear + type LinearIssuanceBlocks: Get; + #[pallet::constant] + /// The split of issuance for liquidity mining rewards + type LiquidityMiningSplit: Get; + #[pallet::constant] + /// The split of issuance for staking rewards + type StakingSplit: Get; + #[pallet::constant] + /// The split of issuance for sequencers rewards + type SequencersSplit: Get; + #[pallet::constant] + /// The number of blocks the tge tokens vest for + type TGEReleasePeriod: Get; + #[pallet::constant] + /// The block at which the tge tokens begin to vest + type TGEReleaseBegin: Get; + /// The vesting pallet + type VestingProvider: MultiTokenVestingSchedule< + Self::AccountId, + Currency = Self::Tokens, + Moment = BlockNumberFor, + >; + type WeightInfo: WeightInfo; + type LiquidityMiningApi: LiquidityMiningApi>; + } + + #[pallet::storage] + #[pallet::getter(fn get_issuance_config)] + pub type IssuanceConfigStore = + StorageValue<_, IssuanceInfo>, OptionQuery>; + + #[pallet::storage] + #[pallet::getter(fn get_tge_total)] + pub type TGETotal = StorageValue<_, BalanceOf, ValueQuery>; + + #[pallet::storage] + #[pallet::getter(fn is_tge_finalized)] + pub type IsTGEFinalized = StorageValue<_, bool, ValueQuery>; + + #[pallet::storage] + #[pallet::getter(fn get_session_issuance)] + pub type SessionIssuance = StorageMap< + _, + Twox64Concat, + u32, + Option<(BalanceOf, BalanceOf, BalanceOf)>, + ValueQuery, + >; + + #[pallet::error] + /// Errors + pub enum Error { + /// The issuance config has already been initialized + IssuanceConfigAlreadyInitialized, + /// The issuance config has not been initialized + IssuanceConfigNotInitialized, + /// TGE must be finalized before issuance config is inti + TGENotFinalized, + /// The TGE is already finalized + TGEIsAlreadyFinalized, + /// The issuance config is invalid + IssuanceConfigInvalid, + /// An underflow or an overflow has occured + MathError, + /// unknown pool + UnknownPool, + /// The issuance config has not been initialized + InvalidSplitAmounts, + } + + // XYK extrinsics. + #[pallet::call] + impl Pallet { + #[pallet::call_index(0)] + #[pallet::weight(T::WeightInfo::init_issuance_config())] + pub fn init_issuance_config(origin: OriginFor) -> DispatchResultWithPostInfo { + ensure_root(origin)?; + Self::do_init_issuance_config() + } + + #[pallet::call_index(1)] + #[pallet::weight(T::WeightInfo::finalize_tge())] + pub fn finalize_tge(origin: OriginFor) -> DispatchResultWithPostInfo { + ensure_root(origin)?; + + ensure!(!IsTGEFinalized::::get(), Error::::TGEIsAlreadyFinalized); + + IsTGEFinalized::::put(true); + + Pallet::::deposit_event(Event::TGEFinalized); + + Ok(().into()) + } + + #[pallet::call_index(2)] + #[pallet::weight(T::WeightInfo::execute_tge(tge_infos.len() as u32))] + pub fn execute_tge( + origin: OriginFor, + tge_infos: Vec>>, + ) -> DispatchResultWithPostInfo { + ensure_root(origin)?; + + ensure!(!IsTGEFinalized::::get(), Error::::TGEIsAlreadyFinalized); + + ensure!(!T::TGEReleasePeriod::get().is_zero(), Error::::MathError); + + let lock_percent: Percent = Percent::from_percent(100) + .checked_sub(&T::ImmediateTGEReleasePercent::get()) + .ok_or(Error::::MathError)?; + + for tge_info in tge_infos { + if lock_percent.is_zero() { + let imb = T::Tokens::deposit_creating( + T::NativeCurrencyId::get().into(), + &tge_info.who.clone(), + tge_info.amount, + ); + if !tge_info.amount.clone().is_zero() && imb.peek().is_zero() { + Pallet::::deposit_event(Event::TGEInstanceFailed(tge_info.clone())); + } else { + TGETotal::::mutate(|v| *v = v.saturating_add(tge_info.amount)); + Pallet::::deposit_event(Event::TGEInstanceSucceeded(tge_info)); + continue; + } + } + + let locked: BalanceOf = (lock_percent * tge_info.amount).max(One::one()); + let per_block: BalanceOf = + (locked / T::TGEReleasePeriod::get().into()).max(One::one()); + + if T::VestingProvider::can_add_vesting_schedule( + &tge_info.who, + locked, + per_block, + T::TGEReleaseBegin::get().into(), + T::NativeCurrencyId::get(), + ) + .is_ok() + { + let imb = T::Tokens::deposit_creating( + T::NativeCurrencyId::get().into(), + &tge_info.who.clone(), + tge_info.amount, + ); + + if !tge_info.amount.is_zero() && imb.peek().is_zero() { + Pallet::::deposit_event(Event::TGEInstanceFailed(tge_info)); + } else { + let _ = T::VestingProvider::add_vesting_schedule( + &tge_info.who, + locked, + per_block, + T::TGEReleaseBegin::get().into(), + T::NativeCurrencyId::get().into(), + ); + TGETotal::::mutate(|v| *v = v.saturating_add(tge_info.amount)); + Pallet::::deposit_event(Event::TGEInstanceSucceeded(tge_info)); + } + } else { + Pallet::::deposit_event(Event::TGEInstanceFailed(tge_info)); + } + } + + Ok(().into()) + } + + #[pallet::call_index(3)] + #[pallet::weight(T::WeightInfo::set_issuance_config())] + pub fn set_issuance_config( + origin: OriginFor, + linear_issuance_amount: Option>, + linear_issuance_blocks: Option, + liquidity_mining_split: Option, + staking_split: Option, + sequencers_split: Option, + ) -> DispatchResultWithPostInfo { + ensure_root(origin)?; + + let config = IssuanceConfigStore::::try_mutate(|config| { + let cfg = config.as_mut().ok_or(Error::::IssuanceConfigNotInitialized)?; + + if let Some(linear_issuance_amount) = linear_issuance_amount { + cfg.linear_issuance_amount = linear_issuance_amount; + } + + if let Some(linear_issuance_blocks) = linear_issuance_blocks { + cfg.linear_issuance_blocks = linear_issuance_blocks; + } + + if let Some(liquidity_mining_split) = liquidity_mining_split { + cfg.liquidity_mining_split = liquidity_mining_split; + } + + if let Some(staking_split) = staking_split { + cfg.staking_split = staking_split; + } + + if let Some(sequencers_split) = sequencers_split { + cfg.sequencers_split = sequencers_split; + } + + Ok::>, Error>(cfg.clone()) + })?; + + let total_splits = + config.liquidity_mining_split + config.staking_split + config.sequencers_split; + + ensure!(total_splits.is_one(), Error::::InvalidSplitAmounts); + + Pallet::::deposit_event(Event::IssuanceConfigSet(config)); + + Ok(().into()) + } + } + + #[pallet::event] + #[pallet::generate_deposit(pub(super) fn deposit_event)] + pub enum Event { + /// Issuance for upcoming session issued + SessionIssuanceIssued(u32, BalanceOf, BalanceOf, BalanceOf), + /// Issuance for upcoming session calculated and recorded + SessionIssuanceRecorded(u32, BalanceOf, BalanceOf, BalanceOf), + /// Issuance configuration has been finalized + IssuanceConfigInitialized(IssuanceInfo>), + /// TGE has been finalized + TGEFinalized, + /// A TGE instance has failed + TGEInstanceFailed(TgeInfo>), + /// A TGE instance has succeeded + TGEInstanceSucceeded(TgeInfo>), + /// Issuance configuration updated + IssuanceConfigSet(IssuanceInfo>), + } +} + +impl ComputeIssuance for Pallet { + fn initialize() { + IsTGEFinalized::::put(true); + Self::do_init_issuance_config().unwrap(); + } + + fn compute_issuance(n: u32) { + let _ = Pallet::::calculate_and_store_round_issuance(n); + // So that we have easy access to the complete issuance record + // TODO? + // Maybe enable this and clean? + // let _ = Pallet::::clear_round_issuance_history(n); + } +} + +pub trait ProvideTotalCrowdloanRewardAllocation { + fn get_total_crowdloan_allocation() -> Option>; +} + +impl ProvideTotalCrowdloanRewardAllocation for Pallet { + fn get_total_crowdloan_allocation() -> Option> { + IssuanceConfigStore::::get() + .map(|issuance_config| issuance_config.total_crowdloan_allocation) + } +} + +impl GetIssuance> for Pallet { + fn get_all_issuance(n: u32) -> Option<(BalanceOf, BalanceOf, BalanceOf)> { + SessionIssuance::::get(n) + } + fn get_liquidity_mining_issuance(n: u32) -> Option> { + SessionIssuance::::get(n).map(|(x, _, _)| x) + } + fn get_staking_issuance(n: u32) -> Option> { + SessionIssuance::::get(n).map(|(_, x, _)| x) + } + fn get_sequencer_issuance(n: u32) -> Option> { + SessionIssuance::::get(n).map(|(_, _, x)| x) + } +} + +impl Pallet { + pub fn do_init_issuance_config() -> DispatchResultWithPostInfo { + ensure!( + IssuanceConfigStore::::get().is_none(), + Error::::IssuanceConfigAlreadyInitialized + ); + ensure!(IsTGEFinalized::::get(), Error::::TGENotFinalized); + + let issuance_config: IssuanceInfo> = IssuanceInfo { + linear_issuance_amount: T::LinearIssuanceAmount::get(), + issuance_at_init: T::Tokens::total_issuance(T::NativeCurrencyId::get().into()), + linear_issuance_blocks: T::LinearIssuanceBlocks::get(), + liquidity_mining_split: T::LiquidityMiningSplit::get(), + staking_split: T::StakingSplit::get(), + sequencers_split: T::SequencersSplit::get(), + total_crowdloan_allocation: T::TotalCrowdloanAllocation::get(), + }; + + Pallet::::build_issuance_config(issuance_config.clone())?; + + Pallet::::deposit_event(Event::IssuanceConfigInitialized(issuance_config)); + + Ok(().into()) + } + + pub fn build_issuance_config(issuance_config: IssuanceInfo>) -> DispatchResult { + ensure!( + issuance_config + .liquidity_mining_split + .checked_add(&issuance_config.staking_split) + .ok_or(Error::::IssuanceConfigInvalid)? + .checked_add(&issuance_config.sequencers_split) + .ok_or(Error::::IssuanceConfigInvalid)? == + Perbill::from_percent(100), + Error::::IssuanceConfigInvalid + ); + ensure!( + issuance_config.linear_issuance_blocks != u32::zero(), + Error::::IssuanceConfigInvalid + ); + ensure!( + issuance_config.linear_issuance_blocks > T::BlocksPerRound::get(), + Error::::IssuanceConfigInvalid + ); + ensure!(T::BlocksPerRound::get() != u32::zero(), Error::::IssuanceConfigInvalid); + IssuanceConfigStore::::put(issuance_config); + Ok(()) + } + + pub fn calculate_and_store_round_issuance(current_round: u32) -> DispatchResult { + let config = + IssuanceConfigStore::::get().ok_or(Error::::IssuanceConfigNotInitialized)?; + // Get everything from config and ignore the storage config data + let to_be_issued: BalanceOf = config.linear_issuance_amount; + let linear_issuance_sessions: u32 = config + .linear_issuance_blocks + .checked_div(T::BlocksPerRound::get()) + .ok_or(Error::::MathError)?; + let linear_issuance_per_session = to_be_issued + .checked_div(&linear_issuance_sessions.into()) + .ok_or(Error::::MathError)?; + + let current_round_issuance: BalanceOf = linear_issuance_per_session; + + let liquidity_mining_issuance = config.liquidity_mining_split * current_round_issuance; + let staking_issuance = config.staking_split * current_round_issuance; + let sequencers_issuance = config.sequencers_split * current_round_issuance; + + T::LiquidityMiningApi::distribute_rewards(liquidity_mining_issuance); + + { + let liquidity_mining_issuance_issued = T::Tokens::deposit_creating( + T::NativeCurrencyId::get().into(), + &T::LiquidityMiningIssuanceVault::get(), + liquidity_mining_issuance, + ); + let staking_issuance_issued = T::Tokens::deposit_creating( + T::NativeCurrencyId::get().into(), + &T::StakingIssuanceVault::get(), + staking_issuance, + ); + let sequencers_issuance_issued = T::Tokens::deposit_creating( + T::NativeCurrencyId::get().into(), + &T::SequencersIssuanceVault::get(), + sequencers_issuance, + ); + Self::deposit_event(Event::SessionIssuanceIssued( + current_round, + liquidity_mining_issuance_issued.peek(), + staking_issuance_issued.peek(), + sequencers_issuance_issued.peek(), + )); + } + + SessionIssuance::::insert( + current_round, + Some((liquidity_mining_issuance, staking_issuance, sequencers_issuance)), + ); + + Pallet::::deposit_event(Event::SessionIssuanceRecorded( + current_round, + liquidity_mining_issuance, + staking_issuance, + sequencers_issuance, + )); + + Ok(()) + } + + pub fn clear_round_issuance_history(current_round: u32) -> DispatchResult { + if current_round >= T::HistoryLimit::get() { + SessionIssuance::::remove(current_round - T::HistoryLimit::get()); + } + Ok(()) + } +} diff --git a/gasp-node/pallets/issuance/src/mock.rs b/gasp-node/pallets/issuance/src/mock.rs new file mode 100644 index 000000000..7b28b01fe --- /dev/null +++ b/gasp-node/pallets/issuance/src/mock.rs @@ -0,0 +1,260 @@ +// This file is part of Acala. + +// Copyright (C) 2020-2021 Acala Foundation. +// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 + +// This program 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. + +// This program 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 this program. If not, see . + +use super::*; +use crate as pallet_issuance; +use frame_support::{ + assert_ok, construct_runtime, derive_impl, parameter_types, + traits::{Contains, Nothing, WithdrawReasons}, + PalletId, +}; +use orml_traits::parameter_type_with_key; +use sp_core::U256; +use sp_runtime::{ + traits::{AccountIdConversion, ConvertInto}, + BuildStorage, SaturatedConversion, +}; +use sp_std::convert::TryFrom; +use std::{collections::HashMap, sync::Mutex}; + +pub(crate) type AccountId = u64; +pub(crate) type Amount = i128; +pub(crate) type Balance = u128; +pub(crate) type TokenId = u32; +pub const MGA_TOKEN_ID: TokenId = 0; + +#[derive_impl(frame_system::config_preludes::TestDefaultConfig as frame_system::DefaultConfig)] +impl frame_system::Config for Test { + type Block = Block; +} + +parameter_type_with_key! { + pub ExistentialDeposits: |currency_id: TokenId| -> Balance { + match currency_id { + _ => 0, + } + }; +} + +pub struct DustRemovalWhitelist; +impl Contains for DustRemovalWhitelist { + fn contains(a: &AccountId) -> bool { + *a == TreasuryAccount::get() + } +} + +parameter_types! { + pub const TreasuryPalletId: PalletId = PalletId(*b"py/trsry"); + pub TreasuryAccount: AccountId = TreasuryPalletId::get().into_account_truncating(); + pub const MaxLocks: u32 = 50; + pub const MgaTokenId: TokenId = 0u32; + pub const BlocksPerRound: u32 = 5u32; + pub const HistoryLimit: u32 = 10u32; +} + +impl orml_tokens::Config for Test { + type RuntimeEvent = RuntimeEvent; + type Balance = Balance; + type Amount = Amount; + type CurrencyId = TokenId; + type WeightInfo = (); + type ExistentialDeposits = ExistentialDeposits; + type MaxLocks = MaxLocks; + type DustRemovalWhitelist = DustRemovalWhitelist; + type MaxReserves = (); + type ReserveIdentifier = [u8; 8]; + type CurrencyHooks = (); + type NontransferableTokens = Nothing; + type NontransferableTokensAllowList = Nothing; +} + +parameter_types! { + pub const LiquidityMiningIssuanceVaultId: PalletId = PalletId(*b"py/lqmiv"); + pub LiquidityMiningIssuanceVault: AccountId = LiquidityMiningIssuanceVaultId::get().into_account_truncating(); + pub const StakingIssuanceVaultId: PalletId = PalletId(*b"py/stkiv"); + pub const SequencersIssuanceVaultId: PalletId = PalletId(*b"py/seqiv"); + pub StakingIssuanceVault: AccountId = StakingIssuanceVaultId::get().into_account_truncating(); + pub SequencersIssuanceVault: AccountId = SequencersIssuanceVaultId::get().into_account_truncating(); + + + pub const TotalCrowdloanAllocation: Balance = 200_000_000; + pub const LinearIssuanceAmount: Balance = 4_000_000_000; + pub const LinearIssuanceBlocks: u32 = 22_222u32; + pub const LiquidityMiningSplit: Perbill = Perbill::from_parts(555555556); + pub const StakingSplit: Perbill = Perbill::from_parts(222222222); + pub const SequencersSplit: Perbill = Perbill::from_parts(222222222); + pub const ImmediateTGEReleasePercent: Percent = Percent::from_percent(20); + pub const TGEReleasePeriod: u32 = 100u32; // 2 years + pub const TGEReleaseBegin: u32 = 10u32; // Two weeks into chain start + +} + +lazy_static::lazy_static! { + static ref ACTIVATED_POOL: Mutex> = { + let m = HashMap::new(); + Mutex::new(m) + }; +} + +pub struct MockLiquidityMiningApi; + +impl LiquidityMiningApi for MockLiquidityMiningApi { + fn distribute_rewards(_liquidity_mining_rewards: Balance) {} +} + +impl pallet_issuance::Config for Test { + type RuntimeEvent = RuntimeEvent; + type NativeCurrencyId = MgaTokenId; + type Tokens = orml_tokens::MultiTokenCurrencyAdapter; + type BlocksPerRound = BlocksPerRound; + type HistoryLimit = HistoryLimit; + type LiquidityMiningIssuanceVault = LiquidityMiningIssuanceVault; + type StakingIssuanceVault = StakingIssuanceVault; + type SequencersIssuanceVault = SequencersIssuanceVault; + type TotalCrowdloanAllocation = TotalCrowdloanAllocation; + type LinearIssuanceAmount = LinearIssuanceAmount; + type LinearIssuanceBlocks = LinearIssuanceBlocks; + type LiquidityMiningSplit = LiquidityMiningSplit; + type StakingSplit = StakingSplit; + type SequencersSplit = SequencersSplit; + type ImmediateTGEReleasePercent = ImmediateTGEReleasePercent; + type TGEReleasePeriod = TGEReleasePeriod; + type TGEReleaseBegin = TGEReleaseBegin; + type VestingProvider = Vesting; + type WeightInfo = (); + type LiquidityMiningApi = MockLiquidityMiningApi; +} + +parameter_types! { + pub const MinVestedTransfer: Balance = 100u128; + pub UnvestedFundsAllowedWithdrawReasons: WithdrawReasons = + WithdrawReasons::except(WithdrawReasons::TRANSFER | WithdrawReasons::RESERVE); +} + +impl pallet_vesting_mangata::Config for Test { + type RuntimeEvent = RuntimeEvent; + type Tokens = orml_tokens::MultiTokenCurrencyAdapter; + type BlockNumberToBalance = ConvertInto; + type MinVestedTransfer = MinVestedTransfer; + type WeightInfo = pallet_vesting_mangata::weights::SubstrateWeight; + type UnvestedFundsAllowedWithdrawReasons = UnvestedFundsAllowedWithdrawReasons; + type BlockNumberProvider = System; + // `VestingInfo` encode length is 36bytes. 28 schedules gets encoded as 1009 bytes, which is the + // highest number of schedules that encodes less than 2^10. + // Should be atleast twice the number of tge recipients + const MAX_VESTING_SCHEDULES: u32 = 200; +} + +type Block = frame_system::mocking::MockBlock; + +construct_runtime!( + pub enum Test { + System: frame_system, + Tokens: orml_tokens, + Vesting: pallet_vesting_mangata, + Issuance: pallet_issuance, + } +); + +// This function basically just builds a genesis storage key/value store according to +// our desired mockup. +pub fn new_test_ext_without_issuance_config() -> sp_io::TestExternalities { + let mut t = frame_system::GenesisConfig::::default() + .build_storage() + .expect("Frame system builds valid default genesis config"); + + orml_tokens::GenesisConfig:: { + tokens_endowment: vec![(0u64, 0u32, 2_000_000_000)], + created_tokens_for_staking: Default::default(), + } + .assimilate_storage(&mut t) + .expect("Tokens storage can be assimilated"); + + let mut ext = sp_io::TestExternalities::new(t); + ext.execute_with(|| { + System::set_block_number(1); + + if !StakeCurrency::exists(MGA_TOKEN_ID) { + assert_ok!(StakeCurrency::create(&99999, 100)); + } + }); + ext +} + +// This function basically just builds a genesis storage key/value store according to +// our desired mockup. +pub fn new_test_ext() -> sp_io::TestExternalities { + let mut t = frame_system::GenesisConfig::::default() + .build_storage() + .expect("Frame system builds valid default genesis config"); + + orml_tokens::GenesisConfig:: { + tokens_endowment: vec![(0u64, 0u32, 2_000_000_000)], + created_tokens_for_staking: Default::default(), + } + .assimilate_storage(&mut t) + .expect("Tokens storage can be assimilated"); + + let mut ext = sp_io::TestExternalities::new(t); + ext.execute_with(|| { + System::set_block_number(1); + + if !StakeCurrency::exists(MGA_TOKEN_ID) { + assert_ok!(StakeCurrency::create(&99999, 100)); + } + + let current_issuance = StakeCurrency::total_issuance(MGA_TOKEN_ID); + let target_tge = 2_000_000_000u128; + assert!(current_issuance <= target_tge); + + assert_ok!(StakeCurrency::mint(MGA_TOKEN_ID, &99999, target_tge - current_issuance)); + + assert_ok!(Issuance::finalize_tge(RuntimeOrigin::root())); + assert_ok!(Issuance::init_issuance_config(RuntimeOrigin::root())); + assert_ok!(Issuance::calculate_and_store_round_issuance(0u32)); + }); + ext +} + +pub type StakeCurrency = orml_tokens::MultiTokenCurrencyAdapter; + +pub(crate) fn roll_to_while_minting(n: u64, expected_amount_minted: Option) { + let mut session_number: u32; + let mut session_issuance: (Balance, Balance, Balance); + let mut block_issuance: Balance; + while System::block_number() < n { + System::on_finalize(System::block_number()); + System::set_block_number(System::block_number() + 1); + System::on_initialize(System::block_number()); + session_number = System::block_number().saturated_into::() / BlocksPerRound::get(); + session_issuance = >::get_all_issuance(session_number) + .expect("session issuance is always populated in advance"); + block_issuance = (session_issuance.0 + session_issuance.1 + session_issuance.2) / + (BlocksPerRound::get().saturated_into::()); + + if let Some(x) = expected_amount_minted { + assert_eq!(x, block_issuance); + } + + // Compute issuance for the next session only after all issuance has been issued is current session + // To avoid overestimating the missing issuance and overshooting the cap + if ((System::block_number().saturated_into::() + 1u32) % BlocksPerRound::get()) == 0 { + ::compute_issuance(session_number + 1u32); + } + } +} diff --git a/gasp-node/pallets/issuance/src/tests.rs b/gasp-node/pallets/issuance/src/tests.rs new file mode 100644 index 000000000..f2dca7450 --- /dev/null +++ b/gasp-node/pallets/issuance/src/tests.rs @@ -0,0 +1,446 @@ +use super::*; +use mock::{ + new_test_ext, new_test_ext_without_issuance_config, roll_to_while_minting, BlocksPerRound, + Issuance, RuntimeOrigin, StakeCurrency, System, Test, Tokens, Vesting, MGA_TOKEN_ID, +}; +use sp_runtime::{traits::BadOrigin, SaturatedConversion}; + +use frame_support::{assert_noop, assert_ok}; + +#[test] +fn init_issuance_config_works() { + new_test_ext_without_issuance_config().execute_with(|| { + let current_issuance = StakeCurrency::total_issuance(MGA_TOKEN_ID); + + assert_eq!(Issuance::is_tge_finalized(), false); + assert_ok!(Issuance::finalize_tge(RuntimeOrigin::root())); + assert_eq!(Issuance::is_tge_finalized(), true); + + assert_ok!(Issuance::init_issuance_config(RuntimeOrigin::root())); + assert_eq!( + Issuance::get_issuance_config(), + Some(IssuanceInfo { + linear_issuance_amount: 4_000_000_000u128, + issuance_at_init: current_issuance, + linear_issuance_blocks: 22_222u32, + liquidity_mining_split: Perbill::from_parts(555555556), + staking_split: Perbill::from_parts(222222222), + sequencers_split: Perbill::from_parts(222222222), + total_crowdloan_allocation: 200_000_000u128, + }) + ); + }); +} + +#[test] +fn cannot_finalize_tge_when_already_finalized() { + new_test_ext_without_issuance_config().execute_with(|| { + assert_eq!(Issuance::is_tge_finalized(), false); + assert_ok!(Issuance::finalize_tge(RuntimeOrigin::root())); + assert_eq!(Issuance::is_tge_finalized(), true); + + assert_noop!( + Issuance::finalize_tge(RuntimeOrigin::root()), + Error::::TGEIsAlreadyFinalized + ); + }); +} + +#[test] +fn cannot_init_issuance_config_when_tge_is_not_finalized() { + new_test_ext_without_issuance_config().execute_with(|| { + assert_eq!(Issuance::is_tge_finalized(), false); + + assert_noop!( + Issuance::init_issuance_config(RuntimeOrigin::root()), + Error::::TGENotFinalized + ); + }); +} + +#[test] +fn cannot_init_issuance_config_when_already_init() { + new_test_ext_without_issuance_config().execute_with(|| { + let current_issuance = StakeCurrency::total_issuance(MGA_TOKEN_ID); + + assert_eq!(Issuance::is_tge_finalized(), false); + assert_ok!(Issuance::finalize_tge(RuntimeOrigin::root())); + assert_eq!(Issuance::is_tge_finalized(), true); + + assert_ok!(Issuance::init_issuance_config(RuntimeOrigin::root())); + assert_eq!( + Issuance::get_issuance_config(), + Some(IssuanceInfo { + linear_issuance_amount: 4_000_000_000u128, + issuance_at_init: current_issuance, + linear_issuance_blocks: 22_222u32, + liquidity_mining_split: Perbill::from_parts(555555556), + staking_split: Perbill::from_parts(222222222), + sequencers_split: Perbill::from_parts(222222222), + total_crowdloan_allocation: 200_000_000u128, + }) + ); + assert_noop!( + Issuance::init_issuance_config(RuntimeOrigin::root()), + Error::::IssuanceConfigAlreadyInitialized + ); + }); +} + +#[test] +fn execute_tge_works() { + new_test_ext_without_issuance_config().execute_with(|| { + assert_eq!(Issuance::is_tge_finalized(), false); + + assert_ok!(Issuance::execute_tge( + RuntimeOrigin::root(), + vec![ + TgeInfo { who: 1, amount: 1000u128 }, + TgeInfo { who: 2, amount: 2000u128 }, + TgeInfo { who: 3, amount: 3000u128 }, + TgeInfo { who: 4, amount: 4000u128 } + ] + )); + assert_eq!(Issuance::get_tge_total(), 10_000u128); + + assert_eq!(StakeCurrency::free_balance(MGA_TOKEN_ID, &1), 1000u128); + assert_eq!(StakeCurrency::free_balance(MGA_TOKEN_ID, &2), 2000u128); + assert_eq!(StakeCurrency::free_balance(MGA_TOKEN_ID, &3), 3000u128); + assert_eq!(StakeCurrency::free_balance(MGA_TOKEN_ID, &4), 4000u128); + + assert_eq!(Tokens::locks(&1, MGA_TOKEN_ID)[0].amount, 800u128); + assert_eq!(Tokens::locks(&2, MGA_TOKEN_ID)[0].amount, 1600u128); + assert_eq!(Tokens::locks(&3, MGA_TOKEN_ID)[0].amount, 2400u128); + assert_eq!(Tokens::locks(&4, MGA_TOKEN_ID)[0].amount, 3200u128); + + assert_eq!(Vesting::vesting(&1, MGA_TOKEN_ID).unwrap()[0].locked(), 800u128); + assert_eq!(Vesting::vesting(&2, MGA_TOKEN_ID).unwrap()[0].locked(), 1600u128); + assert_eq!(Vesting::vesting(&3, MGA_TOKEN_ID).unwrap()[0].locked(), 2400u128); + assert_eq!(Vesting::vesting(&4, MGA_TOKEN_ID).unwrap()[0].locked(), 3200u128); + + assert_eq!(Vesting::vesting(&1, MGA_TOKEN_ID).unwrap()[0].per_block(), 8u128); + assert_eq!(Vesting::vesting(&2, MGA_TOKEN_ID).unwrap()[0].per_block(), 16u128); + assert_eq!(Vesting::vesting(&3, MGA_TOKEN_ID).unwrap()[0].per_block(), 24u128); + assert_eq!(Vesting::vesting(&4, MGA_TOKEN_ID).unwrap()[0].per_block(), 32u128); + + assert_ok!(Issuance::finalize_tge(RuntimeOrigin::root())); + assert_eq!(Issuance::is_tge_finalized(), true); + }); +} + +#[test] +fn cannot_execute_tge_if_already_finalized() { + new_test_ext_without_issuance_config().execute_with(|| { + assert_eq!(Issuance::is_tge_finalized(), false); + assert_ok!(Issuance::finalize_tge(RuntimeOrigin::root())); + assert_eq!(Issuance::is_tge_finalized(), true); + + assert_noop!( + Issuance::execute_tge(RuntimeOrigin::root(), vec![]), + Error::::TGEIsAlreadyFinalized + ); + }); +} + +#[test] +fn linear_issuance_works() { + new_test_ext().execute_with(|| { + let session_number = System::block_number().saturated_into::() / BlocksPerRound::get(); + let session_issuance = >::get_all_issuance(session_number) + .expect("session issuance is always populated in advance"); + let block_issuance = (session_issuance.0 + session_issuance.1 + session_issuance.2) / + (BlocksPerRound::get().saturated_into::()); + + // Mint in block 1 + // We are not minting in block 0, but that's okay + assert_eq!(900090, (session_issuance.0 + session_issuance.1 + session_issuance.2)); + assert_eq!(180018, block_issuance); + + roll_to_while_minting(10000, Some(180018)); + + // Mint for crowdloan + let _ = orml_tokens::MultiTokenCurrencyAdapter::::mint(0u32, &1u64, 200_000_000u128); + + roll_to_while_minting(22218, Some(180018)); + + assert_eq!(6199999960, Tokens::total_issuance(0u32)); + + // This the point the next session's issuance will be calculated and minted + // on the basis of total_issuance + roll_to_while_minting(22219, Some(180018)); + + assert_eq!(6200900050, Tokens::total_issuance(0u32)); + }); +} + +#[ignore] +#[test] +fn linear_issuance_doesnt_change_upon_burn() { + new_test_ext().execute_with(|| { + roll_to_while_minting(15000, Some(81008)); + + orml_tokens::MultiTokenCurrencyAdapter::::burn_and_settle(0u32, &0u64, 100_000_000) + .unwrap(); + + assert_eq!(3115525040, Tokens::total_issuance(0u32)); + + // Mint for crowdloan + let _ = orml_tokens::MultiTokenCurrencyAdapter::::mint(0u32, &1u64, 200_000_000u128); + + roll_to_while_minting(22218, Some(81008)); + + assert_eq!(3899997760, Tokens::total_issuance(0u32)); + + // This the point the next session's issuance will be calculated and minted + // on the basis of total_issuance + roll_to_while_minting(22219, Some(81008)); + + assert_eq!(3900402800, Tokens::total_issuance(0u32)); + }); +} + +#[ignore] +#[test] +fn issuance_stops_upon_reaching_cap() { + new_test_ext().execute_with(|| { + // This the point the next session's issuance will be calculated and minted + // on the basis of total_issuance + + // Mint for crowdloan + let _ = orml_tokens::MultiTokenCurrencyAdapter::::mint(0u32, &1u64, 200_000_000u128); + + // At this point the entirety of the missing issuance will be allocated to the next session + roll_to_while_minting(22219, Some(81008)); + + assert_eq!(4000000000, Tokens::total_issuance(0u32)); + + roll_to_while_minting(22224, Some(448)); + + assert_eq!(4000000000, Tokens::total_issuance(0u32)); + + // Now there is not enough missing issuance to issue so no more mga will be issued + + roll_to_while_minting(23000, Some(0)); + + assert_eq!(4000000000, Tokens::total_issuance(0u32)); + }); +} + +#[ignore] +#[test] +fn issuance_does_not_stop_upon_burn() { + new_test_ext().execute_with(|| { + // Mint for crowdloan + let _ = orml_tokens::MultiTokenCurrencyAdapter::::mint(0u32, &1u64, 200_000_000u128); + + // This the point the next session's issuance will be calculated and minted + // on the basis of total_issuance + roll_to_while_minting(22219, Some(81008)); + + assert_eq!(4000000000, Tokens::total_issuance(0u32)); + + roll_to_while_minting(22221, Some(448)); + + orml_tokens::MultiTokenCurrencyAdapter::::burn_and_settle(0u32, &0u64, 100_000) + .unwrap(); + + // At this point the entirety of the missing issuance will be allocated to the next session + + roll_to_while_minting(22224, Some(448)); + + assert_eq!(4000000000, Tokens::total_issuance(0u32)); + + roll_to_while_minting(22229, Some(20000)); + + assert_eq!(4000000000, Tokens::total_issuance(0u32)); + + roll_to_while_minting(24001, Some(0)); + + assert_eq!(4000000000, Tokens::total_issuance(0u32)); + }); +} + +#[ignore] +#[test] +fn issuance_restarts_upon_burn() { + new_test_ext().execute_with(|| { + // Mint for crowdloan + let _ = orml_tokens::MultiTokenCurrencyAdapter::::mint(0u32, &1u64, 200_000_000u128); + + // This the point the next session's issuance will be calculated and minted + // on the basis of total_issuance + roll_to_while_minting(22219, Some(81008)); + + assert_eq!(4000000000, Tokens::total_issuance(0u32)); + + // At this point the entirety of the missing issuance will be allocated to the next session + + roll_to_while_minting(22224, Some(448)); + + assert_eq!(4000000000, Tokens::total_issuance(0u32)); + + // Now there is not enough missing issuance to issue so no more mga will be issued + + roll_to_while_minting(23002, Some(0)); + + assert_eq!(4000000000, Tokens::total_issuance(0u32)); + + orml_tokens::MultiTokenCurrencyAdapter::::burn_and_settle(0u32, &0u64, 100_000) + .unwrap(); + + assert_eq!(3999900000, Tokens::total_issuance(0u32)); + + roll_to_while_minting(23004, Some(0)); + + roll_to_while_minting(23009, Some(20000)); + + assert_eq!(4000000000, Tokens::total_issuance(0u32)); + + roll_to_while_minting(24001, Some(0)); + + assert_eq!(4000000000, Tokens::total_issuance(0u32)); + }); +} + +#[ignore] +#[test] +fn issuance_after_linear_period_never_execeeds_linear() { + new_test_ext().execute_with(|| { + // Mint for crowdloan + let _ = orml_tokens::MultiTokenCurrencyAdapter::::mint(0u32, &1u64, 200_000_000u128); + + // This the point the next session's issuance will be calculated and minted + // on the basis of total_issuance + roll_to_while_minting(22219, Some(81008)); + + assert_eq!(4000000000, Tokens::total_issuance(0u32)); + + // At this point the entirety of the missing issuance will be allocated to the next session + + roll_to_while_minting(22224, Some(448)); + + assert_eq!(4000000000, Tokens::total_issuance(0u32)); + + // Now there is not enough missing issuance to issue so no more mga will be issued + + roll_to_while_minting(23002, Some(0)); + + assert_eq!(4000000000, Tokens::total_issuance(0u32)); + + orml_tokens::MultiTokenCurrencyAdapter::::burn_and_settle(0u32, &0u64, 100_000) + .unwrap(); + + assert_eq!(3999900000, Tokens::total_issuance(0u32)); + + roll_to_while_minting(23004, Some(0)); + + roll_to_while_minting(23009, Some(20000)); + + assert_eq!(4000000000, Tokens::total_issuance(0u32)); + + roll_to_while_minting(23023, Some(0)); + + assert_eq!(4000000000, Tokens::total_issuance(0u32)); + + orml_tokens::MultiTokenCurrencyAdapter::::burn_and_settle(0u32, &0u64, 100_000_000) + .unwrap(); + + assert_eq!(3900000000, Tokens::total_issuance(0u32)); + + roll_to_while_minting(23024, Some(0)); + + roll_to_while_minting(23051, Some(81008)); + + assert_eq!(3902430240, Tokens::total_issuance(0u32)); + }); +} + +#[test] +fn update_issuance_config_only_as_root() { + new_test_ext_without_issuance_config().execute_with(|| { + const USER_ID: u64 = 0; + + assert_ok!(Issuance::finalize_tge(RuntimeOrigin::root())); + assert_ok!(Issuance::init_issuance_config(RuntimeOrigin::root())); + + assert_noop!( + Issuance::set_issuance_config( + RuntimeOrigin::signed(USER_ID), + None, + None, + None, + None, + None + ), + sp_runtime::traits::BadOrigin + ); + }); +} + +#[test] +fn set_issuance_modifies_the_config() { + new_test_ext_without_issuance_config().execute_with(|| { + const USER_ID: u64 = 0; + + assert_ok!(Issuance::finalize_tge(RuntimeOrigin::root())); + assert_ok!(Issuance::init_issuance_config(RuntimeOrigin::root())); + + let initial_config = IssuanceConfigStore::::get().unwrap(); + + let linear_issuance_amount = 5_000_000_000u128; + let linear_issuance_blocks = 33_222u32; + let liquidity_mining_split = Perbill::from_percent(10); + let staking_split = Perbill::from_percent(20); + let sequencers_split = Perbill::from_percent(70); + + assert!(initial_config.linear_issuance_amount != linear_issuance_amount); + assert!(initial_config.linear_issuance_blocks != linear_issuance_blocks); + assert!(initial_config.liquidity_mining_split != liquidity_mining_split); + assert!(initial_config.staking_split != staking_split); + assert!(initial_config.sequencers_split != sequencers_split); + + assert_ok!(Issuance::set_issuance_config( + RuntimeOrigin::root(), + Some(linear_issuance_amount), + Some(linear_issuance_blocks), + Some(liquidity_mining_split), + Some(staking_split), + Some(sequencers_split) + )); + + let cfg = IssuanceConfigStore::::get().unwrap(); + assert_eq!(cfg.linear_issuance_amount, linear_issuance_amount); + assert_eq!(cfg.linear_issuance_blocks, linear_issuance_blocks); + assert_eq!(cfg.liquidity_mining_split, liquidity_mining_split); + assert_eq!(cfg.staking_split, staking_split); + assert_eq!(cfg.sequencers_split, sequencers_split); + }); +} + +#[test] +fn test_fail_on_wrong_splits_amounts() { + new_test_ext_without_issuance_config().execute_with(|| { + const USER_ID: u64 = 0; + + assert_ok!(Issuance::finalize_tge(RuntimeOrigin::root())); + assert_ok!(Issuance::init_issuance_config(RuntimeOrigin::root())); + + let initial_config = IssuanceConfigStore::::get().unwrap(); + + let liquidity_mining_split = Perbill::from_percent(10); + let staking_split = Perbill::from_percent(20); + let sequencers_split = Perbill::from_percent(30); + + assert_noop!( + Issuance::set_issuance_config( + RuntimeOrigin::root(), + None, + None, + Some(liquidity_mining_split), + Some(staking_split), + Some(sequencers_split) + ), + Error::::InvalidSplitAmounts + ); + }); +} diff --git a/gasp-node/pallets/issuance/src/weights.rs b/gasp-node/pallets/issuance/src/weights.rs new file mode 100644 index 000000000..da1a6ed01 --- /dev/null +++ b/gasp-node/pallets/issuance/src/weights.rs @@ -0,0 +1,158 @@ +// This file is part of Mangata. + +// Copyright (C) 2020-2022 Mangata Foundation. +// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 + +// This program 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. + +// This program 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 this program. If not, see . + +//! Autogenerated weights for pallet_issuance +//! +//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 32.0.0 +//! DATE: 2025-01-15, STEPS: `50`, REPEAT: 20, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! EXECUTION: , WASM-EXECUTION: Compiled, CHAIN: Some("rollup-local"), DB CACHE: 1024 + +// Executed Command: +// target/release/rollup-node +// benchmark +// pallet +// -l=info,runtime::collective=warn,xyk=warn +// --chain +// rollup-local +// --wasm-execution +// compiled +// --pallet +// * +// --extrinsic +// * +// --steps +// 50 +// --repeat +// 20 +// --template +// ./templates/module-weight-template.hbs +// --output +// ./benchmarks/ + +#![cfg_attr(rustfmt, rustfmt_skip)] +#![allow(unused_parens)] +#![allow(unused_imports)] +#![allow(clippy::unnecessary_cast)] + +use frame_support::{traits::Get, weights::{Weight, constants::RocksDbWeight}}; +use sp_std::marker::PhantomData; + +/// Weight functions needed for pallet_issuance. +pub trait WeightInfo { + fn init_issuance_config() -> Weight; + fn finalize_tge() -> Weight; + fn execute_tge(x: u32, ) -> Weight; + fn set_issuance_config() -> Weight; +} + +/// Weights for pallet_issuance using the Mangata node and recommended hardware. +pub struct ModuleWeight(PhantomData); +impl WeightInfo for ModuleWeight { + // Storage: `Issuance::IssuanceConfigStore` (r:1 w:1) + // Proof: `Issuance::IssuanceConfigStore` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + // Storage: `Issuance::IsTGEFinalized` (r:1 w:0) + // Proof: `Issuance::IsTGEFinalized` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + // Storage: `Tokens::TotalIssuance` (r:1 w:0) + // Proof: `Tokens::TotalIssuance` (`max_values`: None, `max_size`: Some(28), added: 2503, mode: `MaxEncodedLen`) + fn init_issuance_config() -> Weight { + (Weight::from_parts(17_750_000, 0)) + .saturating_add(T::DbWeight::get().reads(3 as u64)) + .saturating_add(T::DbWeight::get().writes(1 as u64)) + } + // Storage: `Issuance::IsTGEFinalized` (r:1 w:1) + // Proof: `Issuance::IsTGEFinalized` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + fn finalize_tge() -> Weight { + (Weight::from_parts(9_809_000, 0)) + .saturating_add(T::DbWeight::get().reads(1 as u64)) + .saturating_add(T::DbWeight::get().writes(1 as u64)) + } + // Storage: `Issuance::IsTGEFinalized` (r:1 w:0) + // Proof: `Issuance::IsTGEFinalized` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + // Storage: `Tokens::Accounts` (r:100 w:100) + // Proof: `Tokens::Accounts` (`max_values`: None, `max_size`: Some(96), added: 2571, mode: `MaxEncodedLen`) + // Storage: `Tokens::TotalIssuance` (r:1 w:1) + // Proof: `Tokens::TotalIssuance` (`max_values`: None, `max_size`: Some(28), added: 2503, mode: `MaxEncodedLen`) + // Storage: `System::Account` (r:100 w:100) + // Proof: `System::Account` (`max_values`: None, `max_size`: Some(52), added: 2527, mode: `MaxEncodedLen`) + // Storage: `Issuance::TGETotal` (r:1 w:1) + // Proof: `Issuance::TGETotal` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + fn execute_tge(x: u32, ) -> Weight { + (Weight::from_parts(26_290_460, 0)) + // Standard Error: 10_945 + .saturating_add((Weight::from_parts(22_350_615, 0)).saturating_mul(x as u64)) + .saturating_add(T::DbWeight::get().reads(3 as u64)) + .saturating_add(T::DbWeight::get().reads((2 as u64).saturating_mul(x as u64))) + .saturating_add(T::DbWeight::get().writes(2 as u64)) + .saturating_add(T::DbWeight::get().writes((2 as u64).saturating_mul(x as u64))) + } + // Storage: `Issuance::IssuanceConfigStore` (r:1 w:1) + // Proof: `Issuance::IssuanceConfigStore` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + fn set_issuance_config() -> Weight { + (Weight::from_parts(11_770_000, 0)) + .saturating_add(T::DbWeight::get().reads(1 as u64)) + .saturating_add(T::DbWeight::get().writes(1 as u64)) + } +} + +// For backwards compatibility and tests +impl WeightInfo for () { + // Storage: `Issuance::IssuanceConfigStore` (r:1 w:1) + // Proof: `Issuance::IssuanceConfigStore` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + // Storage: `Issuance::IsTGEFinalized` (r:1 w:0) + // Proof: `Issuance::IsTGEFinalized` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + // Storage: `Tokens::TotalIssuance` (r:1 w:0) + // Proof: `Tokens::TotalIssuance` (`max_values`: None, `max_size`: Some(28), added: 2503, mode: `MaxEncodedLen`) + fn init_issuance_config() -> Weight { + (Weight::from_parts(17_750_000, 0)) + .saturating_add(RocksDbWeight::get().reads(3 as u64)) + .saturating_add(RocksDbWeight::get().writes(1 as u64)) + } + // Storage: `Issuance::IsTGEFinalized` (r:1 w:1) + // Proof: `Issuance::IsTGEFinalized` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + fn finalize_tge() -> Weight { + (Weight::from_parts(9_809_000, 0)) + .saturating_add(RocksDbWeight::get().reads(1 as u64)) + .saturating_add(RocksDbWeight::get().writes(1 as u64)) + } + // Storage: `Issuance::IsTGEFinalized` (r:1 w:0) + // Proof: `Issuance::IsTGEFinalized` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + // Storage: `Tokens::Accounts` (r:100 w:100) + // Proof: `Tokens::Accounts` (`max_values`: None, `max_size`: Some(96), added: 2571, mode: `MaxEncodedLen`) + // Storage: `Tokens::TotalIssuance` (r:1 w:1) + // Proof: `Tokens::TotalIssuance` (`max_values`: None, `max_size`: Some(28), added: 2503, mode: `MaxEncodedLen`) + // Storage: `System::Account` (r:100 w:100) + // Proof: `System::Account` (`max_values`: None, `max_size`: Some(52), added: 2527, mode: `MaxEncodedLen`) + // Storage: `Issuance::TGETotal` (r:1 w:1) + // Proof: `Issuance::TGETotal` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + fn execute_tge(x: u32, ) -> Weight { + (Weight::from_parts(26_290_460, 0)) + // Standard Error: 10_945 + .saturating_add((Weight::from_parts(22_350_615, 0)).saturating_mul(x as u64)) + .saturating_add(RocksDbWeight::get().reads(3 as u64)) + .saturating_add(RocksDbWeight::get().reads((2 as u64).saturating_mul(x as u64))) + .saturating_add(RocksDbWeight::get().writes(2 as u64)) + .saturating_add(RocksDbWeight::get().writes((2 as u64).saturating_mul(x as u64))) + } + // Storage: `Issuance::IssuanceConfigStore` (r:1 w:1) + // Proof: `Issuance::IssuanceConfigStore` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + fn set_issuance_config() -> Weight { + (Weight::from_parts(11_770_000, 0)) + .saturating_add(RocksDbWeight::get().reads(1 as u64)) + .saturating_add(RocksDbWeight::get().writes(1 as u64)) + } +} diff --git a/gasp-node/pallets/maintenance/Cargo.toml b/gasp-node/pallets/maintenance/Cargo.toml new file mode 100644 index 000000000..383e4a181 --- /dev/null +++ b/gasp-node/pallets/maintenance/Cargo.toml @@ -0,0 +1,58 @@ +[package] +authors = ["Mangata team"] +edition = "2018" +name = "pallet-maintenance" +version = "0.1.0" + +[package.metadata.docs.rs] +targets = ["x86_64-unknown-linux-gnu"] + +[dependencies] +codec = { workspace = true, default-features = false } +hex = { workspace = true, default-features = false } +hex-literal = { workspace = true, default-features = false } +log = { workspace = true, default-features = false } +scale-info = { workspace = true, default-features = false, features = ["derive"] } +serde = { workspace = true, optional = true } + +frame-benchmarking = { workspace = true, default-features = false } +frame-support = { workspace = true, default-features = false } +frame-system = { workspace = true, default-features = false } +mangata-support = { workspace = true, default-features = false } +mangata-types = { workspace = true, default-features = false } +sp-core = { workspace = true, default-features = false } +sp-runtime = { workspace = true, default-features = false } +sp-std = { workspace = true, default-features = false } + +orml-tokens = { workspace = true, default-features = false } + +[dev-dependencies] +env_logger.workspace = true +lazy_static.workspace = true +serial_test.workspace = true + +sp-io = { workspace = true, default-features = false } + +orml-traits = { workspace = true, default-features = false } + + +[features] +default = ["std"] +enable-trading = [] +std = [ + "codec/std", + "frame-benchmarking/std", + "frame-support/std", + "frame-system/std", + "hex/std", + "mangata-support/std", + "orml-tokens/std", + "serde", + "sp-std/std", + "sp-core/std", + "sp-runtime/std", +] + +runtime-benchmarks = ["frame-benchmarking/runtime-benchmarks"] + +try-runtime = ["frame-support/try-runtime", "frame-system/try-runtime", "orml-tokens/try-runtime", "sp-runtime/try-runtime"] diff --git a/gasp-node/pallets/maintenance/src/benchmarking.rs b/gasp-node/pallets/maintenance/src/benchmarking.rs new file mode 100644 index 000000000..c5e947cae --- /dev/null +++ b/gasp-node/pallets/maintenance/src/benchmarking.rs @@ -0,0 +1,110 @@ +// This file is part of Substrate. + +// Copyright (C) 2020-2022 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. + +//! TokenTimeout pallet benchmarking. + +#![cfg(feature = "runtime-benchmarks")] + +use super::*; + +use frame_benchmarking::{benchmarks, whitelisted_caller}; +use frame_support::{assert_err, assert_ok, traits::tokens::currency::MultiTokenCurrency}; +use frame_system::RawOrigin; +use orml_tokens::MultiTokenCurrencyExtended; +use sp_std::collections::btree_map::BTreeMap; + +use crate::Pallet as TokenTimeout; + +const MGA_TOKEN_ID: TokenId = 0; + +benchmarks! { + + update_timeout_metadata{ + let period_length: T::BlockNumber = 1000u32.into(); + let timeout_amount: Balance = 1000; + let mut swap_value_thresholds_vec: Vec<(TokenId, Option)> = Vec::new(); + for i in 0..::MaxCuratedTokens::get() { + swap_value_thresholds_vec.push((i, Some(i.into()))); + } + }: {assert_ok!(TokenTimeout::::update_timeout_metadata(RawOrigin::Root.into(), Some(period_length), Some(timeout_amount), Some(swap_value_thresholds_vec)));} + verify{ + assert_eq!(TokenTimeout::::get_timeout_metadata().unwrap().period_length, period_length); + assert_eq!(TokenTimeout::::get_timeout_metadata().unwrap().timeout_amount, timeout_amount); + assert_eq!(TokenTimeout::::get_timeout_metadata().unwrap().swap_value_threshold.len(), ::MaxCuratedTokens::get() as usize); + } + + release_timeout{ + + let caller: T::AccountId = whitelisted_caller(); + let period_length: T::BlockNumber = 10u32.into(); + let timeout_amount: Balance = 1000; + + let now= >::block_number(); + let token_id = MGA_TOKEN_ID; + + if ::Tokens::get_next_currency_id().into() > TokenId::from(MGA_TOKEN_ID){ + assert_ok!(::Tokens::mint(token_id.into(), &caller.clone(), 1_000_000u128.into())); + } else { + assert_eq!(::Tokens::create(&caller.clone(), 1_000_000u128.into()).unwrap().into(), token_id); + } + + let initial_user_free_balance:Balance = ::Tokens::free_balance(token_id.into(), &caller.clone()).into(); + let initial_user_reserved_balance:Balance = ::Tokens::reserved_balance(token_id.into(), &caller.clone()).into(); + let initial_user_locked_balance:Balance = ::Tokens::locked_balance(token_id.into(), &caller.clone()).into(); + + assert_ok!(TokenTimeout::::update_timeout_metadata(RawOrigin::Root.into(), Some(period_length), Some(timeout_amount), None)); + + assert_eq!(TokenTimeout::::get_timeout_metadata().unwrap().period_length, period_length); + assert_eq!(TokenTimeout::::get_timeout_metadata().unwrap().timeout_amount, timeout_amount); + assert_eq!(TokenTimeout::::get_timeout_metadata().unwrap().swap_value_threshold.len(), 0u32 as usize); + + assert_ok!( + as TimeoutTriggerTrait<_>>::process_timeout(&caller) + ); + + assert_eq!(::Tokens::free_balance(token_id.into(), &caller.clone()).into(), + initial_user_free_balance - timeout_amount); + assert_eq!(::Tokens::reserved_balance(token_id.into(), &caller.clone()).into(), + initial_user_reserved_balance + timeout_amount); + assert_eq!(::Tokens::locked_balance(token_id.into(), &caller.clone()).into(), + initial_user_locked_balance); + + assert_eq!(TokenTimeout::::get_account_timeout_data(caller.clone()), AccountTimeoutDataInfo{ + total_timeout_amount: timeout_amount, + last_timeout_block: now, + }); + + frame_system::Pallet::::set_block_number(now + period_length); + + }: {assert_ok!(TokenTimeout::::release_timeout(RawOrigin::Signed(caller.clone().into()).into()));} + verify{ + assert_eq!(::Tokens::free_balance(token_id.into(), &caller.clone()).into(), + initial_user_free_balance); + assert_eq!(::Tokens::reserved_balance(token_id.into(), &caller.clone()).into(), + initial_user_reserved_balance); + assert_eq!(::Tokens::locked_balance(token_id.into(), &caller.clone()).into(), + initial_user_locked_balance); + + assert_eq!(TokenTimeout::::get_account_timeout_data(caller.clone()), AccountTimeoutDataInfo{ + total_timeout_amount: 0, + last_timeout_block: 0u32.into(), + }); + } + + + impl_benchmark_test_suite!(TokenTimeout, crate::mock::new_test_ext(), crate::mock::Test) +} diff --git a/gasp-node/pallets/maintenance/src/lib.rs b/gasp-node/pallets/maintenance/src/lib.rs new file mode 100644 index 000000000..8502a9688 --- /dev/null +++ b/gasp-node/pallets/maintenance/src/lib.rs @@ -0,0 +1,247 @@ +#![cfg_attr(not(feature = "std"), no_std)] + +use frame_support::{ + ensure, + pallet_prelude::*, + traits::{Get, StorageVersion}, +}; +use frame_system::{ensure_signed, pallet_prelude::*}; +use mangata_support::traits::{GetMaintenanceStatusTrait, SetMaintenanceModeOn}; + +use sp_std::{convert::TryInto, prelude::*}; + +#[cfg(test)] +mod mock; +#[cfg(test)] +mod tests; + +pub(crate) const LOG_TARGET: &'static str = "maintenance"; + +// syntactic sugar for logging. +#[macro_export] +macro_rules! log { + ($level:tt, $patter:expr $(, $values:expr)* $(,)?) => { + log::$level!( + target: crate::LOG_TARGET, + concat!("[{:?}] 💸 ", $patter), >::block_number() $(, $values)* + ) + }; +} + +pub use pallet::*; + +#[frame_support::pallet] +pub mod pallet { + + use super::*; + + const STORAGE_VERSION: StorageVersion = StorageVersion::new(0); + + #[pallet::pallet] + #[pallet::storage_version(STORAGE_VERSION)] + pub struct Pallet(PhantomData); + + #[pallet::hooks] + impl Hooks> for Pallet {} + + #[derive( + Eq, PartialEq, RuntimeDebug, Clone, Encode, Decode, MaxEncodedLen, TypeInfo, Default, + )] + pub struct MaintenanceStatusInfo { + pub is_maintenance: bool, + pub is_upgradable_in_maintenance: bool, + } + + #[pallet::storage] + #[pallet::getter(fn get_maintenance_status)] + pub type MaintenanceStatus = StorageValue<_, MaintenanceStatusInfo, ValueQuery>; + + #[pallet::event] + #[pallet::generate_deposit(pub(super) fn deposit_event)] + pub enum Event { + /// Maintenance mode has been switched on + MaintenanceModeSwitchedOn(T::AccountId), + /// Maintenance mode has been switched off + MaintenanceModeSwitchedOff(T::AccountId), + /// Upgradablilty in maintenance mode has been switched on + UpgradabilityInMaintenanceModeSwitchedOn(T::AccountId), + /// Upgradablilty in maintenance mode has been switched off + UpgradabilityInMaintenanceModeSwitchedOff(T::AccountId), + /// Maintenance mode has been switched on externally + MaintenanceModeSwitchedOnExternally, + } + + #[pallet::error] + /// Errors + pub enum Error { + /// Timeouts were incorrectly initialized + NotFoundationAccount, + /// Not in maintenance mode + NotInMaintenanceMode, + /// Already in maintenance mode + AlreadyInMaintenanceMode, + /// Already upgradable in maintenance mode + AlreadyUpgradableInMaintenanceMode, + /// Already not upgradable in maintenance mode + AlreadyNotUpgradableInMaintenanceMode, + /// Upgrade blocked by Maintenance + UpgradeBlockedByMaintenance, + } + + #[pallet::config] + pub trait Config: frame_system::Config { + type RuntimeEvent: From> + IsType<::RuntimeEvent>; + type FoundationAccountsProvider: Get>; + } + + #[pallet::call] + impl Pallet { + #[pallet::call_index(0)] + #[pallet::weight(T::DbWeight::get().reads_writes(1, 1).saturating_add(Weight::from_parts(40_000_000, 0)))] + pub fn switch_maintenance_mode_on(origin: OriginFor) -> DispatchResultWithPostInfo { + let caller = ensure_signed(origin)?; + + ensure!( + T::FoundationAccountsProvider::get().contains(&caller), + Error::::NotFoundationAccount + ); + + let current_maintenance_status = MaintenanceStatus::::get(); + + ensure!( + !current_maintenance_status.is_maintenance, + Error::::AlreadyInMaintenanceMode + ); + + let maintenance_status = + MaintenanceStatusInfo { is_maintenance: true, is_upgradable_in_maintenance: false }; + + MaintenanceStatus::::put(maintenance_status); + + Pallet::::deposit_event(Event::MaintenanceModeSwitchedOn(caller)); + + Ok(().into()) + } + + #[pallet::call_index(1)] + #[pallet::weight(T::DbWeight::get().reads_writes(1, 1).saturating_add(Weight::from_parts(40_000_000, 0)))] + pub fn switch_maintenance_mode_off(origin: OriginFor) -> DispatchResultWithPostInfo { + let caller = ensure_signed(origin)?; + + ensure!( + T::FoundationAccountsProvider::get().contains(&caller), + Error::::NotFoundationAccount + ); + + let current_maintenance_status = MaintenanceStatus::::get(); + + ensure!(current_maintenance_status.is_maintenance, Error::::NotInMaintenanceMode); + + let maintenance_status = MaintenanceStatusInfo { + is_maintenance: false, + is_upgradable_in_maintenance: false, + }; + + MaintenanceStatus::::put(maintenance_status); + + Pallet::::deposit_event(Event::MaintenanceModeSwitchedOff(caller)); + + Ok(().into()) + } + + #[pallet::call_index(2)] + #[pallet::weight(T::DbWeight::get().reads_writes(1, 1).saturating_add(Weight::from_parts(40_000_000, 0)))] + pub fn switch_upgradability_in_maintenance_mode_on( + origin: OriginFor, + ) -> DispatchResultWithPostInfo { + let caller = ensure_signed(origin)?; + + ensure!( + T::FoundationAccountsProvider::get().contains(&caller), + Error::::NotFoundationAccount + ); + + let current_maintenance_status = MaintenanceStatus::::get(); + + ensure!(current_maintenance_status.is_maintenance, Error::::NotInMaintenanceMode); + + ensure!( + !current_maintenance_status.is_upgradable_in_maintenance, + Error::::AlreadyUpgradableInMaintenanceMode + ); + + let maintenance_status = + MaintenanceStatusInfo { is_maintenance: true, is_upgradable_in_maintenance: true }; + + MaintenanceStatus::::put(maintenance_status); + + Pallet::::deposit_event(Event::UpgradabilityInMaintenanceModeSwitchedOn(caller)); + + Ok(().into()) + } + + #[pallet::call_index(3)] + #[pallet::weight(T::DbWeight::get().reads_writes(1, 1).saturating_add(Weight::from_parts(40_000_000, 0)))] + pub fn switch_upgradability_in_maintenance_mode_off( + origin: OriginFor, + ) -> DispatchResultWithPostInfo { + let caller = ensure_signed(origin)?; + + ensure!( + T::FoundationAccountsProvider::get().contains(&caller), + Error::::NotFoundationAccount + ); + + let current_maintenance_status = MaintenanceStatus::::get(); + + ensure!(current_maintenance_status.is_maintenance, Error::::NotInMaintenanceMode); + + ensure!( + current_maintenance_status.is_upgradable_in_maintenance, + Error::::AlreadyNotUpgradableInMaintenanceMode + ); + + let maintenance_status = + MaintenanceStatusInfo { is_maintenance: true, is_upgradable_in_maintenance: false }; + + MaintenanceStatus::::put(maintenance_status); + + Pallet::::deposit_event(Event::UpgradabilityInMaintenanceModeSwitchedOff(caller)); + + Ok(().into()) + } + } +} + +impl Pallet { + fn is_maintenance() -> bool { + let current_maintenance_status = MaintenanceStatus::::get(); + current_maintenance_status.is_maintenance + } + + fn is_upgradable() -> bool { + let current_maintenance_status = MaintenanceStatus::::get(); + (!current_maintenance_status.is_maintenance) || + (current_maintenance_status.is_maintenance && + current_maintenance_status.is_upgradable_in_maintenance) + } +} + +impl SetMaintenanceModeOn for Pallet { + fn trigger_maintanance_mode() { + MaintenanceStatus::::mutate(|status: &mut _| { + status.is_maintenance = true; + }); + Self::deposit_event(Event::MaintenanceModeSwitchedOnExternally); + } +} + +impl GetMaintenanceStatusTrait for Pallet { + fn is_maintenance() -> bool { + >::is_maintenance() + } + + fn is_upgradable() -> bool { + >::is_upgradable() + } +} diff --git a/gasp-node/pallets/maintenance/src/mock.rs b/gasp-node/pallets/maintenance/src/mock.rs new file mode 100644 index 000000000..f23bdd237 --- /dev/null +++ b/gasp-node/pallets/maintenance/src/mock.rs @@ -0,0 +1,46 @@ +// Copyright (C) 2020 Mangata team + +use super::*; +use crate as pallet_maintenance; +use frame_support::{construct_runtime, derive_impl, parameter_types, traits::Everything}; +use frame_system as system; +use sp_runtime::BuildStorage; +use sp_std::convert::TryFrom; + +pub(crate) type AccountId = u64; + +type Block = frame_system::mocking::MockBlock; + +construct_runtime!( + pub enum Test { + System: frame_system, + Maintenance: pallet_maintenance, + } +); + +#[derive_impl(frame_system::config_preludes::TestDefaultConfig as frame_system::DefaultConfig)] +impl frame_system::Config for Test { + type Block = Block; +} + +pub struct FoundationAccountsProvider(PhantomData); + +impl Get> for FoundationAccountsProvider +where + ::AccountId: From, +{ + fn get() -> Vec { + vec![999u64.into()] + } +} + +impl pallet_maintenance::Config for Test { + type RuntimeEvent = RuntimeEvent; + type FoundationAccountsProvider = FoundationAccountsProvider; +} + +// This function basically just builds a genesis storage key/value store according to +// our desired mockup. +pub fn new_test_ext() -> sp_io::TestExternalities { + system::GenesisConfig::::default().build_storage().unwrap().into() +} diff --git a/gasp-node/pallets/maintenance/src/tests.rs b/gasp-node/pallets/maintenance/src/tests.rs new file mode 100644 index 000000000..0eade1dd5 --- /dev/null +++ b/gasp-node/pallets/maintenance/src/tests.rs @@ -0,0 +1,242 @@ +use super::*; +use crate::mock::*; +use frame_support::{assert_noop, assert_ok, error::BadOrigin}; + +#[test] +fn switching_maintenance_mode_on_works() { + new_test_ext().execute_with(|| { + assert_eq!(::is_maintenance(), false); + assert_eq!(::is_upgradable(), true); + + assert_eq!( + MaintenanceStatus::::get(), + MaintenanceStatusInfo { is_maintenance: false, is_upgradable_in_maintenance: false } + ); + + assert_noop!(Maintenance::switch_maintenance_mode_on(RuntimeOrigin::root()), BadOrigin); + assert_noop!( + Maintenance::switch_maintenance_mode_on(RuntimeOrigin::signed(0u64.into())), + Error::::NotFoundationAccount + ); + + assert_ok!(Maintenance::switch_maintenance_mode_on(RuntimeOrigin::signed(999u64.into())),); + + assert_eq!(::is_maintenance(), true); + assert_eq!(::is_upgradable(), false); + + assert_eq!( + MaintenanceStatus::::get(), + MaintenanceStatusInfo { is_maintenance: true, is_upgradable_in_maintenance: false } + ); + + assert_noop!( + Maintenance::switch_maintenance_mode_on(RuntimeOrigin::signed(999u64.into())), + Error::::AlreadyInMaintenanceMode + ); + }) +} + +#[test] +fn switching_maintenance_mode_off_works() { + new_test_ext().execute_with(|| { + assert_ok!(Maintenance::switch_maintenance_mode_on(RuntimeOrigin::signed(999u64.into())),); + + assert_eq!(::is_maintenance(), true); + assert_eq!(::is_upgradable(), false); + + assert_eq!( + MaintenanceStatus::::get(), + MaintenanceStatusInfo { is_maintenance: true, is_upgradable_in_maintenance: false } + ); + + assert_noop!(Maintenance::switch_maintenance_mode_off(RuntimeOrigin::root()), BadOrigin); + assert_noop!( + Maintenance::switch_maintenance_mode_off(RuntimeOrigin::signed(0u64.into())), + Error::::NotFoundationAccount + ); + + assert_ok!(Maintenance::switch_maintenance_mode_off(RuntimeOrigin::signed(999u64.into())),); + + assert_eq!(::is_maintenance(), false); + assert_eq!(::is_upgradable(), true); + + assert_eq!( + MaintenanceStatus::::get(), + MaintenanceStatusInfo { is_maintenance: false, is_upgradable_in_maintenance: false } + ); + + assert_noop!( + Maintenance::switch_maintenance_mode_off(RuntimeOrigin::signed(999u64.into())), + Error::::NotInMaintenanceMode + ); + }) +} + +#[test] +fn switching_maintenance_mode_off_while_upgradable_works_correctly() { + new_test_ext().execute_with(|| { + assert_ok!(Maintenance::switch_maintenance_mode_on(RuntimeOrigin::signed(999u64.into())),); + + assert_ok!(Maintenance::switch_upgradability_in_maintenance_mode_on( + RuntimeOrigin::signed(999u64.into()) + ),); + + assert_eq!(::is_maintenance(), true); + assert_eq!(::is_upgradable(), true); + + assert_eq!( + MaintenanceStatus::::get(), + MaintenanceStatusInfo { is_maintenance: true, is_upgradable_in_maintenance: true } + ); + + assert_ok!(Maintenance::switch_maintenance_mode_off(RuntimeOrigin::signed(999u64.into())),); + + assert_eq!(::is_maintenance(), false); + assert_eq!(::is_upgradable(), true); + + assert_eq!( + MaintenanceStatus::::get(), + MaintenanceStatusInfo { is_maintenance: false, is_upgradable_in_maintenance: false } + ); + }) +} + +#[test] +fn switch_upgradability_in_maintenance_mode_on_works() { + new_test_ext().execute_with(|| { + assert_eq!(::is_maintenance(), false); + assert_eq!(::is_upgradable(), true); + + assert_eq!( + MaintenanceStatus::::get(), + MaintenanceStatusInfo { is_maintenance: false, is_upgradable_in_maintenance: false } + ); + + assert_noop!( + Maintenance::switch_upgradability_in_maintenance_mode_on(RuntimeOrigin::signed( + 999u64.into() + )), + Error::::NotInMaintenanceMode + ); + + assert_ok!(Maintenance::switch_maintenance_mode_on(RuntimeOrigin::signed(999u64.into())),); + + assert_eq!(::is_maintenance(), true); + assert_eq!(::is_upgradable(), false); + + assert_eq!( + MaintenanceStatus::::get(), + MaintenanceStatusInfo { is_maintenance: true, is_upgradable_in_maintenance: false } + ); + + assert_noop!( + Maintenance::switch_upgradability_in_maintenance_mode_on(RuntimeOrigin::root()), + BadOrigin + ); + assert_noop!( + Maintenance::switch_upgradability_in_maintenance_mode_on(RuntimeOrigin::signed( + 0u64.into() + )), + Error::::NotFoundationAccount + ); + + assert_ok!(Maintenance::switch_upgradability_in_maintenance_mode_on( + RuntimeOrigin::signed(999u64.into()) + ),); + + assert_eq!(::is_maintenance(), true); + assert_eq!(::is_upgradable(), true); + + assert_eq!( + MaintenanceStatus::::get(), + MaintenanceStatusInfo { is_maintenance: true, is_upgradable_in_maintenance: true } + ); + + assert_noop!( + Maintenance::switch_upgradability_in_maintenance_mode_on(RuntimeOrigin::signed( + 999u64.into() + )), + Error::::AlreadyUpgradableInMaintenanceMode + ); + }) +} + +#[test] +fn switch_upgradability_in_maintenance_mode_off_works() { + new_test_ext().execute_with(|| { + assert_noop!( + Maintenance::switch_upgradability_in_maintenance_mode_off(RuntimeOrigin::signed( + 999u64.into() + )), + Error::::NotInMaintenanceMode + ); + + assert_ok!(Maintenance::switch_maintenance_mode_on(RuntimeOrigin::signed(999u64.into())),); + + assert_noop!( + Maintenance::switch_upgradability_in_maintenance_mode_off(RuntimeOrigin::signed( + 999u64.into() + )), + Error::::AlreadyNotUpgradableInMaintenanceMode + ); + + assert_ok!(Maintenance::switch_upgradability_in_maintenance_mode_on( + RuntimeOrigin::signed(999u64.into()) + ),); + + assert_eq!(::is_maintenance(), true); + assert_eq!(::is_upgradable(), true); + + assert_eq!( + MaintenanceStatus::::get(), + MaintenanceStatusInfo { is_maintenance: true, is_upgradable_in_maintenance: true } + ); + + assert_noop!( + Maintenance::switch_upgradability_in_maintenance_mode_off(RuntimeOrigin::root()), + BadOrigin + ); + assert_noop!( + Maintenance::switch_upgradability_in_maintenance_mode_off(RuntimeOrigin::signed( + 0u64.into() + )), + Error::::NotFoundationAccount + ); + + assert_ok!(Maintenance::switch_upgradability_in_maintenance_mode_off( + RuntimeOrigin::signed(999u64.into()) + ),); + + assert_eq!(::is_maintenance(), true); + assert_eq!(::is_upgradable(), false); + + assert_eq!( + MaintenanceStatus::::get(), + MaintenanceStatusInfo { is_maintenance: true, is_upgradable_in_maintenance: false } + ); + + assert_noop!( + Maintenance::switch_upgradability_in_maintenance_mode_off(RuntimeOrigin::signed( + 999u64.into() + )), + Error::::AlreadyNotUpgradableInMaintenanceMode + ); + }) +} + +#[test] +fn test_triggering_maintanance_mode_through_api_triggers_an_event() { + new_test_ext().execute_with(|| { + assert_eq!( + MaintenanceStatus::::get(), + MaintenanceStatusInfo { is_maintenance: false, is_upgradable_in_maintenance: false } + ); + + ::trigger_maintanance_mode(); + + assert_eq!( + MaintenanceStatus::::get(), + MaintenanceStatusInfo { is_maintenance: true, is_upgradable_in_maintenance: false } + ); + }) +} diff --git a/gasp-node/pallets/market/Cargo.toml b/gasp-node/pallets/market/Cargo.toml new file mode 100644 index 000000000..9af7274e0 --- /dev/null +++ b/gasp-node/pallets/market/Cargo.toml @@ -0,0 +1,69 @@ +[package] +name = "pallet-market" +description = "An overarching pallet providing entry points to pools & trading." +version = "1.0.0" +authors.workspace = true +homepage.workspace = true +repository.workspace = true +edition.workspace = true + +[package.metadata.docs.rs] +targets = ["x86_64-unknown-linux-gnu"] + +[dependencies] +codec = { workspace = true, default-features = false } +log = { workspace = true, default-features = false } +scale-info = { workspace = true, default-features = false, features = ["derive"] } +serde = { workspace = true, optional = true, features = ["derive"] } + +frame-benchmarking = { workspace = true, default-features = false } +frame-support = { workspace = true, default-features = false } +frame-system = { workspace = true, default-features = false } +mangata-support = { workspace = true, default-features = false } +mangata-types = { workspace = true, default-features = false } +sp-arithmetic = { workspace = true, default-features = false } +sp-api = { workspace = true, default-features = false } +sp-core = { workspace = true, default-features = false } +sp-io = { workspace = true, default-features = false } +sp-runtime = { workspace = true, default-features = false } +sp-std = { workspace = true, default-features = false } + +orml-traits = { workspace = true, default-features = false } +orml-tokens = { workspace = true, default-features = false } + +[dev-dependencies] +primitive-types = { version = "0.12.1", default-features = false, features = ["codec", "num-traits", "scale-info"] } +mockall.workspace = true + +pallet-xyk = { path = "../xyk" } +pallet-stable-swap = { path = "../stable-swap" } +pallet-vesting-mangata = { workspace = true } + +[features] +default = ["std"] +std = [ + "codec/std", + "frame-benchmarking/std", + "frame-support/std", + "frame-system/std", + "mangata-support/std", + "scale-info/std", + "serde", + "sp-arithmetic/std", + "sp-api/std", + "sp-core/std", + "sp-io/std", + "sp-runtime/std", + "sp-std/std", + "orml-traits/std", + "orml-tokens/std", +] + +runtime-benchmarks = ["frame-benchmarking/runtime-benchmarks"] + +try-runtime = [ + "frame-support/try-runtime", + "frame-system/try-runtime", + "sp-runtime/try-runtime", + "orml-tokens/try-runtime", +] diff --git a/gasp-node/pallets/market/rpc/Cargo.toml b/gasp-node/pallets/market/rpc/Cargo.toml new file mode 100644 index 000000000..1ddb47ea4 --- /dev/null +++ b/gasp-node/pallets/market/rpc/Cargo.toml @@ -0,0 +1,33 @@ +[package] +name = "market-rpc" +description = "RPC for market APIs" +version = "1.0.0" +authors.workspace = true +homepage.workspace = true +repository.workspace = true +edition.workspace = true + +[dependencies] +codec = { workspace = true } +jsonrpsee = { workspace = true, features = ["server", "macros"] } +serde = { workspace = true, features = ["derive"], optional = true } + +sp-api = { workspace = true, default-features = false } +sp-blockchain = { workspace = true, default-features = false } +sp-core = { workspace = true, default-features = false } +sp-rpc = { workspace = true, default-features = false } +sp-runtime = { workspace = true, default-features = false } +sp-std = { workspace = true, default-features = false } + +pallet-market = { path = "../", default-features = false } + +[features] +default = ["std"] +std = [ + "serde", + "sp-api/std", + "sp-core/std", + "sp-std/std", + "sp-runtime/std", + "pallet-market/std", +] diff --git a/gasp-node/pallets/market/rpc/src/lib.rs b/gasp-node/pallets/market/rpc/src/lib.rs new file mode 100644 index 000000000..c5ae30981 --- /dev/null +++ b/gasp-node/pallets/market/rpc/src/lib.rs @@ -0,0 +1,312 @@ +use codec::Codec; +use jsonrpsee::{ + core::{async_trait, RpcResult}, + proc_macros::rpc, + types::error::ErrorObject, +}; +pub use pallet_market::MarketRuntimeApi; +use pallet_market::{RpcAssetMetadata, RpcPoolInfo}; +use sp_api::ProvideRuntimeApi; +use sp_blockchain::HeaderBackend; +use sp_core::U256; +use sp_rpc::number::NumberOrHex; +use sp_runtime::traits::{Block as BlockT, MaybeDisplay, MaybeFromStr}; +use sp_std::convert::{TryFrom, TryInto}; +use std::sync::Arc; + +#[rpc(client, server)] +pub trait MarketApi { + #[method(name = "market_calculate_sell_price")] + fn calculate_sell_price( + &self, + pool_id: TokenId, + sell_asset_id: TokenId, + sell_amount: NumberOrHex, + at: Option, + ) -> RpcResult; + + #[method(name = "market_calculate_sell_price_with_impact")] + fn calculate_sell_price_with_impact( + &self, + pool_id: TokenId, + sell_asset_id: TokenId, + sell_amount: NumberOrHex, + at: Option, + ) -> RpcResult<(NumberOrHex, NumberOrHex)>; + + #[method(name = "market_calculate_buy_price")] + fn calculate_buy_price( + &self, + pool_id: TokenId, + buy_asset_id: TokenId, + buy_amount: NumberOrHex, + at: Option, + ) -> RpcResult; + + #[method(name = "market_calculate_buy_price_with_impact")] + fn calculate_buy_price_with_impact( + &self, + pool_id: TokenId, + buy_asset_id: TokenId, + buy_amount: NumberOrHex, + at: Option, + ) -> RpcResult<(NumberOrHex, NumberOrHex)>; + + #[method(name = "market_calculate_expected_amount_for_minting")] + fn calculate_expected_amount_for_minting( + &self, + pool_id: TokenId, + asset_id: TokenId, + amount: NumberOrHex, + at: Option, + ) -> RpcResult; + + #[method(name = "market_calculate_expected_lp_minted")] + fn calculate_expected_lp_minted( + &self, + pool_id: TokenId, + amounts: (NumberOrHex, NumberOrHex), + at: Option, + ) -> RpcResult; + + #[method(name = "market_get_burn_amount")] + fn get_burn_amount( + &self, + pool_id: TokenId, + liquidity_asset_amount: NumberOrHex, + at: Option, + ) -> RpcResult<(NumberOrHex, NumberOrHex)>; + + #[method(name = "market_get_pools_for_trading")] + fn get_pools_for_trading(&self, at: Option) -> RpcResult>; + + #[method(name = "market_get_tradeable_tokens")] + fn get_tradeable_tokens( + &self, + at: Option, + ) -> RpcResult>>; + + #[method(name = "market_get_pools")] + fn get_pools( + &self, + pool_id: Option, + at: Option, + ) -> RpcResult>>; +} + +pub struct Market { + client: Arc, + _marker: std::marker::PhantomData, +} + +impl Market { + pub fn new(client: Arc) -> Self { + Self { client, _marker: Default::default() } + } +} + +trait TryIntoBalance { + fn try_into_balance(self) -> RpcResult; +} + +impl> TryIntoBalance for NumberOrHex { + fn try_into_balance(self) -> RpcResult { + self.into_u256().try_into().or(Err(ErrorObject::owned( + 1, + "Unable to serve the request", + Some(String::from("input parameter doesnt fit into u128")), + ))) + } +} + +#[async_trait] +impl MarketApiServer<::Hash, Balance, TokenId> + for Market +where + Block: BlockT, + C: Send + Sync + 'static, + C: ProvideRuntimeApi, + C: HeaderBackend, + C::Api: MarketRuntimeApi, + Balance: Codec + MaybeDisplay + MaybeFromStr + TryFrom + Into + Default, + TokenId: Codec + MaybeDisplay + MaybeFromStr, +{ + fn calculate_sell_price( + &self, + pool_id: TokenId, + sell_asset_id: TokenId, + sell_amount: NumberOrHex, + _at: Option<::Hash>, + ) -> RpcResult { + let api = self.client.runtime_api(); + let at = self.client.info().best_hash; + + api.calculate_sell_price(at, pool_id, sell_asset_id, sell_amount.try_into_balance()?) + .map(|val| val.unwrap_or_default().into()) + .map_err(|e| { + ErrorObject::owned(1, "Unable to serve the request", Some(format!("{:?}", e))) + }) + } + + fn calculate_sell_price_with_impact( + &self, + pool_id: TokenId, + sell_asset_id: TokenId, + sell_amount: NumberOrHex, + _at: Option<::Hash>, + ) -> RpcResult<(NumberOrHex, NumberOrHex)> { + let api = self.client.runtime_api(); + let at = self.client.info().best_hash; + + api.calculate_sell_price_with_impact( + at, + pool_id, + sell_asset_id, + sell_amount.try_into_balance()?, + ) + .map(|val| val.unwrap_or_default()) + .map(|val| (val.0.into(), val.1.into())) + .map_err(|e| ErrorObject::owned(1, "Unable to serve the request", Some(format!("{:?}", e)))) + } + + fn calculate_buy_price( + &self, + pool_id: TokenId, + buy_asset_id: TokenId, + buy_amount: NumberOrHex, + _at: Option<::Hash>, + ) -> RpcResult { + let api = self.client.runtime_api(); + let at = self.client.info().best_hash; + + api.calculate_buy_price(at, pool_id, buy_asset_id, buy_amount.try_into_balance()?) + .map(|val| val.unwrap_or_default().into()) + .map_err(|e| { + ErrorObject::owned(1, "Unable to serve the request", Some(format!("{:?}", e))) + }) + } + + fn calculate_buy_price_with_impact( + &self, + pool_id: TokenId, + buy_asset_id: TokenId, + buy_amount: NumberOrHex, + _at: Option<::Hash>, + ) -> RpcResult<(NumberOrHex, NumberOrHex)> { + let api = self.client.runtime_api(); + let at = self.client.info().best_hash; + + api.calculate_buy_price_with_impact( + at, + pool_id, + buy_asset_id, + buy_amount.try_into_balance()?, + ) + .map(|val| val.unwrap_or_default()) + .map(|val| (val.0.into(), val.1.into())) + .map_err(|e| ErrorObject::owned(1, "Unable to serve the request", Some(format!("{:?}", e)))) + } + + fn get_burn_amount( + &self, + pool_id: TokenId, + liquidity_asset_amount: NumberOrHex, + _at: Option<::Hash>, + ) -> RpcResult<(NumberOrHex, NumberOrHex)> { + let api = self.client.runtime_api(); + let at = self.client.info().best_hash; + + api.get_burn_amount(at, pool_id, liquidity_asset_amount.try_into_balance()?) + .map(|val| val.unwrap_or_default()) + .map(|(val1, val2)| (val1.into(), val2.into())) + .map_err(|e| { + ErrorObject::owned(1, "Unable to serve the request", Some(format!("{:?}", e))) + }) + } + + fn get_pools_for_trading( + &self, + _at: Option<::Hash>, + ) -> RpcResult> { + let api = self.client.runtime_api(); + let at = self.client.info().best_hash; + + api.get_pools_for_trading(at).map_err(|e| { + ErrorObject::owned(1, "Unable to serve the request", Some(format!("{:?}", e))) + }) + } + + fn get_tradeable_tokens( + &self, + _at: Option<::Hash>, + ) -> RpcResult>> { + let api = self.client.runtime_api(); + let at = self.client.info().best_hash; + + api.get_tradeable_tokens(at).map_err(|e| { + ErrorObject::owned(1, "Unable to serve the request", Some(format!("{:?}", e))) + }) + } + + fn calculate_expected_amount_for_minting( + &self, + pool_id: TokenId, + asset_id: TokenId, + amount: NumberOrHex, + _at: Option<::Hash>, + ) -> RpcResult { + let api = self.client.runtime_api(); + let at = self.client.info().best_hash; + + api.calculate_expected_amount_for_minting(at, pool_id, asset_id, amount.try_into_balance()?) + .map(|val| val.unwrap_or_default().into()) + .map_err(|e| { + ErrorObject::owned(1, "Unable to serve the request", Some(format!("{:?}", e))) + }) + } + + fn calculate_expected_lp_minted( + &self, + pool_id: TokenId, + amounts: (NumberOrHex, NumberOrHex), + _at: Option<::Hash>, + ) -> RpcResult { + let api = self.client.runtime_api(); + let at = self.client.info().best_hash; + + let amount_0 = amounts.0.try_into_balance()?; + let amount_1 = amounts.1.try_into_balance()?; + + api.calculate_expected_lp_minted(at, pool_id, (amount_0, amount_1)) + .map(|val| val.unwrap_or_default().into()) + .map_err(|e| { + ErrorObject::owned(1, "Unable to serve the request", Some(format!("{:?}", e))) + }) + } + + fn get_pools( + &self, + pool_id: Option, + _at: Option<::Hash>, + ) -> RpcResult>> { + let api = self.client.runtime_api(); + let at = self.client.info().best_hash; + + api.get_pools(at, pool_id) + .map(|infos| { + { + infos.into_iter().map(|info| RpcPoolInfo { + pool_id: info.pool_id, + kind: info.kind, + lp_token_id: info.lp_token_id, + assets: info.assets, + reserves: info.reserves.into_iter().map(|r| r.into()).collect(), + }) + } + .collect() + }) + .map_err(|e| { + ErrorObject::owned(1, "Unable to serve the request", Some(format!("{:?}", e))) + }) + } +} diff --git a/gasp-node/pallets/market/src/benchmarking.rs b/gasp-node/pallets/market/src/benchmarking.rs new file mode 100644 index 000000000..32758d775 --- /dev/null +++ b/gasp-node/pallets/market/src/benchmarking.rs @@ -0,0 +1,908 @@ +use super::*; + +use crate::Pallet as Market; +use frame_benchmarking::{v2::*, whitelisted_caller}; +use frame_support::assert_ok; +use frame_system::RawOrigin as SystemOrigin; +use mangata_support::traits::ComputeIssuance; +use sp_runtime::{traits::Bounded, SaturatedConversion}; + +const UNIT: u128 = 1_000_000_000_000_000_000; + +fn assert_last_event(generic_event: ::RuntimeEvent) { + let events = frame_system::Pallet::::events(); + let system_event: ::RuntimeEvent = generic_event.into(); + // compare to the last event record + let frame_system::EventRecord { event, .. } = &events[events.len() - 1]; + assert_eq!(event, &system_event); +} + +fn create_asset(who: &T::AccountId) -> T::CurrencyId +where + T::Balance: From, +{ + let native = T::NativeCurrencyId::get(); + let id = T::Currency::create(who, (1_000_000_000 * UNIT).into()).expect("is ok"); + // create some default entries for stable swap to work + let _ = T::AssetRegistry::create_pool_asset(id, native, native); + id +} + +fn make_pool( + who: &T::AccountId, + kind: PoolKind, +) -> (T::CurrencyId, T::CurrencyId, T::CurrencyId) +where + T::Balance: From, +{ + let asset1 = create_asset::(&who); + let asset2 = create_asset::(&who); + let lp_token = T::Currency::get_next_currency_id(); + assert_ok!(Market::::create_pool( + SystemOrigin::Signed(who.clone()).into(), + kind, + asset1, + (1_000_000 * UNIT).into(), + asset2, + (UNIT / 1_000_000).into() + )); + + (asset1, asset2, lp_token) +} + +fn forward_to_next_session() { + let current_block: u32 = frame_system::Pallet::::block_number().saturated_into::(); + + let blocks_per_session: u32 = T::Rewards::rewards_period(); + let target_block_nr: u32; + let target_session_nr: u32; + + if current_block == 0_u32 || current_block == 1_u32 { + target_session_nr = 1_u32; + target_block_nr = blocks_per_session; + } else { + // to fail on user trying to manage block nr on its own + assert!(current_block % blocks_per_session == 0); + target_session_nr = (current_block / blocks_per_session) + 1_u32; + target_block_nr = target_session_nr * blocks_per_session; + } + + frame_system::Pallet::::set_block_number(target_block_nr.into()); + T::ComputeIssuance::compute_issuance(target_session_nr); +} + +#[benchmarks(where ::Balance: From, ::CurrencyId: From + Into)] +mod benchmarks { + use super::*; + + #[benchmark] + fn create_pool_xyk() { + let kind = PoolKind::Xyk; + let caller: T::AccountId = whitelisted_caller(); + let asset1 = create_asset::(&caller); + let asset2 = create_asset::(&caller); + let lp_token = T::Currency::get_next_currency_id(); + + #[extrinsic_call] + create_pool( + SystemOrigin::Signed(caller.clone()), + kind, + asset1, + (10 * UNIT).into(), + asset2, + 10.into(), + ); + + let lp_supply = T::Currency::total_issuance(lp_token); + assert_last_event::( + Event::LiquidityMinted { + who: caller, + pool_id: lp_token, + amounts_provided: ((10 * UNIT).into(), 10.into()), + lp_token, + lp_token_minted: lp_supply, + total_supply: lp_supply, + } + .into(), + ); + } + + #[benchmark] + fn mint_liquidity_xyk() { + let kind = PoolKind::Xyk; + let caller: T::AccountId = whitelisted_caller(); + let (asset1, _, pool_id) = make_pool::(&caller, kind); + T::Rewards::enable_native_rewards(pool_id, 1u8); + + let amount1 = (10 * UNIT).into(); + + assert_ok!(Market::::mint_liquidity( + SystemOrigin::Signed(caller.clone()).into(), + pool_id, + asset1, + amount1, + T::Balance::max_value(), + )); + + forward_to_next_session::(); + + let amount2 = + Market::::calculate_expected_amount_for_minting(pool_id, asset1, amount1).unwrap(); + let lp_minted = + Market::::calculate_expected_lp_minted(pool_id, (amount1, amount2)).unwrap(); + + #[extrinsic_call] + mint_liquidity(SystemOrigin::Signed(caller.clone()), pool_id, asset1, amount1, amount2); + + let lp_supply = T::Currency::total_issuance(pool_id); + assert_last_event::( + Event::LiquidityMinted { + who: caller, + pool_id, + amounts_provided: (amount1, amount2), + lp_token: pool_id, + lp_token_minted: lp_minted, + total_supply: lp_supply, + } + .into(), + ); + } + + #[benchmark] + fn mint_liquidity_fixed_amounts_xyk() { + let kind = PoolKind::Xyk; + let caller: T::AccountId = whitelisted_caller(); + let (asset1, _, pool_id) = make_pool::(&caller, kind); + T::Rewards::enable_native_rewards(pool_id, 1u8); + + let amounts = ((10 * UNIT).into(), Zero::zero()); + + assert_ok!(Market::::mint_liquidity( + SystemOrigin::Signed(caller.clone()).into(), + pool_id, + asset1, + amounts.0, + T::Balance::max_value(), + )); + + forward_to_next_session::(); + + // xyk returns None in this case, does a swap internally + let lp_minted = Market::::calculate_expected_lp_minted(pool_id, amounts) + .unwrap_or(2496237962221088530.into()); + + #[extrinsic_call] + mint_liquidity_fixed_amounts( + SystemOrigin::Signed(caller.clone()), + pool_id, + amounts, + Zero::zero(), + ); + + let lp_supply = T::Currency::total_issuance(pool_id); + assert_last_event::( + Event::LiquidityMinted { + who: caller, + pool_id, + amounts_provided: amounts, + lp_token: pool_id, + lp_token_minted: lp_minted, + total_supply: lp_supply, + } + .into(), + ); + } + + #[benchmark] + fn mint_liquidity_using_vesting_native_tokens_by_vesting_index_xyk() { + let kind = PoolKind::Xyk; + let caller: T::AccountId = whitelisted_caller(); + let native = T::NativeCurrencyId::get(); + // in test mock this would be native, in runtime we have genesis + let _ = create_asset::(&caller); + assert_ok!(T::Currency::mint(native, &caller, (1_000 * UNIT).into())); + + let asset2 = create_asset::(&caller); + let pool_id = T::Currency::get_next_currency_id(); + assert_ok!(Market::::create_pool( + SystemOrigin::Signed(caller.clone()).into(), + kind, + native, + UNIT.into(), + asset2, + UNIT.into() + )); + + T::Rewards::enable_native_rewards(pool_id, 1u8); + assert_ok!(Market::::mint_liquidity( + SystemOrigin::Signed(caller.clone()).into(), + pool_id, + asset2, + (10 * UNIT).into(), + T::Balance::max_value(), + )); + + forward_to_next_session::(); + + let lock = (5 * UNIT).into(); + assert_ok!(T::Vesting::lock_tokens(&caller, native, lock, None, 1_000_000_u32.into())); + + forward_to_next_session::(); + + let amount1 = UNIT.into(); + let amount2 = + Market::::calculate_expected_amount_for_minting(pool_id, native, amount1).unwrap(); + let lp_minted = + Market::::calculate_expected_lp_minted(pool_id, (amount1, amount2)).unwrap(); + + #[extrinsic_call] + mint_liquidity_using_vesting_native_tokens_by_vesting_index( + SystemOrigin::Signed(caller.clone()), + pool_id, + 0, + Some(amount1), + T::Balance::max_value(), + ); + + let lp_supply = T::Currency::total_issuance(pool_id); + assert_last_event::( + Event::LiquidityMinted { + who: caller, + pool_id, + amounts_provided: (amount1, amount2), + lp_token: pool_id, + lp_token_minted: lp_minted, + total_supply: lp_supply, + } + .into(), + ); + } + + #[benchmark] + fn mint_liquidity_using_vesting_native_tokens_xyk() { + let kind = PoolKind::Xyk; + let caller: T::AccountId = whitelisted_caller(); + let native = T::NativeCurrencyId::get(); + // in test mock this would be native, in runtime we have genesis + let _ = create_asset::(&caller); + assert_ok!(T::Currency::mint(native, &caller, (1_000 * UNIT).into())); + + let asset2 = create_asset::(&caller); + let pool_id = T::Currency::get_next_currency_id(); + assert_ok!(Market::::create_pool( + SystemOrigin::Signed(caller.clone()).into(), + kind, + native, + UNIT.into(), + asset2, + UNIT.into() + )); + + T::Rewards::enable_native_rewards(pool_id, 1u8); + assert_ok!(Market::::mint_liquidity( + SystemOrigin::Signed(caller.clone()).into(), + pool_id, + asset2, + (10 * UNIT).into(), + T::Balance::max_value(), + )); + + forward_to_next_session::(); + + let lock = (5 * UNIT).into(); + assert_ok!(T::Vesting::lock_tokens(&caller, native, lock, None, 1_000_000_u32.into())); + + forward_to_next_session::(); + + let amount1 = UNIT.into(); + let amount2 = + Market::::calculate_expected_amount_for_minting(pool_id, native, amount1).unwrap(); + let lp_minted = + Market::::calculate_expected_lp_minted(pool_id, (amount1, amount2)).unwrap(); + + #[extrinsic_call] + mint_liquidity_using_vesting_native_tokens( + SystemOrigin::Signed(caller.clone()), + pool_id, + amount1, + amount2, + ); + + let lp_supply = T::Currency::total_issuance(pool_id); + assert_last_event::( + Event::LiquidityMinted { + who: caller, + pool_id, + amounts_provided: (amount1, amount2), + lp_token: pool_id, + lp_token_minted: lp_minted, + total_supply: lp_supply, + } + .into(), + ); + } + + #[benchmark] + fn burn_liquidity_xyk() { + let kind = PoolKind::Xyk; + let caller: T::AccountId = whitelisted_caller(); + let (asset1, _, pool_id) = make_pool::(&caller, kind); + T::Rewards::enable_native_rewards(pool_id, 1u8); + + let amount1 = (10 * UNIT).into(); + + assert_ok!(Market::::mint_liquidity( + SystemOrigin::Signed(caller.clone()).into(), + pool_id, + asset1, + amount1, + T::Balance::max_value(), + )); + + forward_to_next_session::(); + + let burn_amount = UNIT.into(); + let min_amounts = Market::::get_burn_amount(pool_id, burn_amount).unwrap(); + + #[extrinsic_call] + burn_liquidity( + SystemOrigin::Signed(caller.clone()), + pool_id, + burn_amount, + min_amounts.0, + min_amounts.1, + ); + + let lp_supply = T::Currency::total_issuance(pool_id); + assert_last_event::( + Event::LiquidityBurned { + who: caller, + pool_id, + amounts: min_amounts, + burned_amount: burn_amount, + total_supply: lp_supply, + } + .into(), + ); + } + + #[benchmark] + fn multiswap_asset_xyk(y: Linear<2, 100>) { + let kind = PoolKind::Xyk; + let caller: T::AccountId = whitelisted_caller(); + // in test mock this would be native, in runtime we have genesis + let _ = create_asset::(&caller); + let native = T::NativeCurrencyId::get(); + assert_ok!(T::Currency::mint(native, &caller, (100_000 * UNIT).into())); + + let from = T::Currency::get_next_currency_id(); + for _ in 0..y { + create_asset::(&caller); + } + let pool_id_start = T::Currency::get_next_currency_id(); + for i in 0..(y - 1) { + assert_ok!(Market::::create_pool( + SystemOrigin::Signed(caller.clone()).into(), + kind.clone(), + from + i.into(), + UNIT.into(), + from + (i + 1).into(), + UNIT.into() + )); + } + let pool_id_end = T::Currency::get_next_currency_id(); + for i in 0..y { + assert_ok!(Market::::create_pool( + SystemOrigin::Signed(caller.clone()).into(), + PoolKind::Xyk, + native, + UNIT.into(), + from + i.into(), + UNIT.into() + )); + } + let swaps: Vec = + (pool_id_start.into()..pool_id_end.into()).map(|id: u32| id.into()).collect(); + + let to = pool_id_start - 1.into(); + + let before1 = T::Currency::available_balance(from, &caller); + let before2 = T::Currency::available_balance(to, &caller); + + let amount = UNIT.into(); + #[extrinsic_call] + multiswap_asset( + SystemOrigin::Signed(caller.clone()), + swaps, + from, + amount, + to, + Zero::zero(), + ); + + let after1 = T::Currency::available_balance(from, &caller); + let after2 = T::Currency::available_balance(to, &caller); + + assert_eq!(before1, after1 + amount); + assert!(before2 < after2); + } + + #[benchmark] + fn multiswap_asset_buy_xyk(y: Linear<2, 100>) { + let kind = PoolKind::Xyk; + let caller: T::AccountId = whitelisted_caller(); + // in test mock this would be native, in runtime we have genesis + let _ = create_asset::(&caller); + let native = T::NativeCurrencyId::get(); + assert_ok!(T::Currency::mint(native, &caller, (100_000 * UNIT).into())); + + let from = T::Currency::get_next_currency_id(); + for _ in 0..y { + create_asset::(&caller); + } + let pool_id_start = T::Currency::get_next_currency_id(); + for i in 0..(y - 1) { + assert_ok!(Market::::create_pool( + SystemOrigin::Signed(caller.clone()).into(), + kind.clone(), + from + i.into(), + UNIT.into(), + from + (i + 1).into(), + UNIT.into() + )); + } + let pool_id_end = T::Currency::get_next_currency_id(); + for i in 0..y { + assert_ok!(Market::::create_pool( + SystemOrigin::Signed(caller.clone()).into(), + PoolKind::Xyk, + native, + UNIT.into(), + from + i.into(), + UNIT.into() + )); + } + let swaps: Vec = + (pool_id_start.into()..pool_id_end.into()).map(|id: u32| id.into()).collect(); + + let to = pool_id_start - 1.into(); + + let before1 = T::Currency::available_balance(from, &caller); + let before2 = T::Currency::available_balance(to, &caller); + + let amount = (UNIT / 1_000).into(); + #[extrinsic_call] + multiswap_asset_buy( + SystemOrigin::Signed(caller.clone()), + swaps, + to, + amount, + from, + T::Balance::max_value(), + ); + + let after1 = T::Currency::available_balance(from, &caller); + let after2 = T::Currency::available_balance(to, &caller); + + assert!(before1 > after1); + assert_eq!(before2 + amount, after2); + } + + // copypaste for stableswap + #[benchmark] + fn create_pool_sswap() { + let kind = PoolKind::StableSwap; + let caller: T::AccountId = whitelisted_caller(); + let asset1 = create_asset::(&caller); + let asset2 = create_asset::(&caller); + let lp_token = T::Currency::get_next_currency_id(); + + #[extrinsic_call] + create_pool( + SystemOrigin::Signed(caller.clone()), + kind, + asset1, + (10 * UNIT).into(), + asset2, + 10.into(), + ); + + let lp_supply = T::Currency::total_issuance(lp_token); + assert_last_event::( + Event::LiquidityMinted { + who: caller, + pool_id: lp_token, + amounts_provided: ((10 * UNIT).into(), 10.into()), + lp_token, + lp_token_minted: lp_supply, + total_supply: lp_supply, + } + .into(), + ); + } + + #[benchmark] + fn mint_liquidity_sswap() { + let kind = PoolKind::StableSwap; + let caller: T::AccountId = whitelisted_caller(); + let (asset1, _, pool_id) = make_pool::(&caller, kind); + T::Rewards::enable_native_rewards(pool_id, 1u8); + + let amount1 = (10 * UNIT).into(); + + assert_ok!(Market::::mint_liquidity( + SystemOrigin::Signed(caller.clone()).into(), + pool_id, + asset1, + amount1, + T::Balance::max_value(), + )); + + forward_to_next_session::(); + + let amount2 = + Market::::calculate_expected_amount_for_minting(pool_id, asset1, amount1).unwrap(); + let lp_minted = + Market::::calculate_expected_lp_minted(pool_id, (amount1, amount2)).unwrap(); + + #[extrinsic_call] + mint_liquidity(SystemOrigin::Signed(caller.clone()), pool_id, asset1, amount1, amount2); + + let lp_supply = T::Currency::total_issuance(pool_id); + assert_last_event::( + Event::LiquidityMinted { + who: caller, + pool_id, + amounts_provided: (amount1, amount2), + lp_token: pool_id, + lp_token_minted: lp_minted, + total_supply: lp_supply, + } + .into(), + ); + } + + #[benchmark] + fn mint_liquidity_fixed_amounts_sswap() { + let kind = PoolKind::StableSwap; + let caller: T::AccountId = whitelisted_caller(); + let (asset1, _, pool_id) = make_pool::(&caller, kind); + T::Rewards::enable_native_rewards(pool_id, 1u8); + + let amounts = ((10 * UNIT).into(), Zero::zero()); + + assert_ok!(Market::::mint_liquidity( + SystemOrigin::Signed(caller.clone()).into(), + pool_id, + asset1, + amounts.0, + T::Balance::max_value(), + )); + + forward_to_next_session::(); + + // xyk returns None in this case, does a swap internally + let lp_minted = Market::::calculate_expected_lp_minted(pool_id, amounts) + .unwrap_or(4192957199889574550.into()); + + #[extrinsic_call] + mint_liquidity_fixed_amounts( + SystemOrigin::Signed(caller.clone()), + pool_id, + amounts, + Zero::zero(), + ); + + let lp_supply = T::Currency::total_issuance(pool_id); + assert_last_event::( + Event::LiquidityMinted { + who: caller, + pool_id, + amounts_provided: amounts, + lp_token: pool_id, + lp_token_minted: lp_minted, + total_supply: lp_supply, + } + .into(), + ); + } + + #[benchmark] + fn mint_liquidity_using_vesting_native_tokens_by_vesting_index_sswap() { + let kind = PoolKind::StableSwap; + let caller: T::AccountId = whitelisted_caller(); + let native = T::NativeCurrencyId::get(); + // in test mock this would be native, in runtime we have genesis + let _ = create_asset::(&caller); + assert_ok!(T::Currency::mint(native, &caller, (1_000 * UNIT).into())); + + let asset2 = create_asset::(&caller); + let pool_id = T::Currency::get_next_currency_id(); + assert_ok!(Market::::create_pool( + SystemOrigin::Signed(caller.clone()).into(), + kind, + native, + UNIT.into(), + asset2, + UNIT.into() + )); + + T::Rewards::enable_native_rewards(pool_id, 1u8); + assert_ok!(Market::::mint_liquidity( + SystemOrigin::Signed(caller.clone()).into(), + pool_id, + asset2, + (10 * UNIT).into(), + T::Balance::max_value(), + )); + + forward_to_next_session::(); + + let lock = (5 * UNIT).into(); + assert_ok!(T::Vesting::lock_tokens(&caller, native, lock, None, 1_000_000_u32.into())); + + forward_to_next_session::(); + + let amount1 = UNIT.into(); + let amount2 = + Market::::calculate_expected_amount_for_minting(pool_id, native, amount1).unwrap(); + let lp_minted = + Market::::calculate_expected_lp_minted(pool_id, (amount1, amount2)).unwrap(); + + #[extrinsic_call] + mint_liquidity_using_vesting_native_tokens_by_vesting_index( + SystemOrigin::Signed(caller.clone()), + pool_id, + 0, + Some(amount1), + T::Balance::max_value(), + ); + + let lp_supply = T::Currency::total_issuance(pool_id); + assert_last_event::( + Event::LiquidityMinted { + who: caller, + pool_id, + amounts_provided: (amount1, amount2), + lp_token: pool_id, + lp_token_minted: lp_minted, + total_supply: lp_supply, + } + .into(), + ); + } + + #[benchmark] + fn mint_liquidity_using_vesting_native_tokens_sswap() { + let kind = PoolKind::StableSwap; + let caller: T::AccountId = whitelisted_caller(); + let native = T::NativeCurrencyId::get(); + // in test mock this would be native, in runtime we have genesis + let _ = create_asset::(&caller); + assert_ok!(T::Currency::mint(native, &caller, (1_000 * UNIT).into())); + + let asset2 = create_asset::(&caller); + let pool_id = T::Currency::get_next_currency_id(); + assert_ok!(Market::::create_pool( + SystemOrigin::Signed(caller.clone()).into(), + kind, + native, + UNIT.into(), + asset2, + UNIT.into() + )); + + T::Rewards::enable_native_rewards(pool_id, 1u8); + assert_ok!(Market::::mint_liquidity( + SystemOrigin::Signed(caller.clone()).into(), + pool_id, + asset2, + (10 * UNIT).into(), + T::Balance::max_value(), + )); + + forward_to_next_session::(); + + let lock = (5 * UNIT).into(); + assert_ok!(T::Vesting::lock_tokens(&caller, native, lock, None, 1_000_000_u32.into())); + + forward_to_next_session::(); + + let amount1 = UNIT.into(); + let amount2 = + Market::::calculate_expected_amount_for_minting(pool_id, native, amount1).unwrap(); + let lp_minted = + Market::::calculate_expected_lp_minted(pool_id, (amount1, amount2)).unwrap(); + + #[extrinsic_call] + mint_liquidity_using_vesting_native_tokens( + SystemOrigin::Signed(caller.clone()), + pool_id, + amount1, + amount2, + ); + + let lp_supply = T::Currency::total_issuance(pool_id); + assert_last_event::( + Event::LiquidityMinted { + who: caller, + pool_id, + amounts_provided: (amount1, amount2), + lp_token: pool_id, + lp_token_minted: lp_minted, + total_supply: lp_supply, + } + .into(), + ); + } + + #[benchmark] + fn burn_liquidity_sswap() { + let kind = PoolKind::StableSwap; + let caller: T::AccountId = whitelisted_caller(); + let (asset1, _, pool_id) = make_pool::(&caller, kind); + T::Rewards::enable_native_rewards(pool_id, 1u8); + + let amount1 = (10 * UNIT).into(); + + assert_ok!(Market::::mint_liquidity( + SystemOrigin::Signed(caller.clone()).into(), + pool_id, + asset1, + amount1, + T::Balance::max_value(), + )); + + forward_to_next_session::(); + + let burn_amount = UNIT.into(); + let min_amounts = Market::::get_burn_amount(pool_id, burn_amount).unwrap(); + + #[extrinsic_call] + burn_liquidity( + SystemOrigin::Signed(caller.clone()), + pool_id, + burn_amount, + min_amounts.0, + min_amounts.1, + ); + + let lp_supply = T::Currency::total_issuance(pool_id); + assert_last_event::( + Event::LiquidityBurned { + who: caller, + pool_id, + amounts: min_amounts, + burned_amount: burn_amount, + total_supply: lp_supply, + } + .into(), + ); + } + + #[benchmark] + fn multiswap_asset_sswap(y: Linear<2, 100>) { + let kind = PoolKind::StableSwap; + let caller: T::AccountId = whitelisted_caller(); + // in test mock this would be native, in runtime we have genesis + let _ = create_asset::(&caller); + let native = T::NativeCurrencyId::get(); + assert_ok!(T::Currency::mint(native, &caller, (100_000 * UNIT).into())); + + let from = T::Currency::get_next_currency_id(); + for _ in 0..y { + create_asset::(&caller); + } + let pool_id_start = T::Currency::get_next_currency_id(); + for i in 0..(y - 1) { + assert_ok!(Market::::create_pool( + SystemOrigin::Signed(caller.clone()).into(), + kind.clone(), + from + i.into(), + UNIT.into(), + from + (i + 1).into(), + UNIT.into() + )); + } + let pool_id_end = T::Currency::get_next_currency_id(); + for i in 0..y { + assert_ok!(Market::::create_pool( + SystemOrigin::Signed(caller.clone()).into(), + PoolKind::Xyk, + native, + UNIT.into(), + from + i.into(), + UNIT.into() + )); + } + let swaps: Vec = + (pool_id_start.into()..pool_id_end.into()).map(|id: u32| id.into()).collect(); + + let to = pool_id_start - 1.into(); + + let before1 = T::Currency::available_balance(from, &caller); + let before2 = T::Currency::available_balance(to, &caller); + + let amount = UNIT.into(); + #[extrinsic_call] + multiswap_asset( + SystemOrigin::Signed(caller.clone()), + swaps, + from, + amount, + to, + Zero::zero(), + ); + + let after1 = T::Currency::available_balance(from, &caller); + let after2 = T::Currency::available_balance(to, &caller); + + assert_eq!(before1, after1 + amount); + assert!(before2 < after2); + } + + #[benchmark] + fn multiswap_asset_buy_sswap(y: Linear<2, 100>) { + let kind = PoolKind::StableSwap; + let caller: T::AccountId = whitelisted_caller(); + // in test mock this would be native, in runtime we have genesis + let _ = create_asset::(&caller); + let native = T::NativeCurrencyId::get(); + assert_ok!(T::Currency::mint(native, &caller, (100_000 * UNIT).into())); + + let from = T::Currency::get_next_currency_id(); + for _ in 0..y { + create_asset::(&caller); + } + let pool_id_start = T::Currency::get_next_currency_id(); + for i in 0..(y - 1) { + assert_ok!(Market::::create_pool( + SystemOrigin::Signed(caller.clone()).into(), + kind.clone(), + from + i.into(), + UNIT.into(), + from + (i + 1).into(), + UNIT.into() + )); + } + + let pool_id_end = T::Currency::get_next_currency_id(); + for i in 0..y { + assert_ok!(Market::::create_pool( + SystemOrigin::Signed(caller.clone()).into(), + PoolKind::Xyk, + native, + UNIT.into(), + from + i.into(), + UNIT.into() + )); + } + + let swaps: Vec = + (pool_id_start.into()..pool_id_end.into()).map(|id: u32| id.into()).collect(); + + let to = pool_id_start - 1.into(); + + let before1 = T::Currency::available_balance(from, &caller); + let before2 = T::Currency::available_balance(to, &caller); + + let amount = (UNIT / 1_000).into(); + #[extrinsic_call] + multiswap_asset_buy( + SystemOrigin::Signed(caller.clone()), + swaps, + to, + amount, + from, + T::Balance::max_value(), + ); + + let after1 = T::Currency::available_balance(from, &caller); + let after2 = T::Currency::available_balance(to, &caller); + + assert!(before1 > after1); + assert_eq!(before2 + amount, after2); + } + + impl_benchmark_test_suite!(Market, crate::mock::new_test_ext(), crate::mock::Test); +} diff --git a/gasp-node/pallets/market/src/lib.rs b/gasp-node/pallets/market/src/lib.rs new file mode 100644 index 000000000..e9d0e251c --- /dev/null +++ b/gasp-node/pallets/market/src/lib.rs @@ -0,0 +1,1275 @@ +//! # Market Pallet. + +#![cfg_attr(not(feature = "std"), no_std)] + +use codec::Codec; +#[cfg(feature = "std")] +use serde::{Deserialize, Serialize}; + +use frame_support::{ + ensure, + pallet_prelude::*, + traits::{ + tokens::{ + currency::{MultiTokenCurrency, MultiTokenVestingLocks}, + Balance, CurrencyId, + }, + Contains, + }, +}; +use frame_system::pallet_prelude::*; +use mangata_support::{ + pools::{ComputeBalances, Inspect, Mutate, PoolPair, SwapResult, TreasuryBurn, Valuate}, + traits::{ + AssetRegistryProviderTrait, GetMaintenanceStatusTrait, ProofOfStakeRewardsApi, + Valuate as ValuateXyk, XykFunctionsTrait, + }, +}; +use mangata_types::multipurpose_liquidity::ActivateKind; + +#[cfg(feature = "runtime-benchmarks")] +use mangata_support::traits::ComputeIssuance; + +use sp_runtime::traits::{MaybeDisplay, MaybeFromStr, Saturating, Zero}; +use sp_std::{convert::TryInto, fmt::Debug, vec, vec::Vec}; + +use orml_tokens::MultiTokenCurrencyExtended; +use orml_traits::asset_registry::Inspect as AssetRegistryInspect; + +pub mod weights; +pub use crate::weights::WeightInfo; + +pub use pallet::*; + +#[cfg(feature = "runtime-benchmarks")] +mod benchmarking; +#[cfg(test)] +mod mock; +#[cfg(test)] +mod tests; + +#[cfg_attr(feature = "std", derive(Serialize, Deserialize))] +#[derive(Encode, Decode, Eq, PartialEq, Debug, Clone, TypeInfo)] +pub enum PoolKind { + /// Classic XYK invariant + Xyk, + /// StableSwap + StableSwap, +} + +#[derive(Clone, Debug)] +pub struct PoolInfo { + pub pool_id: CurrencyId, + pub kind: PoolKind, + pub pool: PoolPair, +} + +impl PoolInfo { + fn same_and_other(&self, same: C) -> Option<(C, C)> { + if same == self.pool.0 { + Some(self.pool) + } else if same == self.pool.1 { + Some((self.pool.1, self.pool.0)) + } else { + None + } + } +} + +#[derive(Encode, Decode, Eq, PartialEq, Debug, Clone, TypeInfo)] +pub struct AtomicSwap { + pub pool_id: CurrencyId, + pub kind: PoolKind, + pub asset_in: CurrencyId, + pub asset_out: CurrencyId, + pub amount_in: Balance, + pub amount_out: Balance, +} + +// use LP token as pool id, extra type for readability +pub type PoolIdOf = ::CurrencyId; +// pools are composed of a pair of assets +pub type PoolInfoOf = PoolInfo<::CurrencyId>; +pub type AssetPairOf = (::CurrencyId, ::CurrencyId); +pub type BalancePairOf = (::Balance, ::Balance); +pub type AtomicSwapOf = AtomicSwap<::CurrencyId, ::Balance>; + +#[frame_support::pallet] +pub mod pallet { + use super::*; + + #[pallet::pallet] + pub struct Pallet(_); + + #[pallet::config] + pub trait Config: frame_system::Config { + /// Overarching event type. + type RuntimeEvent: From> + IsType<::RuntimeEvent>; + + /// Currency type that this works on. + type Currency: MultiTokenCurrencyExtended< + Self::AccountId, + Balance = Self::Balance, + CurrencyId = Self::CurrencyId, + >; + + /// The `Currency::Balance` type of the currency. + type Balance: Balance; + + /// Identifier for the assets. + type CurrencyId: CurrencyId; + + /// Native currency + type NativeCurrencyId: Get; + + /// Xyk pools + type Xyk: XykFunctionsTrait + + Inspect + + ValuateXyk + + TreasuryBurn + + ComputeBalances; + + /// StableSwap pools + type StableSwap: Mutate + + ComputeBalances; + + /// Reward apis for native asset LP tokens activation + type Rewards: ProofOfStakeRewardsApi; + + /// Vesting apis for providing native vested liquidity + type Vesting: MultiTokenVestingLocks< + Self::AccountId, + Moment = BlockNumberFor, + Currency = Self::Currency, + >; + + /// Apis for LP asset creation in asset registry + type AssetRegistry: AssetRegistryProviderTrait; + + /// List of tokens ids that are not allowed to be used at all + type DisabledTokens: Contains; + + /// List of assets that are not allowed to form a pool + type DisallowedPools: Contains>; + + /// Disable trading with maintenance mode + type MaintenanceStatusProvider: GetMaintenanceStatusTrait; + + /// Weight information for extrinsics in this pallet. + type WeightInfo: WeightInfo; + + /// Tokens which cannot be transfered by extrinsics/user or use in pool, unless foundation override + type NontransferableTokens: Contains; + + /// A list of Foundation members with elevated rights + type FoundationAccountsProvider: Get>; + + /// A special account used for nontransferable tokens to allow 'selling' to balance pools + type ArbitrageBot: Contains; + + #[cfg(feature = "runtime-benchmarks")] + type ComputeIssuance: ComputeIssuance; + } + + #[pallet::error] + pub enum Error { + /// No such pool exists + NoSuchPool, + /// Asset id is not allowed + FunctionNotAvailableForThisToken, + /// Asset ids are not allowed to create a pool + DisallowedPool, + /// Insufficient output amount does not meet min requirements + InsufficientOutputAmount, + /// Excesive input amount does not meet max requirements + ExcesiveInputAmount, + /// Pool is not paired with native currency id + NotPairedWithNativeAsset, + /// Not a promoted pool + NotAPromotedPool, + /// Asset does not exists + AssetDoesNotExists, + /// Operation not available for such pool type + FunctionNotAvailableForThisPoolKind, + /// Trading blocked by maintenance mode + TradingBlockedByMaintenanceMode, + /// Multi swap path contains repetive pools + MultiSwapSamePool, + /// Input asset id is not connected with output asset id for given pools + MultiSwapPathInvalid, + /// Asset cannot be used to create or modify a pool + NontransferableToken, + } + + // Pallet's events. + #[pallet::event] + #[pallet::generate_deposit(pub(super) fn deposit_event)] + pub enum Event { + /// Assets were swapped successfully + AssetsSwapped { + /// The account that initiated the swap + who: T::AccountId, + /// List of the atomic asset swaps + swaps: Vec>, + }, + + /// A successful call of the `CretaPool` extrinsic will create this event. + PoolCreated { + /// The account that created the pool. + creator: T::AccountId, + /// The pool id and the account ID of the pool. + pool_id: PoolIdOf, + /// The id of the liquidity tokens that will be minted when assets are added to this + /// pool. + lp_token: T::CurrencyId, + /// The asset ids associated with the pool. Note that the order of the assets may not be + /// the same as the order specified in the create pool extrinsic. + assets: AssetPairOf, + }, + + /// A successful call of the `AddLiquidity` extrinsic will create this event. + LiquidityMinted { + /// The account that the liquidity was taken from. + who: T::AccountId, + /// The id of the pool that the liquidity was added to. + pool_id: PoolIdOf, + /// The amounts of the assets that were added to the pool. + amounts_provided: BalancePairOf, + /// The id of the LP token that was minted. + lp_token: T::CurrencyId, + /// The amount of lp tokens that were minted of that id. + lp_token_minted: T::Balance, + /// The new total supply of the associated LP token. + total_supply: T::Balance, + }, + + /// A successful call of the `RemoveLiquidity` extrinsic will create this event. + LiquidityBurned { + /// The account that the liquidity token was taken from. + who: T::AccountId, + /// The id of the pool that the liquidity was taken from. + pool_id: PoolIdOf, + /// The amount of the asset that was received. + amounts: BalancePairOf, + /// The amount of the associated LP token that was burned. + burned_amount: T::Balance, + /// The new total supply of the associated LP token. + total_supply: T::Balance, + }, + } + + #[pallet::hooks] + impl Hooks> for Pallet { + fn integrity_test() { + assert!(true, "template",); + } + } + + /// Pallet's callable functions. + #[pallet::call] + impl Pallet { + /// Creates a liquidity pool and an associated new `lp_token` asset + /// For a StableSwap pool, the "stable" rate is computed from the ratio of input amounts, max rate is 1e18:1 + #[pallet::call_index(0)] + #[pallet::weight( + T::WeightInfo::create_pool_xyk().max( + T::WeightInfo::create_pool_sswap() + ))] + pub fn create_pool( + origin: OriginFor, + kind: PoolKind, + first_asset_id: T::CurrencyId, + first_asset_amount: T::Balance, + second_asset_id: T::CurrencyId, + second_asset_amount: T::Balance, + ) -> DispatchResult { + let sender = ensure_signed(origin)?; + + // check assets id, or the foundation has a veto + ensure!( + (!T::NontransferableTokens::contains(&first_asset_id) && + !T::NontransferableTokens::contains(&second_asset_id)) || + T::FoundationAccountsProvider::get().contains(&sender), + Error::::NontransferableToken + ); + + Self::check_assets_allowed((first_asset_id, second_asset_id))?; + + ensure!( + !T::DisallowedPools::contains(&(first_asset_id, second_asset_id)), + Error::::DisallowedPool, + ); + + let lp_token = match kind { + PoolKind::Xyk => { + let lp_token = T::Xyk::create_pool( + sender.clone(), + first_asset_id, + first_asset_amount, + second_asset_id, + second_asset_amount, + )?; + lp_token + }, + PoolKind::StableSwap => { + let lp_token = T::StableSwap::create_pool( + &sender, + first_asset_id, + first_asset_amount, + second_asset_id, + second_asset_amount, + )?; + + T::AssetRegistry::create_pool_asset(lp_token, first_asset_id, second_asset_id)?; + lp_token + }, + }; + + Self::deposit_event(Event::PoolCreated { + creator: sender.clone(), + pool_id: lp_token, + lp_token, + assets: (first_asset_id, second_asset_id), + }); + + let lp_supply = T::Currency::total_issuance(lp_token); + Self::deposit_event(Event::LiquidityMinted { + who: sender, + pool_id: lp_token, + amounts_provided: (first_asset_amount, second_asset_amount), + lp_token, + lp_token_minted: lp_supply, + total_supply: lp_supply, + }); + + Ok(()) + } + + /// Provide liquidity into the pool of `pool_id`, suitable for Xyk pools. + /// An optimal amount of the other asset will be calculated on current rate, + /// a maximum amount should be provided to limit possible rate slippage. + /// For a StableSwap pool a rate of 1:1 is used. + /// Liquidity tokens that represent this share of the pool will be sent to origin. + #[pallet::call_index(1)] + #[pallet::weight( + T::WeightInfo::mint_liquidity_xyk().max( + T::WeightInfo::mint_liquidity_sswap() + ))] + pub fn mint_liquidity( + origin: OriginFor, + pool_id: PoolIdOf, + asset_id: T::CurrencyId, + asset_amount: T::Balance, + max_other_asset_amount: T::Balance, + ) -> DispatchResult { + let sender = ensure_signed(origin)?; + + let pool_info = Self::get_pool_info(pool_id)?; + // check assets id, foundation has no veto + ensure!( + !T::NontransferableTokens::contains(&pool_info.pool.0) && + !T::NontransferableTokens::contains(&pool_info.pool.1), + Error::::NontransferableToken + ); + Self::check_assets_allowed(pool_info.pool)?; + + let (lp_amount, other_asset_amount) = Self::do_mint_liquidity( + &sender, + pool_info, + asset_id, + asset_amount, + max_other_asset_amount, + true, + )?; + + let lp_supply = T::Currency::total_issuance(pool_id); + Self::deposit_event(Event::LiquidityMinted { + who: sender, + pool_id, + amounts_provided: (asset_amount, other_asset_amount), + lp_token: pool_id, + lp_token_minted: lp_amount, + total_supply: lp_supply, + }); + + Ok(()) + } + + /// Provide fixed liquidity into the pool of `pool_id`, suitable for StableSwap pools. + /// For Xyk pools, if a single amount is defined, it will swap internally to to match current rate, + /// setting both values results in error. + /// Liquidity tokens that represent this share of the pool will be sent to origin. + #[pallet::call_index(2)] + #[pallet::weight( + T::WeightInfo::mint_liquidity_fixed_amounts_xyk().max( + T::WeightInfo::mint_liquidity_fixed_amounts_sswap() + ))] + pub fn mint_liquidity_fixed_amounts( + origin: OriginFor, + pool_id: PoolIdOf, + amounts: (T::Balance, T::Balance), + min_amount_lp_tokens: T::Balance, + ) -> DispatchResult { + let sender = ensure_signed(origin)?; + + let pool_info = Self::get_pool_info(pool_id)?; + // check assets id, foundation has no veto + ensure!( + !T::NontransferableTokens::contains(&pool_info.pool.0) && + !T::NontransferableTokens::contains(&pool_info.pool.1), + Error::::NontransferableToken + ); + Self::check_assets_allowed(pool_info.pool)?; + + let lp_amount = match pool_info.kind { + PoolKind::Xyk => { + ensure!( + amounts.0 == Zero::zero() || amounts.1 == Zero::zero(), + Error::::FunctionNotAvailableForThisPoolKind + ); + + let (id, amount) = if amounts.1 == Zero::zero() { + (pool_info.pool.0, amounts.0) + } else { + (pool_info.pool.1, amounts.1) + }; + + let (_, lp_amount) = T::Xyk::provide_liquidity_with_conversion( + sender.clone(), + pool_info.pool.0, + pool_info.pool.1, + id, + amount, + true, + )?; + ensure!(lp_amount > min_amount_lp_tokens, Error::::InsufficientOutputAmount); + lp_amount + }, + PoolKind::StableSwap => { + let amount = T::StableSwap::add_liquidity( + &sender, + pool_id, + amounts, + min_amount_lp_tokens, + )?; + if T::Rewards::native_rewards_enabled(pool_info.pool_id) { + T::Rewards::activate_liquidity( + sender.clone(), + pool_id, + amount, + Some(ActivateKind::AvailableBalance), + )?; + } + amount + }, + }; + + let lp_supply = T::Currency::total_issuance(pool_id); + Self::deposit_event(Event::LiquidityMinted { + who: sender, + pool_id, + amounts_provided: amounts, + lp_token: pool_id, + lp_token_minted: lp_amount, + total_supply: lp_supply, + }); + + Ok(()) + } + + /// Provides liquidity from vested native asset. Tokens are added to pool and + /// minted LP tokens are then vested instead. + /// Only pools paired with native asset are allowed. + #[pallet::call_index(3)] + #[pallet::weight( + T::WeightInfo::mint_liquidity_using_vesting_native_tokens_by_vesting_index_xyk().max( + T::WeightInfo::mint_liquidity_using_vesting_native_tokens_by_vesting_index_sswap() + ))] + pub fn mint_liquidity_using_vesting_native_tokens_by_vesting_index( + origin: OriginFor, + pool_id: PoolIdOf, + native_asset_vesting_index: u32, + vesting_native_asset_unlock_some_amount_or_all: Option, + max_other_asset_amount: T::Balance, + ) -> DispatchResult { + let sender = ensure_signed(origin)?; + + let pool_info = Self::get_pool_info(pool_id)?; + // check assets id, foundation has no veto + ensure!( + !T::NontransferableTokens::contains(&pool_info.pool.0) && + !T::NontransferableTokens::contains(&pool_info.pool.1), + Error::::NontransferableToken + ); + Self::check_assets_allowed(pool_info.pool)?; + + let native_id = T::NativeCurrencyId::get(); + ensure!( + native_id == pool_info.pool.0 || native_id == pool_info.pool.1, + Error::::NotPairedWithNativeAsset + ); + + ensure!(T::Rewards::native_rewards_enabled(pool_id), Error::::NotAPromotedPool); + + let (unlocked_amount, vesting_starting_block, vesting_ending_block_as_balance) = + T::Vesting::unlock_tokens_by_vesting_index( + &sender, + native_id, + native_asset_vesting_index, + vesting_native_asset_unlock_some_amount_or_all, + )?; + + let (lp_amount, other_asset_amount) = Self::do_mint_liquidity( + &sender, + pool_info, + native_id, + unlocked_amount, + max_other_asset_amount, + false, + )?; + + T::Vesting::lock_tokens( + &sender, + pool_id, + lp_amount, + Some(vesting_starting_block), + vesting_ending_block_as_balance, + )?; + + let lp_supply = T::Currency::total_issuance(pool_id); + Self::deposit_event(Event::LiquidityMinted { + who: sender, + pool_id, + amounts_provided: (unlocked_amount, other_asset_amount), + lp_token: pool_id, + lp_token_minted: lp_amount, + total_supply: lp_supply, + }); + + Ok(()) + } + + #[pallet::call_index(4)] + #[pallet::weight( + T::WeightInfo::mint_liquidity_using_vesting_native_tokens_xyk().max( + T::WeightInfo::mint_liquidity_using_vesting_native_tokens_sswap() + ))] + pub fn mint_liquidity_using_vesting_native_tokens( + origin: OriginFor, + pool_id: PoolIdOf, + native_asset_vesting_amount: T::Balance, + max_other_asset_amount: T::Balance, + ) -> DispatchResult { + let sender = ensure_signed(origin)?; + + let pool_info = Self::get_pool_info(pool_id)?; + // check assets id, foundation has no veto + ensure!( + !T::NontransferableTokens::contains(&pool_info.pool.0) && + !T::NontransferableTokens::contains(&pool_info.pool.1), + Error::::NontransferableToken + ); + Self::check_assets_allowed(pool_info.pool)?; + + let native_id = T::NativeCurrencyId::get(); + ensure!( + native_id == pool_info.pool.0 || native_id == pool_info.pool.1, + Error::::NotPairedWithNativeAsset + ); + + ensure!(T::Rewards::native_rewards_enabled(pool_id), Error::::NotAPromotedPool); + + let (vesting_starting_block, vesting_ending_block_as_balance) = + T::Vesting::unlock_tokens(&sender, native_id, native_asset_vesting_amount)?; + + let (lp_amount, other_asset_amount) = Self::do_mint_liquidity( + &sender, + pool_info, + native_id, + native_asset_vesting_amount, + max_other_asset_amount, + false, + )?; + + T::Vesting::lock_tokens( + &sender, + pool_id, + lp_amount, + Some(vesting_starting_block), + vesting_ending_block_as_balance, + )?; + + let lp_supply = T::Currency::total_issuance(pool_id); + Self::deposit_event(Event::LiquidityMinted { + who: sender, + pool_id, + amounts_provided: (native_asset_vesting_amount, other_asset_amount), + lp_token: pool_id, + lp_token_minted: lp_amount, + total_supply: lp_supply, + }); + + Ok(()) + } + + /// Allows you to remove liquidity by providing the `lp_burn_amount` tokens that will be + /// burned in the process. The usage of `min_first_asset_amount`/`min_second_asset_amount` + /// controls the min amount of returned tokens. + #[pallet::call_index(5)] + #[pallet::weight( + T::WeightInfo::burn_liquidity_xyk().max( + T::WeightInfo::burn_liquidity_sswap() + ))] + pub fn burn_liquidity( + origin: OriginFor, + pool_id: PoolIdOf, + liquidity_burn_amount: T::Balance, + min_first_asset_amount: T::Balance, + min_second_asset_amount: T::Balance, + ) -> DispatchResult { + let sender = ensure_signed(origin)?; + + let pool_info = Self::get_pool_info(pool_id)?; + // check assets id, or the foundation has a veto + ensure!( + (!T::NontransferableTokens::contains(&pool_info.pool.0) && + !T::NontransferableTokens::contains(&pool_info.pool.1)) || + T::FoundationAccountsProvider::get().contains(&sender), + Error::::NontransferableToken + ); + Self::check_assets_allowed(pool_info.pool)?; + + let amounts = match pool_info.kind { + PoolKind::Xyk => { + let amounts = T::Xyk::burn_liquidity( + sender.clone(), + pool_info.pool.0, + pool_info.pool.1, + liquidity_burn_amount, + )?; + ensure!( + amounts.0 >= min_first_asset_amount, + Error::::InsufficientOutputAmount + ); + ensure!( + amounts.1 >= min_second_asset_amount, + Error::::InsufficientOutputAmount + ); + amounts + }, + PoolKind::StableSwap => { + // deactivate liquidity if low balance + let balance = T::Currency::available_balance(pool_id, &sender); + let deactivate = liquidity_burn_amount.saturating_sub(balance); + // noop on zero amount + T::Rewards::deactivate_liquidity(sender.clone(), pool_id, deactivate)?; + + let amounts = T::StableSwap::remove_liquidity( + &sender, + pool_id, + liquidity_burn_amount, + (min_first_asset_amount, min_second_asset_amount), + )?; + amounts + }, + }; + + let lp_supply = T::Currency::total_issuance(pool_id); + Self::deposit_event(Event::LiquidityBurned { + who: sender, + pool_id, + amounts, + burned_amount: liquidity_burn_amount, + total_supply: lp_supply, + }); + + Ok(()) + } + + /// Executes a multiswap asset in a series of swap asset atomic swaps. + /// + /// Multiswaps must fee lock instead of paying transaction fees. + /// For a single atomic swap, both `asset_amount_in` and `min_amount_out` are considered to allow free execution without locks. + /// + /// # Args: + /// - `swap_token_list` - This list of tokens is the route of the atomic swaps, starting with the asset sold and ends with the asset finally bought + /// - `asset_id_in`: The id of the asset sold + /// - `asset_amount_in`: The amount of the asset sold + /// - `asset_id_out`: The id of the asset received + /// - `min_amount_out` - The minimum amount of requested asset that must be bought in order to not fail on slippage, use RPC calls to calc expected value + // This call is part of the fee lock mechanism, which allows free execution on success + // in case of an error & no native asset to cover fees, a fixed % is subtracted from input swap asset to avoid DOS attacks + // `OnChargeTransaction` impl should check whether the sender has funds to cover such fee + // or consider transaction invalid + #[pallet::call_index(6)] + #[pallet::weight( + T::WeightInfo::multiswap_asset_xyk(swap_pool_list.len() as u32).max( + T::WeightInfo::multiswap_asset_sswap(swap_pool_list.len() as u32) + ))] + pub fn multiswap_asset( + origin: OriginFor, + swap_pool_list: Vec>, + asset_id_in: T::CurrencyId, + asset_amount_in: T::Balance, + asset_id_out: T::CurrencyId, + min_amount_out: T::Balance, + ) -> DispatchResultWithPostInfo { + let sender = ensure_signed(origin)?; + + // ensure maintenance mode + ensure!( + !T::MaintenanceStatusProvider::is_maintenance(), + Error::::TradingBlockedByMaintenanceMode + ); + + let (pools, path) = Self::get_valid_path(&swap_pool_list, asset_id_in, asset_id_out)?; + + let swaps = + Self::do_swaps(&sender, pools, path.clone(), asset_amount_in, min_amount_out)?; + + Self::deposit_event(Event::AssetsSwapped { who: sender.clone(), swaps }); + + // total swaps inc + + Ok(Pays::No.into()) + } + + /// Executes a multiswap asset in a series of swap asset atomic swaps. + /// The precise output amount is provided instead. + /// + /// Multiswaps must fee lock instead of paying transaction fees. + /// For a single atomic swap, both `asset_amount_out` and `max_amount_in` are considered to allow free execution without locks. + /// + /// # Args: + /// - `swap_token_list` - This list of tokens is the route of the atomic swaps, starting with the asset sold and ends with the asset finally bought + /// - `asset_id_out`: The id of the asset received + /// - `asset_amount_out`: The amount of the asset received + /// - `asset_id_in`: The id of the asset sold + /// - `max_amount_in` - The maximum amount of sold asset in order to not fail on slippage, use RPC calls to calc expected value + // This call is part of the fee lock mechanism, which allows free execution on success + // in case of an error & no native asset to cover fees, a fixed % is subtracted from input swap asset to avoid DOS attacks + // `OnChargeTransaction` impl should check whether the sender has funds to cover such fee + // or consider transaction invalid + #[pallet::call_index(7)] + #[pallet::weight( + T::WeightInfo::multiswap_asset_buy_xyk(swap_pool_list.len() as u32).max( + T::WeightInfo::multiswap_asset_buy_sswap(swap_pool_list.len() as u32) + ))] + pub fn multiswap_asset_buy( + origin: OriginFor, + swap_pool_list: Vec>, + asset_id_out: T::CurrencyId, + asset_amount_out: T::Balance, + asset_id_in: T::CurrencyId, + max_amount_in: T::Balance, + ) -> DispatchResultWithPostInfo { + let sender = ensure_signed(origin)?; + + // ensure maintenance mode + ensure!( + !T::MaintenanceStatusProvider::is_maintenance(), + Error::::TradingBlockedByMaintenanceMode + ); + + let (pools, path) = Self::get_valid_path(&swap_pool_list, asset_id_in, asset_id_out)?; + // calc input amount + let mut id = asset_id_out; + let mut amount_in = asset_amount_out; + for (pool, swap) in pools.iter().rev().zip(path.iter().rev()) { + amount_in = Self::calculate_buy_price(pool.pool_id, id, amount_in) + .ok_or(Error::::ExcesiveInputAmount)?; + id = if id == swap.0 { swap.1 } else { swap.0 }; + } + + ensure!(amount_in <= max_amount_in, Error::::ExcesiveInputAmount); + + let swaps = Self::do_swaps(&sender, pools, path.clone(), amount_in, asset_amount_out)?; + + Self::deposit_event(Event::AssetsSwapped { who: sender.clone(), swaps }); + + // total swaps inc + + Ok(Pays::No.into()) + } + } + + impl Pallet { + // impl for runtime apis, rather do the composition here with traits then in runtime with pallets + pub fn calculate_sell_price( + pool_id: T::CurrencyId, + sell_asset_id: T::CurrencyId, + sell_amount: T::Balance, + ) -> Option { + let pool_info = Self::get_pool_info(pool_id).ok()?; + let (_, other) = pool_info.same_and_other(sell_asset_id)?; + match pool_info.kind { + PoolKind::Xyk => T::Xyk::get_dy(pool_id, sell_asset_id, other, sell_amount), + PoolKind::StableSwap => + T::StableSwap::get_dy(pool_id, sell_asset_id, other, sell_amount), + } + } + + pub fn calculate_sell_price_with_impact( + pool_id: T::CurrencyId, + sell_asset_id: T::CurrencyId, + sell_amount: T::Balance, + ) -> Option<(T::Balance, T::Balance)> { + let pool_info = Self::get_pool_info(pool_id).ok()?; + let (_, other) = pool_info.same_and_other(sell_asset_id)?; + match pool_info.kind { + PoolKind::Xyk => + T::Xyk::get_dy_with_impact(pool_id, sell_asset_id, other, sell_amount), + PoolKind::StableSwap => + T::StableSwap::get_dy_with_impact(pool_id, sell_asset_id, other, sell_amount), + } + } + + pub fn calculate_buy_price( + pool_id: T::CurrencyId, + bought_asset_id: T::CurrencyId, + buy_amount: T::Balance, + ) -> Option { + let pool_info = Self::get_pool_info(pool_id).ok()?; + let (_, other) = pool_info.same_and_other(bought_asset_id)?; + match pool_info.kind { + PoolKind::Xyk => T::Xyk::get_dx(pool_id, other, bought_asset_id, buy_amount), + PoolKind::StableSwap => + T::StableSwap::get_dx(pool_id, other, bought_asset_id, buy_amount), + } + } + + pub fn calculate_buy_price_with_impact( + pool_id: T::CurrencyId, + bought_asset_id: T::CurrencyId, + buy_amount: T::Balance, + ) -> Option<(T::Balance, T::Balance)> { + let pool_info = Self::get_pool_info(pool_id).ok()?; + let (_, other) = pool_info.same_and_other(bought_asset_id)?; + match pool_info.kind { + PoolKind::Xyk => + T::Xyk::get_dx_with_impact(pool_id, other, bought_asset_id, buy_amount), + PoolKind::StableSwap => + T::StableSwap::get_dx_with_impact(pool_id, other, bought_asset_id, buy_amount), + } + } + + pub fn get_burn_amount( + pool_id: T::CurrencyId, + lp_burn_amount: T::Balance, + ) -> Option<(T::Balance, T::Balance)> { + T::Xyk::get_burn_amounts(pool_id, lp_burn_amount) + .or_else(|| T::StableSwap::get_burn_amounts(pool_id, lp_burn_amount)) + } + + pub fn get_pools_for_trading() -> Vec { + let mut assets = vec![]; + if let Some(pools) = T::Xyk::get_non_empty_pools() { + assets.extend(pools.iter()); + } + if let Some(pools) = T::StableSwap::get_non_empty_pools() { + assets.extend(pools.iter()); + } + assets + } + + pub fn get_pools(pool_id: Option) -> Vec<(PoolInfoOf, BalancePairOf)> { + let pool_ids = if pool_id.is_some() { + vec![pool_id.unwrap()] + } else { + Self::get_pools_for_trading() + }; + let mut pools = vec![]; + for id in pool_ids.into_iter() { + if let Some(info) = Self::get_pool_info(id).ok() { + let balances = match info.kind { + PoolKind::Xyk => T::Xyk::get_pool_reserves(info.pool_id), + PoolKind::StableSwap => T::StableSwap::get_pool_reserves(info.pool_id), + }; + pools.push((info, balances.unwrap_or_default())) + } + } + pools + } + + pub fn calculate_expected_amount_for_minting( + pool_id: PoolIdOf, + asset_id: T::CurrencyId, + amount: T::Balance, + ) -> Option { + let pool_info = Self::get_pool_info(pool_id).ok()?; + match pool_info.kind { + PoolKind::Xyk => T::Xyk::get_expected_amount_for_mint(pool_id, asset_id, amount), + PoolKind::StableSwap => + T::StableSwap::get_expected_amount_for_mint(pool_id, asset_id, amount), + } + } + + pub fn calculate_expected_lp_minted( + pool_id: PoolIdOf, + amounts: BalancePairOf, + ) -> Option { + let pool_info = Self::get_pool_info(pool_id).ok()?; + match pool_info.kind { + PoolKind::Xyk => T::Xyk::get_mint_amount(pool_id, amounts), + PoolKind::StableSwap => T::StableSwap::get_mint_amount(pool_id, amounts), + } + } + + pub fn get_pool_info(pool_id: PoolIdOf) -> Result, Error> { + if let Some(pool) = T::Xyk::get_pool_info(pool_id) { + return Ok(PoolInfo { pool_id, kind: PoolKind::Xyk, pool }) + } + if let Some(pool) = T::StableSwap::get_pool_info(pool_id) { + return Ok(PoolInfo { pool_id, kind: PoolKind::StableSwap, pool }) + } + + return Err(Error::::NoSuchPool); + } + + fn check_assets_allowed(assets: AssetPairOf) -> Result<(), Error> { + ensure!( + !T::DisabledTokens::contains(&assets.0) && !T::DisabledTokens::contains(&assets.1), + Error::::FunctionNotAvailableForThisToken + ); + Ok(()) + } + + fn get_valid_path( + swap_pool_list: &Vec>, + asset_in: T::CurrencyId, + asset_out: T::CurrencyId, + ) -> Result<(Vec>, Vec>), Error> { + // at least one swap + ensure!(swap_pool_list.len() > 0, Error::::NoSuchPool); + + // check pools repetition + let mut dedup = swap_pool_list.clone(); + dedup.sort(); + dedup.dedup(); + ensure!(dedup.len() == swap_pool_list.len(), Error::::MultiSwapSamePool); + + let mut path: Vec> = vec![]; + let mut pools: Vec> = vec![]; + for &pool_id in swap_pool_list.iter() { + let pool_info = Self::get_pool_info(pool_id)?; + pools.push(pool_info.clone()); + // function not available for tokens + Self::check_assets_allowed(pool_info.pool)?; + + // check pools' asset connection + // first is asset_id_in, last is asset_id_out + let prev_asset_id = if let Some(&last) = path.last() { last.1 } else { asset_in }; + + let pool = pool_info + .same_and_other(prev_asset_id) + .ok_or(Error::::MultiSwapPathInvalid)?; + path.push(pool); + } + + ensure!( + path.last().is_some_and(|&l| l.1 == asset_out), + Error::::MultiSwapPathInvalid + ); + + Ok((pools, path)) + } + + fn do_mint_liquidity( + sender: &T::AccountId, + pool_info: PoolInfoOf, + asset_id: T::CurrencyId, + amount: T::Balance, + max_amount: T::Balance, + activate: bool, + ) -> Result<(T::Balance, T::Balance), DispatchError> { + let (asset_with_amount, asset_other) = + pool_info.same_and_other(asset_id).ok_or(Error::::NoSuchPool)?; + + let amounts = match pool_info.kind { + PoolKind::Xyk => { + let (_, lp_amount, second_asset_withdrawn) = T::Xyk::mint_liquidity( + sender.clone(), + asset_with_amount, + asset_other, + amount, + max_amount, + activate, + )?; + (lp_amount, second_asset_withdrawn) + }, + PoolKind::StableSwap => { + let expected = T::StableSwap::get_expected_amount_for_mint( + pool_info.pool_id, + asset_id, + amount, + ) + .unwrap_or_default(); + + ensure!(expected <= max_amount, Error::::ExcesiveInputAmount); + + let amounts = if asset_id == pool_info.pool.0 { + (amount, expected) + } else { + (expected, amount) + }; + + let lp_amount = T::StableSwap::add_liquidity( + &sender, + pool_info.pool_id, + amounts, + Zero::zero(), + )?; + if activate && T::Rewards::native_rewards_enabled(pool_info.pool_id) { + T::Rewards::activate_liquidity( + sender.clone(), + pool_info.pool_id, + lp_amount, + Some(ActivateKind::AvailableBalance), + )?; + } + (lp_amount, expected) + }, + }; + + Ok(amounts) + } + + fn do_swaps( + sender: &T::AccountId, + pools: Vec>, + path: Vec>, + amount_in: T::Balance, + min_amount_out: T::Balance, + ) -> Result>, DispatchError> { + let mut swaps: Vec> = vec![]; + let mut amount_out = amount_in; + for (pool, swap) in pools.iter().zip(path.into_iter()) { + // check input asset id, or the foundation has a veto + ensure!( + !T::NontransferableTokens::contains(&swap.0) || + T::ArbitrageBot::contains(sender), + Error::::NontransferableToken + ); + + let amount_in = amount_out; + amount_out = match pool.kind { + PoolKind::StableSwap => { + let SwapResult { amount_out, treasury_fee, bnb_fee, .. } = + T::StableSwap::swap( + sender, + pool.pool_id, + swap.0, + swap.1, + amount_in, + Zero::zero(), + )?; + + T::Xyk::settle_treasury_and_burn(swap.1, bnb_fee, treasury_fee)?; + + amount_out + }, + PoolKind::Xyk => T::Xyk::sell_asset( + sender.clone(), + swap.0, + swap.1, + amount_in, + Zero::zero(), + true, + )?, + }; + + swaps.push(AtomicSwap { + pool_id: pool.pool_id, + kind: pool.kind.clone(), + asset_in: swap.0, + asset_out: swap.1, + amount_in, + amount_out, + }); + } + + ensure!(amount_out >= min_amount_out, Error::::InsufficientOutputAmount); + + Ok(swaps) + } + } +} + +// for now ignore the stable swap +// native token is not a stablecoin +// so we don't expect a direct stable pool of (native, token_x) +// stables can be used for multipath evals eg. token_x -> usdc -> usdt -> native +impl Valuate for Pallet { + type CurrencyId = T::CurrencyId; + type Balance = T::Balance; + + // a pool's pair has to be connected to base + fn check_can_valuate( + base_id: Self::CurrencyId, + pool_id: Self::CurrencyId, + ) -> Result<(), DispatchError> { + let pair = Self::get_pool_info(pool_id)?.pool; + if pair.0 == base_id || pair.1 == base_id { + return Ok(()); + } + Err(Error::::NoSuchPool.into()) + } + + fn check_pool_exist(pool_id: Self::CurrencyId) -> Result<(), DispatchError> { + Self::get_pool_info(pool_id)?; + Ok(()) + } + + fn find_paired_pool( + base_id: Self::CurrencyId, + asset_id: Self::CurrencyId, + ) -> Result, DispatchError> { + let pool_id = T::Xyk::get_liquidity_asset(base_id, asset_id)?; + let maybe_pool = Self::get_pools(Some(pool_id)); + let (info, reserves) = maybe_pool.first().ok_or(Error::::NoSuchPool)?; + Ok((pool_id, info.pool, *reserves)) + } + + fn find_valuation( + _: Self::CurrencyId, + asset_id: Self::CurrencyId, + amount: Self::Balance, + ) -> Result { + Ok(T::Xyk::valuate_non_liquidity_token(asset_id, amount)) + } + + fn get_reserve_and_lp_supply( + base_id: Self::CurrencyId, + pool_id: Self::CurrencyId, + ) -> Option<(Self::Balance, Self::Balance)> { + let maybe_pool = Self::get_pools(Some(pool_id)); + let (info, reserves) = maybe_pool.first()?; + let reserve = match info.pool { + (id, _) if id == base_id => reserves.0, + (_, id) if id == base_id => reserves.1, + _ => Zero::zero(), + }; + let issuance = T::Currency::total_issuance(pool_id); + if reserve.is_zero() || issuance.is_zero() { + return None; + } + Some((reserve, issuance)) + } + + fn get_valuation_for_paired( + _: Self::CurrencyId, + pool_id: Self::CurrencyId, + amount: Self::Balance, + ) -> Self::Balance { + T::Xyk::valuate_liquidity_token(pool_id, amount) + } +} + +#[derive(Clone, Eq, PartialEq, Encode, Decode, Default, TypeInfo)] +#[cfg_attr(feature = "std", derive(Debug, Serialize, Deserialize))] +#[cfg_attr(feature = "std", serde(rename_all = "camelCase"))] +pub struct RpcAssetMetadata { + pub token_id: TokenId, + pub decimals: u32, + pub name: Vec, + pub symbol: Vec, +} + +#[derive(Clone, Eq, PartialEq, Encode, Decode, TypeInfo)] +#[cfg_attr(feature = "std", derive(Debug, Serialize, Deserialize))] +#[cfg_attr(feature = "std", serde(rename_all = "camelCase"))] +pub struct RpcPoolInfo { + pub pool_id: TokenId, + pub kind: PoolKind, + pub lp_token_id: TokenId, + pub assets: Vec, + pub reserves: Vec, +} + +sp_api::decl_runtime_apis! { + /// This runtime api allows people to query the size of the liquidity pools + /// and quote prices for swaps. + pub trait MarketRuntimeApi + where + Balance: Codec + MaybeDisplay + MaybeFromStr, + AssetId: Codec + MaybeDisplay + MaybeFromStr, + { + fn calculate_sell_price( + pool_id: AssetId, + sell_asset_id: AssetId, + sell_amount: Balance + ) -> Option; + + fn calculate_sell_price_with_impact( + pool_id: AssetId, + sell_asset_id: AssetId, + sell_amount: Balance + ) -> Option<(Balance, Balance)>; + + fn calculate_buy_price( + pool_id: AssetId, + buy_asset_id: AssetId, + buy_amount: Balance + ) -> Option; + + fn calculate_buy_price_with_impact( + pool_id: AssetId, + buy_asset_id: AssetId, + buy_amount: Balance + ) -> Option<(Balance, Balance)>; + + fn get_burn_amount( + pool_id: AssetId, + lp_burn_amount: Balance, + ) -> Option<(Balance, Balance)>; + + fn calculate_expected_amount_for_minting( + pool_id: AssetId, + asset_id: AssetId, + amount: Balance, + ) -> Option; + + fn calculate_expected_lp_minted( + pool_id: AssetId, + amounts: (Balance, Balance), + ) -> Option; + +// fn get_max_instant_burn_amount( +// user: AccountId, +// liquidity_asset_id: AssetId, +// ) -> Balance; + +// fn get_max_instant_unreserve_amount( +// user: AccountId, +// liquidity_asset_id: AssetId, +// ) -> Balance; + +// fn calculate_rewards_amount( +// user: AccountId, +// liquidity_asset_id: AssetId, +// ) -> Balance; + +// fn calculate_balanced_sell_amount( +// total_amount: Balance, +// reserve_amount: Balance, +// ) -> Balance; + +// fn is_buy_asset_lock_free( +// path: sp_std::vec::Vec, +// input_amount: Balance, +// ) -> Option; + +// fn is_sell_asset_lock_free( +// path: sp_std::vec::Vec, +// input_amount: Balance, +// ) -> Option; + + fn get_tradeable_tokens() -> Vec>; + + fn get_pools_for_trading() -> Vec; + + fn get_pools(pool_id: Option) -> Vec>; + +// fn get_total_number_of_swaps() -> u128; + } +} diff --git a/gasp-node/pallets/market/src/mock.rs b/gasp-node/pallets/market/src/mock.rs new file mode 100644 index 000000000..d0b0649c2 --- /dev/null +++ b/gasp-node/pallets/market/src/mock.rs @@ -0,0 +1,494 @@ +use super::*; +use crate as market; + +use frame_support::{ + construct_runtime, derive_impl, parameter_types, + traits::{ + tokens::currency::MultiTokenCurrency, ConstU128, ConstU32, Contains, ExistenceRequirement, + GetDefault, Nothing, WithdrawReasons, + }, + PalletId, +}; +use frame_system as system; +use mangata_support::traits::{ActivationReservesProviderTrait, ComputeIssuance}; +use mangata_types::assets::L1Asset; +use sp_runtime::{traits::AccountIdConversion, BuildStorage}; +use std::convert::TryFrom; + +pub use orml_tokens::{MultiTokenCurrencyAdapter, MultiTokenCurrencyExtended}; +use orml_traits::{asset_registry::AssetMetadata, parameter_type_with_key}; + +use pallet_xyk::AssetMetadataMutationTrait; + +pub(crate) type AccountId = u128; +pub(crate) type Amount = i128; +pub(crate) type Balance = u128; +pub(crate) type TokenId = u32; + +type Block = frame_system::mocking::MockBlock; + +construct_runtime!( + pub enum Test { + System: frame_system, + Tokens: orml_tokens, + Vesting: pallet_vesting_mangata, + StableSwap: pallet_stable_swap, + Xyk: pallet_xyk, + Market: market, + } +); + +#[derive_impl(frame_system::config_preludes::TestDefaultConfig as frame_system::DefaultConfig)] +impl frame_system::Config for Test { + type AccountId = AccountId; + type Block = Block; + type Lookup = sp_runtime::traits::IdentityLookup; +} + +parameter_type_with_key! { + pub ExistentialDeposits: |currency_id: TokenId| -> Balance { + match currency_id { + _ => 0, + } + }; +} + +pub struct DustRemovalWhitelist; +impl Contains for DustRemovalWhitelist { + fn contains(a: &AccountId) -> bool { + *a == TreasuryAccount::get() + } +} + +parameter_types! { + pub const TreasuryPalletId: PalletId = PalletId(*b"py/trsry"); + pub const BnbTreasurySubAccDerive: [u8; 4] = *b"bnbt"; + pub TreasuryAccount: AccountId = TreasuryPalletId::get().into_account_truncating(); + pub const MaxLocks: u32 = 50; + pub const NativeCurrencyId: u32 = 0_u32; +} + +impl orml_tokens::Config for Test { + type RuntimeEvent = RuntimeEvent; + type Balance = Balance; + type Amount = Amount; + type CurrencyId = TokenId; + type WeightInfo = (); + type ExistentialDeposits = ExistentialDeposits; + type MaxLocks = MaxLocks; + type DustRemovalWhitelist = DustRemovalWhitelist; + type MaxReserves = (); + type ReserveIdentifier = [u8; 8]; + type CurrencyHooks = (); + type NontransferableTokens = Nothing; + type NontransferableTokensAllowList = Nothing; +} + +parameter_types! { + pub const MinVestedTransfer: Balance = 0; + pub UnvestedFundsAllowedWithdrawReasons: WithdrawReasons = + WithdrawReasons::except(WithdrawReasons::TRANSFER | WithdrawReasons::RESERVE); +} + +impl pallet_vesting_mangata::Config for Test { + type RuntimeEvent = RuntimeEvent; + type Tokens = MultiTokenCurrencyAdapter; + type BlockNumberToBalance = sp_runtime::traits::ConvertInto; + type MinVestedTransfer = MinVestedTransfer; + type WeightInfo = pallet_vesting_mangata::weights::SubstrateWeight; + type UnvestedFundsAllowedWithdrawReasons = UnvestedFundsAllowedWithdrawReasons; + type BlockNumberProvider = System; + // `VestingInfo` encode length is 36bytes. 28 schedules gets encoded as 1009 bytes, which is the + // highest number of schedules that encodes less than 2^10. + const MAX_VESTING_SCHEDULES: u32 = 28; +} + +impl pallet_stable_swap::Config for Test { + type RuntimeEvent = RuntimeEvent; + type Currency = MultiTokenCurrencyAdapter; + type Balance = Balance; + type HigherPrecisionBalance = sp_core::U256; + type CurrencyId = TokenId; + type TreasuryPalletId = TreasuryPalletId; + type BnbTreasurySubAccDerive = BnbTreasurySubAccDerive; + type MarketTotalFee = ConstU128<30_000_000>; + type MarketTreasuryFeePart = ConstU128<3_333_333_334>; + type MarketBnBFeePart = ConstU128<5_000_000_000>; + type MaxApmCoeff = ConstU128<1_000_000>; + type DefaultApmCoeff = ConstU128<1_000>; + type MaxAssetsInPool = ConstU32<8>; + type WeightInfo = (); +} + +#[cfg(not(feature = "runtime-benchmarks"))] +mod mocks { + use super::*; + + mockall::mock! { + pub MaintenanceStatusProviderApi {} + impl GetMaintenanceStatusTrait for MaintenanceStatusProviderApi { + fn is_maintenance() -> bool; + fn is_upgradable() -> bool; + } + } + + mockall::mock! { + pub ActivationReservesApi {} + impl ActivationReservesProviderTrait for ActivationReservesApi { + fn get_max_instant_unreserve_amount(token_id: TokenId, account_id: &AccountId) -> Balance; + + fn can_activate( + token_id: TokenId, + account_id: &AccountId, + amount: Balance, + use_balance_from: Option, + ) -> bool; + + fn activate( + token_id: TokenId, + account_id: &AccountId, + amount: Balance, + use_balance_from: Option, + ) -> DispatchResult; + + fn deactivate(token_id: TokenId, account_id: &AccountId, amount: Balance) -> Balance; + } + } + + mockall::mock! { + pub RewardsApi {} + + impl ProofOfStakeRewardsApi for RewardsApi { + + fn enable(liquidity_token_id: TokenId, weight: u8); + + fn disable(liquidity_token_id: TokenId); + + fn is_enabled( + liquidity_token_id: TokenId, + ) -> bool; + + fn claim_rewards_all( + sender: AccountId, + liquidity_token_id: TokenId, + ) -> Result; + + fn activate_liquidity( + sender: AccountId, + liquidity_token_id: TokenId, + amount: Balance, + use_balance_from: Option, + ) -> DispatchResult; + + fn deactivate_liquidity( + sender: AccountId, + liquidity_token_id: TokenId, + amount: Balance, + ) -> DispatchResult; + + fn calculate_rewards_amount( + user: AccountId, + liquidity_asset_id: TokenId, + ) -> Result; + + fn rewards_period() -> u32; + } + } + + mockall::mock! { + pub AssetRegApi {} + + impl AssetRegistryProviderTrait for AssetRegApi { + fn get_l1_asset_id(l1_asset: L1Asset) -> Option; + fn create_l1_asset(l1_asset: L1Asset) -> Result; + fn get_asset_l1_id(asset_id: TokenId) -> Option; + fn create_pool_asset( + lp_asset: TokenId, + asset_1: TokenId, + asset_2: TokenId, + ) -> DispatchResult; + } + + impl orml_traits::asset_registry::Inspect for AssetRegApi { + type AssetId = TokenId; + type Balance = Balance; + type CustomMetadata = (); + type StringLimit = ConstU32<10>; + + fn metadata(asset_id: &TokenId) -> Option>>; + } + + impl AssetMetadataMutationTrait for AssetRegApi { + fn set_asset_info( + asset: TokenId, + name: Vec, + symbol: Vec, + decimals: u32, + ) -> DispatchResult; + } + } + + mockall::mock! { + pub Issuance {} + + impl ComputeIssuance for Issuance { + fn initialize() {} + fn compute_issuance(n: u32); + } + } +} + +#[cfg(feature = "runtime-benchmarks")] +mod mocks { + use super::*; + + pub struct MockMaintenanceStatusProviderApi; + + impl GetMaintenanceStatusTrait for MockMaintenanceStatusProviderApi { + fn is_maintenance() -> bool { + false + } + + fn is_upgradable() -> bool { + unimplemented!() + } + } + pub struct MockActivationReservesApi; + + impl ActivationReservesProviderTrait for MockActivationReservesApi { + fn get_max_instant_unreserve_amount(_: TokenId, _: &AccountId) -> Balance { + Zero::zero() + } + + fn can_activate(_: TokenId, _: &AccountId, _: Balance, _: Option) -> bool { + unimplemented!() + } + + fn activate( + _: TokenId, + _: &AccountId, + _: Balance, + _: Option, + ) -> DispatchResult { + unimplemented!() + } + + fn deactivate(_: TokenId, _: &AccountId, _: Balance) -> Balance { + unimplemented!() + } + } + + pub struct MockRewardsApi; + + impl ProofOfStakeRewardsApi for MockRewardsApi { + fn enable(_: TokenId, _: u8) {} + + fn disable(_: TokenId) { + unimplemented!() + } + + fn is_enabled(_: TokenId) -> bool { + true + } + + fn claim_rewards_all(_: AccountId, _: TokenId) -> Result { + unimplemented!() + } + + fn activate_liquidity( + _: AccountId, + _: TokenId, + _: Balance, + _: Option, + ) -> DispatchResult { + Ok(()).into() + } + + fn deactivate_liquidity(_: AccountId, _: TokenId, _: Balance) -> DispatchResult { + Ok(()).into() + } + + fn calculate_rewards_amount(_: AccountId, _: TokenId) -> Result { + unimplemented!() + } + + fn rewards_period() -> u32 { + 10 + } + } + + pub struct MockAssetRegApi; + + impl AssetRegistryProviderTrait for MockAssetRegApi { + fn get_l1_asset_id(_: L1Asset) -> Option { + unimplemented!() + } + fn create_l1_asset(_: L1Asset) -> Result { + unimplemented!() + } + fn get_asset_l1_id(_: TokenId) -> Option { + unimplemented!() + } + fn create_pool_asset(_: TokenId, _: TokenId, _: TokenId) -> DispatchResult { + Ok(()).into() + } + } + + impl orml_traits::asset_registry::Inspect for MockAssetRegApi { + type AssetId = TokenId; + type Balance = Balance; + type CustomMetadata = (); + type StringLimit = ConstU32<10>; + + fn metadata(_: &TokenId) -> Option>> { + Some(AssetMetadata { + decimals: 18, + name: BoundedVec::new(), + symbol: BoundedVec::new(), + existential_deposit: Zero::zero(), + additional: (), + }) + } + } + + impl AssetMetadataMutationTrait for MockAssetRegApi { + fn set_asset_info(_: TokenId, _: Vec, _: Vec, _: u32) -> DispatchResult { + Ok(()).into() + } + } + + pub struct MockIssuance; + + impl ComputeIssuance for MockIssuance { + fn initialize() { + unimplemented!() + } + fn compute_issuance(_: u32) {} + } +} + +impl pallet_xyk::XykBenchmarkingConfig for Test {} + +impl pallet_xyk::Config for Test { + type RuntimeEvent = RuntimeEvent; + type MaintenanceStatusProvider = mocks::MockMaintenanceStatusProviderApi; + type ActivationReservesProvider = mocks::MockActivationReservesApi; + type Currency = MultiTokenCurrencyAdapter; + type NativeCurrencyId = NativeCurrencyId; + type TreasuryPalletId = TreasuryPalletId; + type BnbTreasurySubAccDerive = BnbTreasurySubAccDerive; + type PoolFeePercentage = ConstU128<20>; + type TreasuryFeePercentage = ConstU128<5>; + type BuyAndBurnFeePercentage = ConstU128<5>; + type LiquidityMiningRewards = mocks::MockRewardsApi; + type WeightInfo = (); + type VestingProvider = Vesting; + type DisallowedPools = Nothing; + type DisabledTokens = Nothing; + type AssetMetadataMutation = mocks::MockAssetRegApi; + type FeeLockWeight = (); +} + +impl market::Config for Test { + type RuntimeEvent = RuntimeEvent; + type Currency = MultiTokenCurrencyAdapter; + type Balance = Balance; + type CurrencyId = TokenId; + type NativeCurrencyId = NativeCurrencyId; + type Xyk = Xyk; + type StableSwap = StableSwap; + type Rewards = mocks::MockRewardsApi; + type Vesting = Vesting; + type AssetRegistry = mocks::MockAssetRegApi; + type DisabledTokens = Nothing; + type DisallowedPools = Nothing; + type MaintenanceStatusProvider = mocks::MockMaintenanceStatusProviderApi; + type WeightInfo = (); + #[cfg(feature = "runtime-benchmarks")] + type ComputeIssuance = mocks::MockIssuance; + type NontransferableTokens = Nothing; + type FoundationAccountsProvider = GetDefault; + type ArbitrageBot = Nothing; +} + +impl Pallet +where + ::Currency: + MultiTokenCurrencyExtended, +{ + pub fn balance(id: TokenId, who: AccountId) -> Balance { + ::Currency::free_balance(id.into(), &who).into() + } + pub fn total_supply(id: TokenId) -> Balance { + ::Currency::total_issuance(id.into()).into() + } + pub fn transfer( + currency_id: TokenId, + source: AccountId, + dest: AccountId, + value: Balance, + ) -> DispatchResult { + ::Currency::transfer( + currency_id, + &source, + &dest, + value, + ExistenceRequirement::KeepAlive, + ) + } + pub fn create_new_token(who: &AccountId, amount: Balance) -> TokenId { + ::Currency::create(who, amount).expect("Token creation failed") + } + + pub fn mint_token(token_id: TokenId, who: &AccountId, amount: Balance) { + ::Currency::mint(token_id, who, amount).expect("Token minting failed") + } +} + +// This function basically just builds a genesis storage key/value store according to +// our desired mockup. +pub fn new_test_ext() -> sp_io::TestExternalities { + let mut ext: sp_io::TestExternalities = + system::GenesisConfig::::default().build_storage().unwrap().into(); + + ext.execute_with(|| { + System::set_block_number(1); + }); + ext +} + +pub(crate) fn events() -> Vec> { + System::events() + .into_iter() + .map(|r| r.event) + .filter_map(|e| if let RuntimeEvent::Market(inner) = e { Some(inner) } else { None }) + .collect::>() +} + +/// Compares the system events with passed in events +/// Prints highlighted diff iff assert_eq fails +#[macro_export] +macro_rules! assert_eq_events { + ($events:expr) => { + match &$events { + e => similar_asserts::assert_eq!(*e, $crate::mock::events()), + } + }; +} + +/// Panics if an event is not found in the system log of events +#[macro_export] +macro_rules! assert_event_emitted { + ($event:expr) => { + match &$event { + e => { + assert!( + $crate::mock::events().iter().find(|x| *x == e).is_some(), + "Event {:?} was not found in events: \n {:?}", + e, + crate::mock::events() + ); + }, + } + }; +} diff --git a/gasp-node/pallets/market/src/tests.rs b/gasp-node/pallets/market/src/tests.rs new file mode 100644 index 000000000..8b1378917 --- /dev/null +++ b/gasp-node/pallets/market/src/tests.rs @@ -0,0 +1 @@ + diff --git a/gasp-node/pallets/market/src/weights.rs b/gasp-node/pallets/market/src/weights.rs new file mode 100644 index 000000000..8470971fc --- /dev/null +++ b/gasp-node/pallets/market/src/weights.rs @@ -0,0 +1,850 @@ +// This file is part of Mangata. + +// Copyright (C) 2020-2022 Mangata Foundation. +// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 + +// This program 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. + +// This program 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 this program. If not, see . + +//! Autogenerated weights for pallet_market +//! +//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 32.0.0 +//! DATE: 2024-11-25, STEPS: `50`, REPEAT: 20, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! EXECUTION: , WASM-EXECUTION: Compiled, CHAIN: Some("rollup-local"), DB CACHE: 1024 + +// Executed Command: +// target/release/rollup-node +// benchmark +// pallet +// -l=info,runtime::collective=warn,xyk=warn +// --chain +// rollup-local +// --wasm-execution +// compiled +// --pallet +// * +// --extrinsic +// * +// --steps +// 50 +// --repeat +// 20 +// --template +// ./templates/module-weight-template.hbs +// --output +// ./benchmarks/ + +#![cfg_attr(rustfmt, rustfmt_skip)] +#![allow(unused_parens)] +#![allow(unused_imports)] +#![allow(clippy::unnecessary_cast)] + +use frame_support::{traits::Get, weights::{Weight, constants::RocksDbWeight}}; +use sp_std::marker::PhantomData; + +/// Weight functions needed for pallet_market. +pub trait WeightInfo { + fn create_pool_xyk() -> Weight; + fn mint_liquidity_xyk() -> Weight; + fn mint_liquidity_fixed_amounts_xyk() -> Weight; + fn mint_liquidity_using_vesting_native_tokens_by_vesting_index_xyk() -> Weight; + fn mint_liquidity_using_vesting_native_tokens_xyk() -> Weight; + fn burn_liquidity_xyk() -> Weight; + fn multiswap_asset_xyk(y: u32, ) -> Weight; + fn multiswap_asset_buy_xyk(y: u32, ) -> Weight; + fn create_pool_sswap() -> Weight; + fn mint_liquidity_sswap() -> Weight; + fn mint_liquidity_fixed_amounts_sswap() -> Weight; + fn mint_liquidity_using_vesting_native_tokens_by_vesting_index_sswap() -> Weight; + fn mint_liquidity_using_vesting_native_tokens_sswap() -> Weight; + fn burn_liquidity_sswap() -> Weight; + fn multiswap_asset_sswap(y: u32, ) -> Weight; + fn multiswap_asset_buy_sswap(y: u32, ) -> Weight; +} + +/// Weights for pallet_market using the Mangata node and recommended hardware. +pub struct ModuleWeight(PhantomData); +impl WeightInfo for ModuleWeight { + // Storage: `AssetRegistry::Metadata` (r:3 w:1) + // Proof: `AssetRegistry::Metadata` (`max_values`: None, `max_size`: Some(153), added: 2628, mode: `MaxEncodedLen`) + // Storage: `Bootstrap::BootstrapSchedule` (r:1 w:0) + // Proof: `Bootstrap::BootstrapSchedule` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + // Storage: `Xyk::Pools` (r:2 w:1) + // Proof: `Xyk::Pools` (`max_values`: None, `max_size`: Some(56), added: 2531, mode: `MaxEncodedLen`) + // Storage: `Tokens::Accounts` (r:5 w:5) + // Proof: `Tokens::Accounts` (`max_values`: None, `max_size`: Some(96), added: 2571, mode: `MaxEncodedLen`) + // Storage: `System::Account` (r:1 w:1) + // Proof: `System::Account` (`max_values`: None, `max_size`: Some(52), added: 2527, mode: `MaxEncodedLen`) + // Storage: `Tokens::NextCurrencyId` (r:1 w:1) + // Proof: `Tokens::NextCurrencyId` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) + // Storage: `Tokens::TotalIssuance` (r:1 w:1) + // Proof: `Tokens::TotalIssuance` (`max_values`: None, `max_size`: Some(28), added: 2503, mode: `MaxEncodedLen`) + // Storage: `Xyk::LiquidityAssets` (r:0 w:1) + // Proof: `Xyk::LiquidityAssets` (`max_values`: None, `max_size`: Some(29), added: 2504, mode: `MaxEncodedLen`) + // Storage: `Xyk::LiquidityPools` (r:0 w:1) + // Proof: `Xyk::LiquidityPools` (`max_values`: None, `max_size`: Some(29), added: 2504, mode: `MaxEncodedLen`) + fn create_pool_xyk() -> Weight { + (Weight::from_parts(170_010_000, 0)) + .saturating_add(T::DbWeight::get().reads(14 as u64)) + .saturating_add(T::DbWeight::get().writes(12 as u64)) + } + // Storage: `Xyk::LiquidityPools` (r:1 w:0) + // Proof: `Xyk::LiquidityPools` (`max_values`: None, `max_size`: Some(29), added: 2504, mode: `MaxEncodedLen`) + // Storage: `AssetRegistry::Metadata` (r:2 w:0) + // Proof: `AssetRegistry::Metadata` (`max_values`: None, `max_size`: Some(153), added: 2628, mode: `MaxEncodedLen`) + // Storage: `Xyk::LiquidityAssets` (r:1 w:0) + // Proof: `Xyk::LiquidityAssets` (`max_values`: None, `max_size`: Some(29), added: 2504, mode: `MaxEncodedLen`) + // Storage: `Xyk::Pools` (r:1 w:1) + // Proof: `Xyk::Pools` (`max_values`: None, `max_size`: Some(56), added: 2531, mode: `MaxEncodedLen`) + // Storage: `Tokens::TotalIssuance` (r:1 w:1) + // Proof: `Tokens::TotalIssuance` (`max_values`: None, `max_size`: Some(28), added: 2503, mode: `MaxEncodedLen`) + // Storage: `Tokens::Accounts` (r:5 w:5) + // Proof: `Tokens::Accounts` (`max_values`: None, `max_size`: Some(96), added: 2571, mode: `MaxEncodedLen`) + // Storage: `Tokens::NextCurrencyId` (r:1 w:0) + // Proof: `Tokens::NextCurrencyId` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) + // Storage: `ProofOfStake::PromotedPoolRewards` (r:1 w:0) + // Proof: `ProofOfStake::PromotedPoolRewards` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + // Storage: `MultiPurposeLiquidity::ReserveStatus` (r:1 w:1) + // Proof: `MultiPurposeLiquidity::ReserveStatus` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`) + // Storage: `ProofOfStake::RewardsInfo` (r:1 w:1) + // Proof: `ProofOfStake::RewardsInfo` (`max_values`: None, `max_size`: None, mode: `Measured`) + // Storage: `ProofOfStake::TotalActivatedLiquidity` (r:1 w:1) + // Proof: `ProofOfStake::TotalActivatedLiquidity` (`max_values`: None, `max_size`: None, mode: `Measured`) + fn mint_liquidity_xyk() -> Weight { + (Weight::from_parts(216_450_000, 0)) + .saturating_add(T::DbWeight::get().reads(16 as u64)) + .saturating_add(T::DbWeight::get().writes(10 as u64)) + } + // Storage: `Xyk::LiquidityPools` (r:1 w:0) + // Proof: `Xyk::LiquidityPools` (`max_values`: None, `max_size`: Some(29), added: 2504, mode: `MaxEncodedLen`) + // Storage: `AssetRegistry::Metadata` (r:2 w:0) + // Proof: `AssetRegistry::Metadata` (`max_values`: None, `max_size`: Some(153), added: 2628, mode: `MaxEncodedLen`) + // Storage: `Xyk::LiquidityAssets` (r:2 w:0) + // Proof: `Xyk::LiquidityAssets` (`max_values`: None, `max_size`: Some(29), added: 2504, mode: `MaxEncodedLen`) + // Storage: `Tokens::TotalIssuance` (r:1 w:1) + // Proof: `Tokens::TotalIssuance` (`max_values`: None, `max_size`: Some(28), added: 2503, mode: `MaxEncodedLen`) + // Storage: `Xyk::Pools` (r:4 w:1) + // Proof: `Xyk::Pools` (`max_values`: None, `max_size`: Some(56), added: 2531, mode: `MaxEncodedLen`) + // Storage: `Tokens::Accounts` (r:7 w:7) + // Proof: `Tokens::Accounts` (`max_values`: None, `max_size`: Some(96), added: 2571, mode: `MaxEncodedLen`) + // Storage: `Maintenance::MaintenanceStatus` (r:1 w:0) + // Proof: `Maintenance::MaintenanceStatus` (`max_values`: Some(1), `max_size`: Some(2), added: 497, mode: `MaxEncodedLen`) + // Storage: `System::Account` (r:2 w:2) + // Proof: `System::Account` (`max_values`: None, `max_size`: Some(52), added: 2527, mode: `MaxEncodedLen`) + // Storage: `Tokens::NextCurrencyId` (r:1 w:0) + // Proof: `Tokens::NextCurrencyId` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) + // Storage: `ProofOfStake::PromotedPoolRewards` (r:1 w:0) + // Proof: `ProofOfStake::PromotedPoolRewards` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + // Storage: `MultiPurposeLiquidity::ReserveStatus` (r:1 w:1) + // Proof: `MultiPurposeLiquidity::ReserveStatus` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`) + // Storage: `ProofOfStake::RewardsInfo` (r:1 w:1) + // Proof: `ProofOfStake::RewardsInfo` (`max_values`: None, `max_size`: None, mode: `Measured`) + // Storage: `ProofOfStake::TotalActivatedLiquidity` (r:1 w:1) + // Proof: `ProofOfStake::TotalActivatedLiquidity` (`max_values`: None, `max_size`: None, mode: `Measured`) + fn mint_liquidity_fixed_amounts_xyk() -> Weight { + (Weight::from_parts(407_209_000, 0)) + .saturating_add(T::DbWeight::get().reads(25 as u64)) + .saturating_add(T::DbWeight::get().writes(14 as u64)) + } + // Storage: `Xyk::LiquidityPools` (r:1 w:0) + // Proof: `Xyk::LiquidityPools` (`max_values`: None, `max_size`: Some(29), added: 2504, mode: `MaxEncodedLen`) + // Storage: `AssetRegistry::Metadata` (r:2 w:0) + // Proof: `AssetRegistry::Metadata` (`max_values`: None, `max_size`: Some(153), added: 2628, mode: `MaxEncodedLen`) + // Storage: `ProofOfStake::PromotedPoolRewards` (r:1 w:0) + // Proof: `ProofOfStake::PromotedPoolRewards` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + // Storage: `Vesting::Vesting` (r:2 w:2) + // Proof: `Vesting::Vesting` (`max_values`: None, `max_size`: Some(1857), added: 4332, mode: `MaxEncodedLen`) + // Storage: `Tokens::Locks` (r:2 w:2) + // Proof: `Tokens::Locks` (`max_values`: None, `max_size`: Some(1249), added: 3724, mode: `MaxEncodedLen`) + // Storage: `Tokens::Accounts` (r:5 w:5) + // Proof: `Tokens::Accounts` (`max_values`: None, `max_size`: Some(96), added: 2571, mode: `MaxEncodedLen`) + // Storage: `Xyk::LiquidityAssets` (r:1 w:0) + // Proof: `Xyk::LiquidityAssets` (`max_values`: None, `max_size`: Some(29), added: 2504, mode: `MaxEncodedLen`) + // Storage: `Xyk::Pools` (r:1 w:1) + // Proof: `Xyk::Pools` (`max_values`: None, `max_size`: Some(56), added: 2531, mode: `MaxEncodedLen`) + // Storage: `Tokens::TotalIssuance` (r:1 w:1) + // Proof: `Tokens::TotalIssuance` (`max_values`: None, `max_size`: Some(28), added: 2503, mode: `MaxEncodedLen`) + // Storage: `Tokens::NextCurrencyId` (r:1 w:0) + // Proof: `Tokens::NextCurrencyId` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) + fn mint_liquidity_using_vesting_native_tokens_by_vesting_index_xyk() -> Weight { + (Weight::from_parts(237_820_000, 0)) + .saturating_add(T::DbWeight::get().reads(17 as u64)) + .saturating_add(T::DbWeight::get().writes(11 as u64)) + } + // Storage: `Xyk::LiquidityPools` (r:1 w:0) + // Proof: `Xyk::LiquidityPools` (`max_values`: None, `max_size`: Some(29), added: 2504, mode: `MaxEncodedLen`) + // Storage: `AssetRegistry::Metadata` (r:2 w:0) + // Proof: `AssetRegistry::Metadata` (`max_values`: None, `max_size`: Some(153), added: 2628, mode: `MaxEncodedLen`) + // Storage: `ProofOfStake::PromotedPoolRewards` (r:1 w:0) + // Proof: `ProofOfStake::PromotedPoolRewards` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + // Storage: `Vesting::Vesting` (r:2 w:2) + // Proof: `Vesting::Vesting` (`max_values`: None, `max_size`: Some(1857), added: 4332, mode: `MaxEncodedLen`) + // Storage: `Tokens::Locks` (r:2 w:2) + // Proof: `Tokens::Locks` (`max_values`: None, `max_size`: Some(1249), added: 3724, mode: `MaxEncodedLen`) + // Storage: `Tokens::Accounts` (r:5 w:5) + // Proof: `Tokens::Accounts` (`max_values`: None, `max_size`: Some(96), added: 2571, mode: `MaxEncodedLen`) + // Storage: `Xyk::LiquidityAssets` (r:1 w:0) + // Proof: `Xyk::LiquidityAssets` (`max_values`: None, `max_size`: Some(29), added: 2504, mode: `MaxEncodedLen`) + // Storage: `Xyk::Pools` (r:1 w:1) + // Proof: `Xyk::Pools` (`max_values`: None, `max_size`: Some(56), added: 2531, mode: `MaxEncodedLen`) + // Storage: `Tokens::TotalIssuance` (r:1 w:1) + // Proof: `Tokens::TotalIssuance` (`max_values`: None, `max_size`: Some(28), added: 2503, mode: `MaxEncodedLen`) + // Storage: `Tokens::NextCurrencyId` (r:1 w:0) + // Proof: `Tokens::NextCurrencyId` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) + fn mint_liquidity_using_vesting_native_tokens_xyk() -> Weight { + (Weight::from_parts(239_980_000, 0)) + .saturating_add(T::DbWeight::get().reads(17 as u64)) + .saturating_add(T::DbWeight::get().writes(11 as u64)) + } + // Storage: `Xyk::LiquidityPools` (r:1 w:0) + // Proof: `Xyk::LiquidityPools` (`max_values`: None, `max_size`: Some(29), added: 2504, mode: `MaxEncodedLen`) + // Storage: `AssetRegistry::Metadata` (r:2 w:0) + // Proof: `AssetRegistry::Metadata` (`max_values`: None, `max_size`: Some(153), added: 2628, mode: `MaxEncodedLen`) + // Storage: `Xyk::LiquidityAssets` (r:1 w:0) + // Proof: `Xyk::LiquidityAssets` (`max_values`: None, `max_size`: Some(29), added: 2504, mode: `MaxEncodedLen`) + // Storage: `MultiPurposeLiquidity::ReserveStatus` (r:1 w:0) + // Proof: `MultiPurposeLiquidity::ReserveStatus` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`) + // Storage: `Xyk::Pools` (r:1 w:1) + // Proof: `Xyk::Pools` (`max_values`: None, `max_size`: Some(56), added: 2531, mode: `MaxEncodedLen`) + // Storage: `Tokens::Accounts` (r:5 w:5) + // Proof: `Tokens::Accounts` (`max_values`: None, `max_size`: Some(96), added: 2571, mode: `MaxEncodedLen`) + // Storage: `Tokens::TotalIssuance` (r:1 w:1) + // Proof: `Tokens::TotalIssuance` (`max_values`: None, `max_size`: Some(28), added: 2503, mode: `MaxEncodedLen`) + fn burn_liquidity_xyk() -> Weight { + (Weight::from_parts(153_580_000, 0)) + .saturating_add(T::DbWeight::get().reads(12 as u64)) + .saturating_add(T::DbWeight::get().writes(7 as u64)) + } + // Storage: `Maintenance::MaintenanceStatus` (r:1 w:0) + // Proof: `Maintenance::MaintenanceStatus` (`max_values`: Some(1), `max_size`: Some(2), added: 497, mode: `MaxEncodedLen`) + // Storage: `Xyk::LiquidityPools` (r:99 w:0) + // Proof: `Xyk::LiquidityPools` (`max_values`: None, `max_size`: Some(29), added: 2504, mode: `MaxEncodedLen`) + // Storage: `AssetRegistry::Metadata` (r:100 w:0) + // Proof: `AssetRegistry::Metadata` (`max_values`: None, `max_size`: Some(153), added: 2628, mode: `MaxEncodedLen`) + // Storage: `Xyk::LiquidityAssets` (r:99 w:0) + // Proof: `Xyk::LiquidityAssets` (`max_values`: None, `max_size`: Some(29), added: 2504, mode: `MaxEncodedLen`) + // Storage: `Tokens::TotalIssuance` (r:100 w:1) + // Proof: `Tokens::TotalIssuance` (`max_values`: None, `max_size`: Some(28), added: 2503, mode: `MaxEncodedLen`) + // Storage: `Xyk::Pools` (r:297 w:198) + // Proof: `Xyk::Pools` (`max_values`: None, `max_size`: Some(56), added: 2531, mode: `MaxEncodedLen`) + // Storage: `Tokens::Accounts` (r:400 w:400) + // Proof: `Tokens::Accounts` (`max_values`: None, `max_size`: Some(96), added: 2571, mode: `MaxEncodedLen`) + // Storage: `System::Account` (r:2 w:2) + // Proof: `System::Account` (`max_values`: None, `max_size`: Some(52), added: 2527, mode: `MaxEncodedLen`) + fn multiswap_asset_xyk(y: u32, ) -> Weight { + (Weight::from_parts(309_150_000, 0)) + // Standard Error: 341_216 + .saturating_add((Weight::from_parts(236_593_363, 0)).saturating_mul(y as u64)) + .saturating_add(T::DbWeight::get().reads(20 as u64)) + .saturating_add(T::DbWeight::get().reads((11 as u64).saturating_mul(y as u64))) + .saturating_add(T::DbWeight::get().writes(1 as u64)) + .saturating_add(T::DbWeight::get().writes((6 as u64).saturating_mul(y as u64))) + } + // Storage: `Maintenance::MaintenanceStatus` (r:1 w:0) + // Proof: `Maintenance::MaintenanceStatus` (`max_values`: Some(1), `max_size`: Some(2), added: 497, mode: `MaxEncodedLen`) + // Storage: `Xyk::LiquidityPools` (r:99 w:0) + // Proof: `Xyk::LiquidityPools` (`max_values`: None, `max_size`: Some(29), added: 2504, mode: `MaxEncodedLen`) + // Storage: `AssetRegistry::Metadata` (r:100 w:0) + // Proof: `AssetRegistry::Metadata` (`max_values`: None, `max_size`: Some(153), added: 2628, mode: `MaxEncodedLen`) + // Storage: `Xyk::Pools` (r:297 w:198) + // Proof: `Xyk::Pools` (`max_values`: None, `max_size`: Some(56), added: 2531, mode: `MaxEncodedLen`) + // Storage: `Xyk::LiquidityAssets` (r:99 w:0) + // Proof: `Xyk::LiquidityAssets` (`max_values`: None, `max_size`: Some(29), added: 2504, mode: `MaxEncodedLen`) + // Storage: `Tokens::TotalIssuance` (r:100 w:1) + // Proof: `Tokens::TotalIssuance` (`max_values`: None, `max_size`: Some(28), added: 2503, mode: `MaxEncodedLen`) + // Storage: `Tokens::Accounts` (r:400 w:400) + // Proof: `Tokens::Accounts` (`max_values`: None, `max_size`: Some(96), added: 2571, mode: `MaxEncodedLen`) + // Storage: `System::Account` (r:2 w:2) + // Proof: `System::Account` (`max_values`: None, `max_size`: Some(52), added: 2527, mode: `MaxEncodedLen`) + fn multiswap_asset_buy_xyk(y: u32, ) -> Weight { + (Weight::from_parts(334_430_000, 0)) + // Standard Error: 468_448 + .saturating_add((Weight::from_parts(255_448_728, 0)).saturating_mul(y as u64)) + .saturating_add(T::DbWeight::get().reads(20 as u64)) + .saturating_add(T::DbWeight::get().reads((11 as u64).saturating_mul(y as u64))) + .saturating_add(T::DbWeight::get().writes(1 as u64)) + .saturating_add(T::DbWeight::get().writes((6 as u64).saturating_mul(y as u64))) + } + // Storage: `AssetRegistry::Metadata` (r:3 w:1) + // Proof: `AssetRegistry::Metadata` (`max_values`: None, `max_size`: Some(153), added: 2628, mode: `MaxEncodedLen`) + // Storage: `Bootstrap::BootstrapSchedule` (r:1 w:0) + // Proof: `Bootstrap::BootstrapSchedule` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + // Storage: `Tokens::NextCurrencyId` (r:1 w:1) + // Proof: `Tokens::NextCurrencyId` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) + // Storage: `Tokens::Accounts` (r:5 w:5) + // Proof: `Tokens::Accounts` (`max_values`: None, `max_size`: Some(96), added: 2571, mode: `MaxEncodedLen`) + // Storage: `System::Account` (r:1 w:1) + // Proof: `System::Account` (`max_values`: None, `max_size`: Some(52), added: 2527, mode: `MaxEncodedLen`) + // Storage: `Tokens::TotalIssuance` (r:1 w:1) + // Proof: `Tokens::TotalIssuance` (`max_values`: None, `max_size`: Some(28), added: 2503, mode: `MaxEncodedLen`) + // Storage: `StableSwap::Pools` (r:0 w:1) + // Proof: `StableSwap::Pools` (`max_values`: None, `max_size`: Some(66), added: 2541, mode: `MaxEncodedLen`) + fn create_pool_sswap() -> Weight { + (Weight::from_parts(173_740_000, 0)) + .saturating_add(T::DbWeight::get().reads(12 as u64)) + .saturating_add(T::DbWeight::get().writes(10 as u64)) + } + // Storage: `Xyk::LiquidityPools` (r:1 w:0) + // Proof: `Xyk::LiquidityPools` (`max_values`: None, `max_size`: Some(29), added: 2504, mode: `MaxEncodedLen`) + // Storage: `StableSwap::Pools` (r:1 w:0) + // Proof: `StableSwap::Pools` (`max_values`: None, `max_size`: Some(66), added: 2541, mode: `MaxEncodedLen`) + // Storage: `AssetRegistry::Metadata` (r:2 w:0) + // Proof: `AssetRegistry::Metadata` (`max_values`: None, `max_size`: Some(153), added: 2628, mode: `MaxEncodedLen`) + // Storage: `Tokens::TotalIssuance` (r:1 w:1) + // Proof: `Tokens::TotalIssuance` (`max_values`: None, `max_size`: Some(28), added: 2503, mode: `MaxEncodedLen`) + // Storage: `Tokens::Accounts` (r:5 w:5) + // Proof: `Tokens::Accounts` (`max_values`: None, `max_size`: Some(96), added: 2571, mode: `MaxEncodedLen`) + // Storage: `Tokens::NextCurrencyId` (r:1 w:0) + // Proof: `Tokens::NextCurrencyId` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) + // Storage: `ProofOfStake::PromotedPoolRewards` (r:1 w:0) + // Proof: `ProofOfStake::PromotedPoolRewards` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + // Storage: `MultiPurposeLiquidity::ReserveStatus` (r:1 w:1) + // Proof: `MultiPurposeLiquidity::ReserveStatus` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`) + // Storage: `ProofOfStake::RewardsInfo` (r:1 w:1) + // Proof: `ProofOfStake::RewardsInfo` (`max_values`: None, `max_size`: None, mode: `Measured`) + // Storage: `ProofOfStake::TotalActivatedLiquidity` (r:1 w:1) + // Proof: `ProofOfStake::TotalActivatedLiquidity` (`max_values`: None, `max_size`: None, mode: `Measured`) + fn mint_liquidity_sswap() -> Weight { + (Weight::from_parts(215_460_000, 0)) + .saturating_add(T::DbWeight::get().reads(15 as u64)) + .saturating_add(T::DbWeight::get().writes(9 as u64)) + } + // Storage: `Xyk::LiquidityPools` (r:1 w:0) + // Proof: `Xyk::LiquidityPools` (`max_values`: None, `max_size`: Some(29), added: 2504, mode: `MaxEncodedLen`) + // Storage: `StableSwap::Pools` (r:1 w:0) + // Proof: `StableSwap::Pools` (`max_values`: None, `max_size`: Some(66), added: 2541, mode: `MaxEncodedLen`) + // Storage: `AssetRegistry::Metadata` (r:2 w:0) + // Proof: `AssetRegistry::Metadata` (`max_values`: None, `max_size`: Some(153), added: 2628, mode: `MaxEncodedLen`) + // Storage: `Tokens::TotalIssuance` (r:1 w:1) + // Proof: `Tokens::TotalIssuance` (`max_values`: None, `max_size`: Some(28), added: 2503, mode: `MaxEncodedLen`) + // Storage: `Tokens::Accounts` (r:6 w:6) + // Proof: `Tokens::Accounts` (`max_values`: None, `max_size`: Some(96), added: 2571, mode: `MaxEncodedLen`) + // Storage: `System::Account` (r:2 w:1) + // Proof: `System::Account` (`max_values`: None, `max_size`: Some(52), added: 2527, mode: `MaxEncodedLen`) + // Storage: `Tokens::NextCurrencyId` (r:1 w:0) + // Proof: `Tokens::NextCurrencyId` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) + // Storage: `ProofOfStake::PromotedPoolRewards` (r:1 w:0) + // Proof: `ProofOfStake::PromotedPoolRewards` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + // Storage: `MultiPurposeLiquidity::ReserveStatus` (r:1 w:1) + // Proof: `MultiPurposeLiquidity::ReserveStatus` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`) + // Storage: `ProofOfStake::RewardsInfo` (r:1 w:1) + // Proof: `ProofOfStake::RewardsInfo` (`max_values`: None, `max_size`: None, mode: `Measured`) + // Storage: `ProofOfStake::TotalActivatedLiquidity` (r:1 w:1) + // Proof: `ProofOfStake::TotalActivatedLiquidity` (`max_values`: None, `max_size`: None, mode: `Measured`) + fn mint_liquidity_fixed_amounts_sswap() -> Weight { + (Weight::from_parts(260_690_000, 0)) + .saturating_add(T::DbWeight::get().reads(18 as u64)) + .saturating_add(T::DbWeight::get().writes(11 as u64)) + } + // Storage: `Xyk::LiquidityPools` (r:1 w:0) + // Proof: `Xyk::LiquidityPools` (`max_values`: None, `max_size`: Some(29), added: 2504, mode: `MaxEncodedLen`) + // Storage: `StableSwap::Pools` (r:1 w:0) + // Proof: `StableSwap::Pools` (`max_values`: None, `max_size`: Some(66), added: 2541, mode: `MaxEncodedLen`) + // Storage: `AssetRegistry::Metadata` (r:2 w:0) + // Proof: `AssetRegistry::Metadata` (`max_values`: None, `max_size`: Some(153), added: 2628, mode: `MaxEncodedLen`) + // Storage: `ProofOfStake::PromotedPoolRewards` (r:1 w:0) + // Proof: `ProofOfStake::PromotedPoolRewards` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + // Storage: `Vesting::Vesting` (r:2 w:2) + // Proof: `Vesting::Vesting` (`max_values`: None, `max_size`: Some(1857), added: 4332, mode: `MaxEncodedLen`) + // Storage: `Tokens::Locks` (r:2 w:2) + // Proof: `Tokens::Locks` (`max_values`: None, `max_size`: Some(1249), added: 3724, mode: `MaxEncodedLen`) + // Storage: `Tokens::Accounts` (r:5 w:5) + // Proof: `Tokens::Accounts` (`max_values`: None, `max_size`: Some(96), added: 2571, mode: `MaxEncodedLen`) + // Storage: `Tokens::TotalIssuance` (r:1 w:1) + // Proof: `Tokens::TotalIssuance` (`max_values`: None, `max_size`: Some(28), added: 2503, mode: `MaxEncodedLen`) + // Storage: `Tokens::NextCurrencyId` (r:1 w:0) + // Proof: `Tokens::NextCurrencyId` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) + fn mint_liquidity_using_vesting_native_tokens_by_vesting_index_sswap() -> Weight { + (Weight::from_parts(237_010_000, 0)) + .saturating_add(T::DbWeight::get().reads(16 as u64)) + .saturating_add(T::DbWeight::get().writes(10 as u64)) + } + // Storage: `Xyk::LiquidityPools` (r:1 w:0) + // Proof: `Xyk::LiquidityPools` (`max_values`: None, `max_size`: Some(29), added: 2504, mode: `MaxEncodedLen`) + // Storage: `StableSwap::Pools` (r:1 w:0) + // Proof: `StableSwap::Pools` (`max_values`: None, `max_size`: Some(66), added: 2541, mode: `MaxEncodedLen`) + // Storage: `AssetRegistry::Metadata` (r:2 w:0) + // Proof: `AssetRegistry::Metadata` (`max_values`: None, `max_size`: Some(153), added: 2628, mode: `MaxEncodedLen`) + // Storage: `ProofOfStake::PromotedPoolRewards` (r:1 w:0) + // Proof: `ProofOfStake::PromotedPoolRewards` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + // Storage: `Vesting::Vesting` (r:2 w:2) + // Proof: `Vesting::Vesting` (`max_values`: None, `max_size`: Some(1857), added: 4332, mode: `MaxEncodedLen`) + // Storage: `Tokens::Locks` (r:2 w:2) + // Proof: `Tokens::Locks` (`max_values`: None, `max_size`: Some(1249), added: 3724, mode: `MaxEncodedLen`) + // Storage: `Tokens::Accounts` (r:5 w:5) + // Proof: `Tokens::Accounts` (`max_values`: None, `max_size`: Some(96), added: 2571, mode: `MaxEncodedLen`) + // Storage: `Tokens::TotalIssuance` (r:1 w:1) + // Proof: `Tokens::TotalIssuance` (`max_values`: None, `max_size`: Some(28), added: 2503, mode: `MaxEncodedLen`) + // Storage: `Tokens::NextCurrencyId` (r:1 w:0) + // Proof: `Tokens::NextCurrencyId` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) + fn mint_liquidity_using_vesting_native_tokens_sswap() -> Weight { + (Weight::from_parts(235_200_000, 0)) + .saturating_add(T::DbWeight::get().reads(16 as u64)) + .saturating_add(T::DbWeight::get().writes(10 as u64)) + } + // Storage: `Xyk::LiquidityPools` (r:1 w:0) + // Proof: `Xyk::LiquidityPools` (`max_values`: None, `max_size`: Some(29), added: 2504, mode: `MaxEncodedLen`) + // Storage: `StableSwap::Pools` (r:1 w:0) + // Proof: `StableSwap::Pools` (`max_values`: None, `max_size`: Some(66), added: 2541, mode: `MaxEncodedLen`) + // Storage: `AssetRegistry::Metadata` (r:2 w:0) + // Proof: `AssetRegistry::Metadata` (`max_values`: None, `max_size`: Some(153), added: 2628, mode: `MaxEncodedLen`) + // Storage: `Tokens::Accounts` (r:5 w:5) + // Proof: `Tokens::Accounts` (`max_values`: None, `max_size`: Some(96), added: 2571, mode: `MaxEncodedLen`) + // Storage: `Tokens::TotalIssuance` (r:1 w:1) + // Proof: `Tokens::TotalIssuance` (`max_values`: None, `max_size`: Some(28), added: 2503, mode: `MaxEncodedLen`) + // Storage: `System::Account` (r:1 w:0) + // Proof: `System::Account` (`max_values`: None, `max_size`: Some(52), added: 2527, mode: `MaxEncodedLen`) + fn burn_liquidity_sswap() -> Weight { + (Weight::from_parts(125_970_000, 0)) + .saturating_add(T::DbWeight::get().reads(11 as u64)) + .saturating_add(T::DbWeight::get().writes(6 as u64)) + } + // Storage: `Maintenance::MaintenanceStatus` (r:1 w:0) + // Proof: `Maintenance::MaintenanceStatus` (`max_values`: Some(1), `max_size`: Some(2), added: 497, mode: `MaxEncodedLen`) + // Storage: `Xyk::LiquidityPools` (r:99 w:0) + // Proof: `Xyk::LiquidityPools` (`max_values`: None, `max_size`: Some(29), added: 2504, mode: `MaxEncodedLen`) + // Storage: `StableSwap::Pools` (r:99 w:0) + // Proof: `StableSwap::Pools` (`max_values`: None, `max_size`: Some(66), added: 2541, mode: `MaxEncodedLen`) + // Storage: `AssetRegistry::Metadata` (r:100 w:0) + // Proof: `AssetRegistry::Metadata` (`max_values`: None, `max_size`: Some(153), added: 2628, mode: `MaxEncodedLen`) + // Storage: `Tokens::Accounts` (r:597 w:597) + // Proof: `Tokens::Accounts` (`max_values`: None, `max_size`: Some(96), added: 2571, mode: `MaxEncodedLen`) + // Storage: `System::Account` (r:101 w:2) + // Proof: `System::Account` (`max_values`: None, `max_size`: Some(52), added: 2527, mode: `MaxEncodedLen`) + // Storage: `Xyk::Pools` (r:198 w:99) + // Proof: `Xyk::Pools` (`max_values`: None, `max_size`: Some(56), added: 2531, mode: `MaxEncodedLen`) + // Storage: `Tokens::TotalIssuance` (r:1 w:1) + // Proof: `Tokens::TotalIssuance` (`max_values`: None, `max_size`: Some(28), added: 2503, mode: `MaxEncodedLen`) + fn multiswap_asset_sswap(y: u32, ) -> Weight { + (Weight::from_parts(289_970_000, 0)) + // Standard Error: 256_965 + .saturating_add((Weight::from_parts(220_298_290, 0)).saturating_mul(y as u64)) + .saturating_add(T::DbWeight::get().reads(20 as u64)) + .saturating_add(T::DbWeight::get().reads((12 as u64).saturating_mul(y as u64))) + .saturating_add(T::DbWeight::get().writes(13 as u64)) + .saturating_add(T::DbWeight::get().writes((7 as u64).saturating_mul(y as u64))) + } + // Storage: `Maintenance::MaintenanceStatus` (r:1 w:0) + // Proof: `Maintenance::MaintenanceStatus` (`max_values`: Some(1), `max_size`: Some(2), added: 497, mode: `MaxEncodedLen`) + // Storage: `Xyk::LiquidityPools` (r:99 w:0) + // Proof: `Xyk::LiquidityPools` (`max_values`: None, `max_size`: Some(29), added: 2504, mode: `MaxEncodedLen`) + // Storage: `StableSwap::Pools` (r:99 w:0) + // Proof: `StableSwap::Pools` (`max_values`: None, `max_size`: Some(66), added: 2541, mode: `MaxEncodedLen`) + // Storage: `AssetRegistry::Metadata` (r:100 w:0) + // Proof: `AssetRegistry::Metadata` (`max_values`: None, `max_size`: Some(153), added: 2628, mode: `MaxEncodedLen`) + // Storage: `Tokens::Accounts` (r:597 w:597) + // Proof: `Tokens::Accounts` (`max_values`: None, `max_size`: Some(96), added: 2571, mode: `MaxEncodedLen`) + // Storage: `System::Account` (r:101 w:2) + // Proof: `System::Account` (`max_values`: None, `max_size`: Some(52), added: 2527, mode: `MaxEncodedLen`) + // Storage: `Xyk::Pools` (r:198 w:99) + // Proof: `Xyk::Pools` (`max_values`: None, `max_size`: Some(56), added: 2531, mode: `MaxEncodedLen`) + // Storage: `Tokens::TotalIssuance` (r:1 w:1) + // Proof: `Tokens::TotalIssuance` (`max_values`: None, `max_size`: Some(28), added: 2503, mode: `MaxEncodedLen`) + fn multiswap_asset_buy_sswap(y: u32, ) -> Weight { + (Weight::from_parts(331_599_000, 0)) + // Standard Error: 265_694 + .saturating_add((Weight::from_parts(261_837_989, 0)).saturating_mul(y as u64)) + .saturating_add(T::DbWeight::get().reads(20 as u64)) + .saturating_add(T::DbWeight::get().reads((12 as u64).saturating_mul(y as u64))) + .saturating_add(T::DbWeight::get().writes(13 as u64)) + .saturating_add(T::DbWeight::get().writes((7 as u64).saturating_mul(y as u64))) + } +} + +// For backwards compatibility and tests +impl WeightInfo for () { + // Storage: `AssetRegistry::Metadata` (r:3 w:1) + // Proof: `AssetRegistry::Metadata` (`max_values`: None, `max_size`: Some(153), added: 2628, mode: `MaxEncodedLen`) + // Storage: `Bootstrap::BootstrapSchedule` (r:1 w:0) + // Proof: `Bootstrap::BootstrapSchedule` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + // Storage: `Xyk::Pools` (r:2 w:1) + // Proof: `Xyk::Pools` (`max_values`: None, `max_size`: Some(56), added: 2531, mode: `MaxEncodedLen`) + // Storage: `Tokens::Accounts` (r:5 w:5) + // Proof: `Tokens::Accounts` (`max_values`: None, `max_size`: Some(96), added: 2571, mode: `MaxEncodedLen`) + // Storage: `System::Account` (r:1 w:1) + // Proof: `System::Account` (`max_values`: None, `max_size`: Some(52), added: 2527, mode: `MaxEncodedLen`) + // Storage: `Tokens::NextCurrencyId` (r:1 w:1) + // Proof: `Tokens::NextCurrencyId` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) + // Storage: `Tokens::TotalIssuance` (r:1 w:1) + // Proof: `Tokens::TotalIssuance` (`max_values`: None, `max_size`: Some(28), added: 2503, mode: `MaxEncodedLen`) + // Storage: `Xyk::LiquidityAssets` (r:0 w:1) + // Proof: `Xyk::LiquidityAssets` (`max_values`: None, `max_size`: Some(29), added: 2504, mode: `MaxEncodedLen`) + // Storage: `Xyk::LiquidityPools` (r:0 w:1) + // Proof: `Xyk::LiquidityPools` (`max_values`: None, `max_size`: Some(29), added: 2504, mode: `MaxEncodedLen`) + fn create_pool_xyk() -> Weight { + (Weight::from_parts(170_010_000, 0)) + .saturating_add(RocksDbWeight::get().reads(14 as u64)) + .saturating_add(RocksDbWeight::get().writes(12 as u64)) + } + // Storage: `Xyk::LiquidityPools` (r:1 w:0) + // Proof: `Xyk::LiquidityPools` (`max_values`: None, `max_size`: Some(29), added: 2504, mode: `MaxEncodedLen`) + // Storage: `AssetRegistry::Metadata` (r:2 w:0) + // Proof: `AssetRegistry::Metadata` (`max_values`: None, `max_size`: Some(153), added: 2628, mode: `MaxEncodedLen`) + // Storage: `Xyk::LiquidityAssets` (r:1 w:0) + // Proof: `Xyk::LiquidityAssets` (`max_values`: None, `max_size`: Some(29), added: 2504, mode: `MaxEncodedLen`) + // Storage: `Xyk::Pools` (r:1 w:1) + // Proof: `Xyk::Pools` (`max_values`: None, `max_size`: Some(56), added: 2531, mode: `MaxEncodedLen`) + // Storage: `Tokens::TotalIssuance` (r:1 w:1) + // Proof: `Tokens::TotalIssuance` (`max_values`: None, `max_size`: Some(28), added: 2503, mode: `MaxEncodedLen`) + // Storage: `Tokens::Accounts` (r:5 w:5) + // Proof: `Tokens::Accounts` (`max_values`: None, `max_size`: Some(96), added: 2571, mode: `MaxEncodedLen`) + // Storage: `Tokens::NextCurrencyId` (r:1 w:0) + // Proof: `Tokens::NextCurrencyId` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) + // Storage: `ProofOfStake::PromotedPoolRewards` (r:1 w:0) + // Proof: `ProofOfStake::PromotedPoolRewards` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + // Storage: `MultiPurposeLiquidity::ReserveStatus` (r:1 w:1) + // Proof: `MultiPurposeLiquidity::ReserveStatus` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`) + // Storage: `ProofOfStake::RewardsInfo` (r:1 w:1) + // Proof: `ProofOfStake::RewardsInfo` (`max_values`: None, `max_size`: None, mode: `Measured`) + // Storage: `ProofOfStake::TotalActivatedLiquidity` (r:1 w:1) + // Proof: `ProofOfStake::TotalActivatedLiquidity` (`max_values`: None, `max_size`: None, mode: `Measured`) + fn mint_liquidity_xyk() -> Weight { + (Weight::from_parts(216_450_000, 0)) + .saturating_add(RocksDbWeight::get().reads(16 as u64)) + .saturating_add(RocksDbWeight::get().writes(10 as u64)) + } + // Storage: `Xyk::LiquidityPools` (r:1 w:0) + // Proof: `Xyk::LiquidityPools` (`max_values`: None, `max_size`: Some(29), added: 2504, mode: `MaxEncodedLen`) + // Storage: `AssetRegistry::Metadata` (r:2 w:0) + // Proof: `AssetRegistry::Metadata` (`max_values`: None, `max_size`: Some(153), added: 2628, mode: `MaxEncodedLen`) + // Storage: `Xyk::LiquidityAssets` (r:2 w:0) + // Proof: `Xyk::LiquidityAssets` (`max_values`: None, `max_size`: Some(29), added: 2504, mode: `MaxEncodedLen`) + // Storage: `Tokens::TotalIssuance` (r:1 w:1) + // Proof: `Tokens::TotalIssuance` (`max_values`: None, `max_size`: Some(28), added: 2503, mode: `MaxEncodedLen`) + // Storage: `Xyk::Pools` (r:4 w:1) + // Proof: `Xyk::Pools` (`max_values`: None, `max_size`: Some(56), added: 2531, mode: `MaxEncodedLen`) + // Storage: `Tokens::Accounts` (r:7 w:7) + // Proof: `Tokens::Accounts` (`max_values`: None, `max_size`: Some(96), added: 2571, mode: `MaxEncodedLen`) + // Storage: `Maintenance::MaintenanceStatus` (r:1 w:0) + // Proof: `Maintenance::MaintenanceStatus` (`max_values`: Some(1), `max_size`: Some(2), added: 497, mode: `MaxEncodedLen`) + // Storage: `System::Account` (r:2 w:2) + // Proof: `System::Account` (`max_values`: None, `max_size`: Some(52), added: 2527, mode: `MaxEncodedLen`) + // Storage: `Tokens::NextCurrencyId` (r:1 w:0) + // Proof: `Tokens::NextCurrencyId` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) + // Storage: `ProofOfStake::PromotedPoolRewards` (r:1 w:0) + // Proof: `ProofOfStake::PromotedPoolRewards` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + // Storage: `MultiPurposeLiquidity::ReserveStatus` (r:1 w:1) + // Proof: `MultiPurposeLiquidity::ReserveStatus` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`) + // Storage: `ProofOfStake::RewardsInfo` (r:1 w:1) + // Proof: `ProofOfStake::RewardsInfo` (`max_values`: None, `max_size`: None, mode: `Measured`) + // Storage: `ProofOfStake::TotalActivatedLiquidity` (r:1 w:1) + // Proof: `ProofOfStake::TotalActivatedLiquidity` (`max_values`: None, `max_size`: None, mode: `Measured`) + fn mint_liquidity_fixed_amounts_xyk() -> Weight { + (Weight::from_parts(407_209_000, 0)) + .saturating_add(RocksDbWeight::get().reads(25 as u64)) + .saturating_add(RocksDbWeight::get().writes(14 as u64)) + } + // Storage: `Xyk::LiquidityPools` (r:1 w:0) + // Proof: `Xyk::LiquidityPools` (`max_values`: None, `max_size`: Some(29), added: 2504, mode: `MaxEncodedLen`) + // Storage: `AssetRegistry::Metadata` (r:2 w:0) + // Proof: `AssetRegistry::Metadata` (`max_values`: None, `max_size`: Some(153), added: 2628, mode: `MaxEncodedLen`) + // Storage: `ProofOfStake::PromotedPoolRewards` (r:1 w:0) + // Proof: `ProofOfStake::PromotedPoolRewards` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + // Storage: `Vesting::Vesting` (r:2 w:2) + // Proof: `Vesting::Vesting` (`max_values`: None, `max_size`: Some(1857), added: 4332, mode: `MaxEncodedLen`) + // Storage: `Tokens::Locks` (r:2 w:2) + // Proof: `Tokens::Locks` (`max_values`: None, `max_size`: Some(1249), added: 3724, mode: `MaxEncodedLen`) + // Storage: `Tokens::Accounts` (r:5 w:5) + // Proof: `Tokens::Accounts` (`max_values`: None, `max_size`: Some(96), added: 2571, mode: `MaxEncodedLen`) + // Storage: `Xyk::LiquidityAssets` (r:1 w:0) + // Proof: `Xyk::LiquidityAssets` (`max_values`: None, `max_size`: Some(29), added: 2504, mode: `MaxEncodedLen`) + // Storage: `Xyk::Pools` (r:1 w:1) + // Proof: `Xyk::Pools` (`max_values`: None, `max_size`: Some(56), added: 2531, mode: `MaxEncodedLen`) + // Storage: `Tokens::TotalIssuance` (r:1 w:1) + // Proof: `Tokens::TotalIssuance` (`max_values`: None, `max_size`: Some(28), added: 2503, mode: `MaxEncodedLen`) + // Storage: `Tokens::NextCurrencyId` (r:1 w:0) + // Proof: `Tokens::NextCurrencyId` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) + fn mint_liquidity_using_vesting_native_tokens_by_vesting_index_xyk() -> Weight { + (Weight::from_parts(237_820_000, 0)) + .saturating_add(RocksDbWeight::get().reads(17 as u64)) + .saturating_add(RocksDbWeight::get().writes(11 as u64)) + } + // Storage: `Xyk::LiquidityPools` (r:1 w:0) + // Proof: `Xyk::LiquidityPools` (`max_values`: None, `max_size`: Some(29), added: 2504, mode: `MaxEncodedLen`) + // Storage: `AssetRegistry::Metadata` (r:2 w:0) + // Proof: `AssetRegistry::Metadata` (`max_values`: None, `max_size`: Some(153), added: 2628, mode: `MaxEncodedLen`) + // Storage: `ProofOfStake::PromotedPoolRewards` (r:1 w:0) + // Proof: `ProofOfStake::PromotedPoolRewards` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + // Storage: `Vesting::Vesting` (r:2 w:2) + // Proof: `Vesting::Vesting` (`max_values`: None, `max_size`: Some(1857), added: 4332, mode: `MaxEncodedLen`) + // Storage: `Tokens::Locks` (r:2 w:2) + // Proof: `Tokens::Locks` (`max_values`: None, `max_size`: Some(1249), added: 3724, mode: `MaxEncodedLen`) + // Storage: `Tokens::Accounts` (r:5 w:5) + // Proof: `Tokens::Accounts` (`max_values`: None, `max_size`: Some(96), added: 2571, mode: `MaxEncodedLen`) + // Storage: `Xyk::LiquidityAssets` (r:1 w:0) + // Proof: `Xyk::LiquidityAssets` (`max_values`: None, `max_size`: Some(29), added: 2504, mode: `MaxEncodedLen`) + // Storage: `Xyk::Pools` (r:1 w:1) + // Proof: `Xyk::Pools` (`max_values`: None, `max_size`: Some(56), added: 2531, mode: `MaxEncodedLen`) + // Storage: `Tokens::TotalIssuance` (r:1 w:1) + // Proof: `Tokens::TotalIssuance` (`max_values`: None, `max_size`: Some(28), added: 2503, mode: `MaxEncodedLen`) + // Storage: `Tokens::NextCurrencyId` (r:1 w:0) + // Proof: `Tokens::NextCurrencyId` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) + fn mint_liquidity_using_vesting_native_tokens_xyk() -> Weight { + (Weight::from_parts(239_980_000, 0)) + .saturating_add(RocksDbWeight::get().reads(17 as u64)) + .saturating_add(RocksDbWeight::get().writes(11 as u64)) + } + // Storage: `Xyk::LiquidityPools` (r:1 w:0) + // Proof: `Xyk::LiquidityPools` (`max_values`: None, `max_size`: Some(29), added: 2504, mode: `MaxEncodedLen`) + // Storage: `AssetRegistry::Metadata` (r:2 w:0) + // Proof: `AssetRegistry::Metadata` (`max_values`: None, `max_size`: Some(153), added: 2628, mode: `MaxEncodedLen`) + // Storage: `Xyk::LiquidityAssets` (r:1 w:0) + // Proof: `Xyk::LiquidityAssets` (`max_values`: None, `max_size`: Some(29), added: 2504, mode: `MaxEncodedLen`) + // Storage: `MultiPurposeLiquidity::ReserveStatus` (r:1 w:0) + // Proof: `MultiPurposeLiquidity::ReserveStatus` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`) + // Storage: `Xyk::Pools` (r:1 w:1) + // Proof: `Xyk::Pools` (`max_values`: None, `max_size`: Some(56), added: 2531, mode: `MaxEncodedLen`) + // Storage: `Tokens::Accounts` (r:5 w:5) + // Proof: `Tokens::Accounts` (`max_values`: None, `max_size`: Some(96), added: 2571, mode: `MaxEncodedLen`) + // Storage: `Tokens::TotalIssuance` (r:1 w:1) + // Proof: `Tokens::TotalIssuance` (`max_values`: None, `max_size`: Some(28), added: 2503, mode: `MaxEncodedLen`) + fn burn_liquidity_xyk() -> Weight { + (Weight::from_parts(153_580_000, 0)) + .saturating_add(RocksDbWeight::get().reads(12 as u64)) + .saturating_add(RocksDbWeight::get().writes(7 as u64)) + } + // Storage: `Maintenance::MaintenanceStatus` (r:1 w:0) + // Proof: `Maintenance::MaintenanceStatus` (`max_values`: Some(1), `max_size`: Some(2), added: 497, mode: `MaxEncodedLen`) + // Storage: `Xyk::LiquidityPools` (r:99 w:0) + // Proof: `Xyk::LiquidityPools` (`max_values`: None, `max_size`: Some(29), added: 2504, mode: `MaxEncodedLen`) + // Storage: `AssetRegistry::Metadata` (r:100 w:0) + // Proof: `AssetRegistry::Metadata` (`max_values`: None, `max_size`: Some(153), added: 2628, mode: `MaxEncodedLen`) + // Storage: `Xyk::LiquidityAssets` (r:99 w:0) + // Proof: `Xyk::LiquidityAssets` (`max_values`: None, `max_size`: Some(29), added: 2504, mode: `MaxEncodedLen`) + // Storage: `Tokens::TotalIssuance` (r:100 w:1) + // Proof: `Tokens::TotalIssuance` (`max_values`: None, `max_size`: Some(28), added: 2503, mode: `MaxEncodedLen`) + // Storage: `Xyk::Pools` (r:297 w:198) + // Proof: `Xyk::Pools` (`max_values`: None, `max_size`: Some(56), added: 2531, mode: `MaxEncodedLen`) + // Storage: `Tokens::Accounts` (r:400 w:400) + // Proof: `Tokens::Accounts` (`max_values`: None, `max_size`: Some(96), added: 2571, mode: `MaxEncodedLen`) + // Storage: `System::Account` (r:2 w:2) + // Proof: `System::Account` (`max_values`: None, `max_size`: Some(52), added: 2527, mode: `MaxEncodedLen`) + fn multiswap_asset_xyk(y: u32, ) -> Weight { + (Weight::from_parts(309_150_000, 0)) + // Standard Error: 341_216 + .saturating_add((Weight::from_parts(236_593_363, 0)).saturating_mul(y as u64)) + .saturating_add(RocksDbWeight::get().reads(20 as u64)) + .saturating_add(RocksDbWeight::get().reads((11 as u64).saturating_mul(y as u64))) + .saturating_add(RocksDbWeight::get().writes(1 as u64)) + .saturating_add(RocksDbWeight::get().writes((6 as u64).saturating_mul(y as u64))) + } + // Storage: `Maintenance::MaintenanceStatus` (r:1 w:0) + // Proof: `Maintenance::MaintenanceStatus` (`max_values`: Some(1), `max_size`: Some(2), added: 497, mode: `MaxEncodedLen`) + // Storage: `Xyk::LiquidityPools` (r:99 w:0) + // Proof: `Xyk::LiquidityPools` (`max_values`: None, `max_size`: Some(29), added: 2504, mode: `MaxEncodedLen`) + // Storage: `AssetRegistry::Metadata` (r:100 w:0) + // Proof: `AssetRegistry::Metadata` (`max_values`: None, `max_size`: Some(153), added: 2628, mode: `MaxEncodedLen`) + // Storage: `Xyk::Pools` (r:297 w:198) + // Proof: `Xyk::Pools` (`max_values`: None, `max_size`: Some(56), added: 2531, mode: `MaxEncodedLen`) + // Storage: `Xyk::LiquidityAssets` (r:99 w:0) + // Proof: `Xyk::LiquidityAssets` (`max_values`: None, `max_size`: Some(29), added: 2504, mode: `MaxEncodedLen`) + // Storage: `Tokens::TotalIssuance` (r:100 w:1) + // Proof: `Tokens::TotalIssuance` (`max_values`: None, `max_size`: Some(28), added: 2503, mode: `MaxEncodedLen`) + // Storage: `Tokens::Accounts` (r:400 w:400) + // Proof: `Tokens::Accounts` (`max_values`: None, `max_size`: Some(96), added: 2571, mode: `MaxEncodedLen`) + // Storage: `System::Account` (r:2 w:2) + // Proof: `System::Account` (`max_values`: None, `max_size`: Some(52), added: 2527, mode: `MaxEncodedLen`) + fn multiswap_asset_buy_xyk(y: u32, ) -> Weight { + (Weight::from_parts(334_430_000, 0)) + // Standard Error: 468_448 + .saturating_add((Weight::from_parts(255_448_728, 0)).saturating_mul(y as u64)) + .saturating_add(RocksDbWeight::get().reads(20 as u64)) + .saturating_add(RocksDbWeight::get().reads((11 as u64).saturating_mul(y as u64))) + .saturating_add(RocksDbWeight::get().writes(1 as u64)) + .saturating_add(RocksDbWeight::get().writes((6 as u64).saturating_mul(y as u64))) + } + // Storage: `AssetRegistry::Metadata` (r:3 w:1) + // Proof: `AssetRegistry::Metadata` (`max_values`: None, `max_size`: Some(153), added: 2628, mode: `MaxEncodedLen`) + // Storage: `Bootstrap::BootstrapSchedule` (r:1 w:0) + // Proof: `Bootstrap::BootstrapSchedule` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + // Storage: `Tokens::NextCurrencyId` (r:1 w:1) + // Proof: `Tokens::NextCurrencyId` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) + // Storage: `Tokens::Accounts` (r:5 w:5) + // Proof: `Tokens::Accounts` (`max_values`: None, `max_size`: Some(96), added: 2571, mode: `MaxEncodedLen`) + // Storage: `System::Account` (r:1 w:1) + // Proof: `System::Account` (`max_values`: None, `max_size`: Some(52), added: 2527, mode: `MaxEncodedLen`) + // Storage: `Tokens::TotalIssuance` (r:1 w:1) + // Proof: `Tokens::TotalIssuance` (`max_values`: None, `max_size`: Some(28), added: 2503, mode: `MaxEncodedLen`) + // Storage: `StableSwap::Pools` (r:0 w:1) + // Proof: `StableSwap::Pools` (`max_values`: None, `max_size`: Some(66), added: 2541, mode: `MaxEncodedLen`) + fn create_pool_sswap() -> Weight { + (Weight::from_parts(173_740_000, 0)) + .saturating_add(RocksDbWeight::get().reads(12 as u64)) + .saturating_add(RocksDbWeight::get().writes(10 as u64)) + } + // Storage: `Xyk::LiquidityPools` (r:1 w:0) + // Proof: `Xyk::LiquidityPools` (`max_values`: None, `max_size`: Some(29), added: 2504, mode: `MaxEncodedLen`) + // Storage: `StableSwap::Pools` (r:1 w:0) + // Proof: `StableSwap::Pools` (`max_values`: None, `max_size`: Some(66), added: 2541, mode: `MaxEncodedLen`) + // Storage: `AssetRegistry::Metadata` (r:2 w:0) + // Proof: `AssetRegistry::Metadata` (`max_values`: None, `max_size`: Some(153), added: 2628, mode: `MaxEncodedLen`) + // Storage: `Tokens::TotalIssuance` (r:1 w:1) + // Proof: `Tokens::TotalIssuance` (`max_values`: None, `max_size`: Some(28), added: 2503, mode: `MaxEncodedLen`) + // Storage: `Tokens::Accounts` (r:5 w:5) + // Proof: `Tokens::Accounts` (`max_values`: None, `max_size`: Some(96), added: 2571, mode: `MaxEncodedLen`) + // Storage: `Tokens::NextCurrencyId` (r:1 w:0) + // Proof: `Tokens::NextCurrencyId` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) + // Storage: `ProofOfStake::PromotedPoolRewards` (r:1 w:0) + // Proof: `ProofOfStake::PromotedPoolRewards` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + // Storage: `MultiPurposeLiquidity::ReserveStatus` (r:1 w:1) + // Proof: `MultiPurposeLiquidity::ReserveStatus` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`) + // Storage: `ProofOfStake::RewardsInfo` (r:1 w:1) + // Proof: `ProofOfStake::RewardsInfo` (`max_values`: None, `max_size`: None, mode: `Measured`) + // Storage: `ProofOfStake::TotalActivatedLiquidity` (r:1 w:1) + // Proof: `ProofOfStake::TotalActivatedLiquidity` (`max_values`: None, `max_size`: None, mode: `Measured`) + fn mint_liquidity_sswap() -> Weight { + (Weight::from_parts(215_460_000, 0)) + .saturating_add(RocksDbWeight::get().reads(15 as u64)) + .saturating_add(RocksDbWeight::get().writes(9 as u64)) + } + // Storage: `Xyk::LiquidityPools` (r:1 w:0) + // Proof: `Xyk::LiquidityPools` (`max_values`: None, `max_size`: Some(29), added: 2504, mode: `MaxEncodedLen`) + // Storage: `StableSwap::Pools` (r:1 w:0) + // Proof: `StableSwap::Pools` (`max_values`: None, `max_size`: Some(66), added: 2541, mode: `MaxEncodedLen`) + // Storage: `AssetRegistry::Metadata` (r:2 w:0) + // Proof: `AssetRegistry::Metadata` (`max_values`: None, `max_size`: Some(153), added: 2628, mode: `MaxEncodedLen`) + // Storage: `Tokens::TotalIssuance` (r:1 w:1) + // Proof: `Tokens::TotalIssuance` (`max_values`: None, `max_size`: Some(28), added: 2503, mode: `MaxEncodedLen`) + // Storage: `Tokens::Accounts` (r:6 w:6) + // Proof: `Tokens::Accounts` (`max_values`: None, `max_size`: Some(96), added: 2571, mode: `MaxEncodedLen`) + // Storage: `System::Account` (r:2 w:1) + // Proof: `System::Account` (`max_values`: None, `max_size`: Some(52), added: 2527, mode: `MaxEncodedLen`) + // Storage: `Tokens::NextCurrencyId` (r:1 w:0) + // Proof: `Tokens::NextCurrencyId` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) + // Storage: `ProofOfStake::PromotedPoolRewards` (r:1 w:0) + // Proof: `ProofOfStake::PromotedPoolRewards` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + // Storage: `MultiPurposeLiquidity::ReserveStatus` (r:1 w:1) + // Proof: `MultiPurposeLiquidity::ReserveStatus` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`) + // Storage: `ProofOfStake::RewardsInfo` (r:1 w:1) + // Proof: `ProofOfStake::RewardsInfo` (`max_values`: None, `max_size`: None, mode: `Measured`) + // Storage: `ProofOfStake::TotalActivatedLiquidity` (r:1 w:1) + // Proof: `ProofOfStake::TotalActivatedLiquidity` (`max_values`: None, `max_size`: None, mode: `Measured`) + fn mint_liquidity_fixed_amounts_sswap() -> Weight { + (Weight::from_parts(260_690_000, 0)) + .saturating_add(RocksDbWeight::get().reads(18 as u64)) + .saturating_add(RocksDbWeight::get().writes(11 as u64)) + } + // Storage: `Xyk::LiquidityPools` (r:1 w:0) + // Proof: `Xyk::LiquidityPools` (`max_values`: None, `max_size`: Some(29), added: 2504, mode: `MaxEncodedLen`) + // Storage: `StableSwap::Pools` (r:1 w:0) + // Proof: `StableSwap::Pools` (`max_values`: None, `max_size`: Some(66), added: 2541, mode: `MaxEncodedLen`) + // Storage: `AssetRegistry::Metadata` (r:2 w:0) + // Proof: `AssetRegistry::Metadata` (`max_values`: None, `max_size`: Some(153), added: 2628, mode: `MaxEncodedLen`) + // Storage: `ProofOfStake::PromotedPoolRewards` (r:1 w:0) + // Proof: `ProofOfStake::PromotedPoolRewards` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + // Storage: `Vesting::Vesting` (r:2 w:2) + // Proof: `Vesting::Vesting` (`max_values`: None, `max_size`: Some(1857), added: 4332, mode: `MaxEncodedLen`) + // Storage: `Tokens::Locks` (r:2 w:2) + // Proof: `Tokens::Locks` (`max_values`: None, `max_size`: Some(1249), added: 3724, mode: `MaxEncodedLen`) + // Storage: `Tokens::Accounts` (r:5 w:5) + // Proof: `Tokens::Accounts` (`max_values`: None, `max_size`: Some(96), added: 2571, mode: `MaxEncodedLen`) + // Storage: `Tokens::TotalIssuance` (r:1 w:1) + // Proof: `Tokens::TotalIssuance` (`max_values`: None, `max_size`: Some(28), added: 2503, mode: `MaxEncodedLen`) + // Storage: `Tokens::NextCurrencyId` (r:1 w:0) + // Proof: `Tokens::NextCurrencyId` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) + fn mint_liquidity_using_vesting_native_tokens_by_vesting_index_sswap() -> Weight { + (Weight::from_parts(237_010_000, 0)) + .saturating_add(RocksDbWeight::get().reads(16 as u64)) + .saturating_add(RocksDbWeight::get().writes(10 as u64)) + } + // Storage: `Xyk::LiquidityPools` (r:1 w:0) + // Proof: `Xyk::LiquidityPools` (`max_values`: None, `max_size`: Some(29), added: 2504, mode: `MaxEncodedLen`) + // Storage: `StableSwap::Pools` (r:1 w:0) + // Proof: `StableSwap::Pools` (`max_values`: None, `max_size`: Some(66), added: 2541, mode: `MaxEncodedLen`) + // Storage: `AssetRegistry::Metadata` (r:2 w:0) + // Proof: `AssetRegistry::Metadata` (`max_values`: None, `max_size`: Some(153), added: 2628, mode: `MaxEncodedLen`) + // Storage: `ProofOfStake::PromotedPoolRewards` (r:1 w:0) + // Proof: `ProofOfStake::PromotedPoolRewards` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + // Storage: `Vesting::Vesting` (r:2 w:2) + // Proof: `Vesting::Vesting` (`max_values`: None, `max_size`: Some(1857), added: 4332, mode: `MaxEncodedLen`) + // Storage: `Tokens::Locks` (r:2 w:2) + // Proof: `Tokens::Locks` (`max_values`: None, `max_size`: Some(1249), added: 3724, mode: `MaxEncodedLen`) + // Storage: `Tokens::Accounts` (r:5 w:5) + // Proof: `Tokens::Accounts` (`max_values`: None, `max_size`: Some(96), added: 2571, mode: `MaxEncodedLen`) + // Storage: `Tokens::TotalIssuance` (r:1 w:1) + // Proof: `Tokens::TotalIssuance` (`max_values`: None, `max_size`: Some(28), added: 2503, mode: `MaxEncodedLen`) + // Storage: `Tokens::NextCurrencyId` (r:1 w:0) + // Proof: `Tokens::NextCurrencyId` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) + fn mint_liquidity_using_vesting_native_tokens_sswap() -> Weight { + (Weight::from_parts(235_200_000, 0)) + .saturating_add(RocksDbWeight::get().reads(16 as u64)) + .saturating_add(RocksDbWeight::get().writes(10 as u64)) + } + // Storage: `Xyk::LiquidityPools` (r:1 w:0) + // Proof: `Xyk::LiquidityPools` (`max_values`: None, `max_size`: Some(29), added: 2504, mode: `MaxEncodedLen`) + // Storage: `StableSwap::Pools` (r:1 w:0) + // Proof: `StableSwap::Pools` (`max_values`: None, `max_size`: Some(66), added: 2541, mode: `MaxEncodedLen`) + // Storage: `AssetRegistry::Metadata` (r:2 w:0) + // Proof: `AssetRegistry::Metadata` (`max_values`: None, `max_size`: Some(153), added: 2628, mode: `MaxEncodedLen`) + // Storage: `Tokens::Accounts` (r:5 w:5) + // Proof: `Tokens::Accounts` (`max_values`: None, `max_size`: Some(96), added: 2571, mode: `MaxEncodedLen`) + // Storage: `Tokens::TotalIssuance` (r:1 w:1) + // Proof: `Tokens::TotalIssuance` (`max_values`: None, `max_size`: Some(28), added: 2503, mode: `MaxEncodedLen`) + // Storage: `System::Account` (r:1 w:0) + // Proof: `System::Account` (`max_values`: None, `max_size`: Some(52), added: 2527, mode: `MaxEncodedLen`) + fn burn_liquidity_sswap() -> Weight { + (Weight::from_parts(125_970_000, 0)) + .saturating_add(RocksDbWeight::get().reads(11 as u64)) + .saturating_add(RocksDbWeight::get().writes(6 as u64)) + } + // Storage: `Maintenance::MaintenanceStatus` (r:1 w:0) + // Proof: `Maintenance::MaintenanceStatus` (`max_values`: Some(1), `max_size`: Some(2), added: 497, mode: `MaxEncodedLen`) + // Storage: `Xyk::LiquidityPools` (r:99 w:0) + // Proof: `Xyk::LiquidityPools` (`max_values`: None, `max_size`: Some(29), added: 2504, mode: `MaxEncodedLen`) + // Storage: `StableSwap::Pools` (r:99 w:0) + // Proof: `StableSwap::Pools` (`max_values`: None, `max_size`: Some(66), added: 2541, mode: `MaxEncodedLen`) + // Storage: `AssetRegistry::Metadata` (r:100 w:0) + // Proof: `AssetRegistry::Metadata` (`max_values`: None, `max_size`: Some(153), added: 2628, mode: `MaxEncodedLen`) + // Storage: `Tokens::Accounts` (r:597 w:597) + // Proof: `Tokens::Accounts` (`max_values`: None, `max_size`: Some(96), added: 2571, mode: `MaxEncodedLen`) + // Storage: `System::Account` (r:101 w:2) + // Proof: `System::Account` (`max_values`: None, `max_size`: Some(52), added: 2527, mode: `MaxEncodedLen`) + // Storage: `Xyk::Pools` (r:198 w:99) + // Proof: `Xyk::Pools` (`max_values`: None, `max_size`: Some(56), added: 2531, mode: `MaxEncodedLen`) + // Storage: `Tokens::TotalIssuance` (r:1 w:1) + // Proof: `Tokens::TotalIssuance` (`max_values`: None, `max_size`: Some(28), added: 2503, mode: `MaxEncodedLen`) + fn multiswap_asset_sswap(y: u32, ) -> Weight { + (Weight::from_parts(289_970_000, 0)) + // Standard Error: 256_965 + .saturating_add((Weight::from_parts(220_298_290, 0)).saturating_mul(y as u64)) + .saturating_add(RocksDbWeight::get().reads(20 as u64)) + .saturating_add(RocksDbWeight::get().reads((12 as u64).saturating_mul(y as u64))) + .saturating_add(RocksDbWeight::get().writes(13 as u64)) + .saturating_add(RocksDbWeight::get().writes((7 as u64).saturating_mul(y as u64))) + } + // Storage: `Maintenance::MaintenanceStatus` (r:1 w:0) + // Proof: `Maintenance::MaintenanceStatus` (`max_values`: Some(1), `max_size`: Some(2), added: 497, mode: `MaxEncodedLen`) + // Storage: `Xyk::LiquidityPools` (r:99 w:0) + // Proof: `Xyk::LiquidityPools` (`max_values`: None, `max_size`: Some(29), added: 2504, mode: `MaxEncodedLen`) + // Storage: `StableSwap::Pools` (r:99 w:0) + // Proof: `StableSwap::Pools` (`max_values`: None, `max_size`: Some(66), added: 2541, mode: `MaxEncodedLen`) + // Storage: `AssetRegistry::Metadata` (r:100 w:0) + // Proof: `AssetRegistry::Metadata` (`max_values`: None, `max_size`: Some(153), added: 2628, mode: `MaxEncodedLen`) + // Storage: `Tokens::Accounts` (r:597 w:597) + // Proof: `Tokens::Accounts` (`max_values`: None, `max_size`: Some(96), added: 2571, mode: `MaxEncodedLen`) + // Storage: `System::Account` (r:101 w:2) + // Proof: `System::Account` (`max_values`: None, `max_size`: Some(52), added: 2527, mode: `MaxEncodedLen`) + // Storage: `Xyk::Pools` (r:198 w:99) + // Proof: `Xyk::Pools` (`max_values`: None, `max_size`: Some(56), added: 2531, mode: `MaxEncodedLen`) + // Storage: `Tokens::TotalIssuance` (r:1 w:1) + // Proof: `Tokens::TotalIssuance` (`max_values`: None, `max_size`: Some(28), added: 2503, mode: `MaxEncodedLen`) + fn multiswap_asset_buy_sswap(y: u32, ) -> Weight { + (Weight::from_parts(331_599_000, 0)) + // Standard Error: 265_694 + .saturating_add((Weight::from_parts(261_837_989, 0)).saturating_mul(y as u64)) + .saturating_add(RocksDbWeight::get().reads(20 as u64)) + .saturating_add(RocksDbWeight::get().reads((12 as u64).saturating_mul(y as u64))) + .saturating_add(RocksDbWeight::get().writes(13 as u64)) + .saturating_add(RocksDbWeight::get().writes((7 as u64).saturating_mul(y as u64))) + } +} diff --git a/gasp-node/pallets/metamask-signature-rpc/Cargo.toml b/gasp-node/pallets/metamask-signature-rpc/Cargo.toml new file mode 100644 index 000000000..c0c866152 --- /dev/null +++ b/gasp-node/pallets/metamask-signature-rpc/Cargo.toml @@ -0,0 +1,41 @@ +[package] +authors = ['Mangata team'] +name = "metamask-signature-rpc" +version = "2.0.0" +edition = "2018" +description = "RPC calls for Metamask" +license = "GPL-3.0-or-later" + +[dependencies] +codec = { workspace = true } +jsonrpsee = { workspace = true, features = ["server", "client", "macros"] } +serde = { workspace = true, features = ["derive"], optional = true } + +# Substrate packages + +sp-api = { workspace = true, default-features = false } +sp-blockchain = { workspace = true, default-features = false } +sp-rpc = { workspace = true, default-features = false } +sp-core = { workspace = true, default-features = false } +sp-std = { workspace = true, default-features = false } +sp-runtime = { workspace = true, default-features = false } +mangata-types = { workspace = true, default-features = false } +array-bytes = { workspace = true } + +# local packages + +metamask-signature-runtime-api = { version = "2.0.0", path = "../metamask-signature-runtime-api", default-features = false } + +[features] +default = ["std"] + +std = [ + "serde", + "sp-api/std", + "sp-core/std", + "sp-std/std", + "sp-runtime/std", + "metamask-signature-runtime-api/std", + "mangata-types/std", + "codec/std", +] diff --git a/gasp-node/pallets/metamask-signature-rpc/src/lib.rs b/gasp-node/pallets/metamask-signature-rpc/src/lib.rs new file mode 100644 index 000000000..f5c7b6cbb --- /dev/null +++ b/gasp-node/pallets/metamask-signature-rpc/src/lib.rs @@ -0,0 +1,77 @@ +// Copyright (C) 2021 Mangata team + +use codec::Codec; +use jsonrpsee::{ + core::{async_trait, RpcResult}, + proc_macros::rpc, + types::error::ErrorObject, +}; +pub use metamask_signature_runtime_api::MetamaskSignatureRuntimeApi; +use sp_api::ProvideRuntimeApi; +use sp_blockchain::HeaderBackend; +use sp_runtime::traits::Block as BlockT; +use std::sync::Arc; + +#[rpc(client, server)] +pub trait MetamaskSignatureApi { + /// Returns eip712 compatible SignedData V4 struct + /// + #[method(name = "metamask_get_eip712_sign_data")] + fn get_eip712_sign_data( + &self, + encoded_call: String, + at: Option, + ) -> RpcResult; +} + +pub struct MetamaskSignature { + client: Arc, + _marker: std::marker::PhantomData, +} + +impl MetamaskSignature { + pub fn new(client: Arc) -> Self { + Self { client, _marker: Default::default() } + } +} + +use array_bytes::hex2bytes; + +#[async_trait] +impl MetamaskSignatureApiServer<::Hash> for MetamaskSignature +where + Block: BlockT, + C: Send + Sync + 'static, + C: ProvideRuntimeApi, + C: HeaderBackend, + C::Api: MetamaskSignatureRuntimeApi, +{ + fn get_eip712_sign_data( + &self, + call: String, + at: Option<::Hash>, + ) -> RpcResult { + let api = self.client.runtime_api(); + let at = at.unwrap_or(self.client.info().best_hash); + + let call = hex2bytes(call).map_err(|e| { + ErrorObject::owned(0, "Unable to serve the request", Some(format!("{:?}", e))) + })?; + + api.get_eip712_sign_data(at, call) + .map_err(|e| { + ErrorObject::owned(0, "Unable to serve the request", Some(format!("{:?}", e))) + }) + .and_then(|v| { + if v.is_empty() { + Err(ErrorObject::owned( + 0, + "Unable to serve the request", + Some(format!("Empty response")), + )) + } else { + Ok(v) + } + }) + } +} diff --git a/gasp-node/pallets/metamask-signature-runtime-api/Cargo.toml b/gasp-node/pallets/metamask-signature-runtime-api/Cargo.toml new file mode 100644 index 000000000..32558e40c --- /dev/null +++ b/gasp-node/pallets/metamask-signature-runtime-api/Cargo.toml @@ -0,0 +1,20 @@ +[package] +authors = ['Mangata team'] +name = "metamask-signature-runtime-api" +version = "2.0.0" +edition = "2018" +license = "GPL-3.0-or-later" + +[dependencies] +codec = { workspace = true, default-features = false, features = ["derive"] } +sp-api = { workspace = true, default-features = false } +sp-std = { workspace = true, default-features = false } + +[features] +default = ["std"] + +std = [ + "codec/std", + "sp-api/std", + "sp-std/std", +] diff --git a/gasp-node/pallets/metamask-signature-runtime-api/src/lib.rs b/gasp-node/pallets/metamask-signature-runtime-api/src/lib.rs new file mode 100644 index 000000000..c75595797 --- /dev/null +++ b/gasp-node/pallets/metamask-signature-runtime-api/src/lib.rs @@ -0,0 +1,11 @@ +// Copyright (C) 2021 Mangata team +#![cfg_attr(not(feature = "std"), no_std)] + +use codec::alloc::string::String; +use sp_std::vec::Vec; + +sp_api::decl_runtime_apis! { + pub trait MetamaskSignatureRuntimeApi{ + fn get_eip712_sign_data( call: Vec) -> String; + } +} diff --git a/gasp-node/pallets/metamask-signature/Cargo.toml b/gasp-node/pallets/metamask-signature/Cargo.toml new file mode 100644 index 000000000..761dfe76f --- /dev/null +++ b/gasp-node/pallets/metamask-signature/Cargo.toml @@ -0,0 +1,42 @@ +[package] +authors = ["Mangata team"] +edition = "2018" +name = "pallet-metamask-signature" +version = "0.1.0" + +[package.metadata.docs.rs] +targets = ["x86_64-unknown-linux-gnu"] + +[dependencies] +codec = { workspace = true, default-features = false, features = ["derive"] } +log = { workspace = true, default-features = false } +serde = { workspace = true, optional = true, features = ["derive"] } +serde_json = { workspace = true, default-features = false } +scale-info = { workspace = true, default-features = false, features = ["derive"] } +sp-std = { workspace = true, default-features = false } +sp-core = { workspace = true, default-features = false } +sp-runtime = { workspace = true, default-features = false } +frame-benchmarking = { workspace = true, default-features = false } +frame-system = { workspace = true, default-features = false } +frame-support = { workspace = true, default-features = false } +alloy-sol-types = { workspace = true, default-features = false } + +[dev-dependencies] + +[features] +default = ["std"] +std = [ + "serde", + "serde_json/std", + "codec/std", + "frame-benchmarking/std", + "frame-support/std", + "frame-system/std", + "scale-info/std", + "sp-std/std", + "sp-core/std", + "sp-runtime/std", + "alloy-sol-types/std", +] +runtime-benchmarks = ["frame-benchmarking/runtime-benchmarks"] +try-runtime = ["frame-support/try-runtime"] diff --git a/gasp-node/pallets/metamask-signature/README.md b/gasp-node/pallets/metamask-signature/README.md new file mode 100644 index 000000000..d0d59537c --- /dev/null +++ b/gasp-node/pallets/metamask-signature/README.md @@ -0,0 +1 @@ +License: MIT-0 \ No newline at end of file diff --git a/gasp-node/pallets/metamask-signature/src/lib.rs b/gasp-node/pallets/metamask-signature/src/lib.rs new file mode 100644 index 000000000..1e350a4ce --- /dev/null +++ b/gasp-node/pallets/metamask-signature/src/lib.rs @@ -0,0 +1,229 @@ +#![cfg_attr(not(feature = "std"), no_std)] + +use frame_support::{dispatch::DispatchResult, ensure, pallet_prelude::*, traits::Get, BoundedVec}; +pub use pallet::*; +use sp_std::{ + convert::{TryFrom, TryInto}, + prelude::*, +}; + +#[frame_support::pallet] +pub mod pallet { + use super::*; + use codec::alloc::string::{String, ToString}; + use frame_support::pallet_prelude::*; + use frame_system::pallet_prelude::*; + + #[pallet::pallet] + pub struct Pallet(_); + + #[pallet::config] + pub trait Config: frame_system::Config { + /// Because this pallet emits events, it depends on the runtime's definition of an event. + type RuntimeEvent: From> + IsType<::RuntimeEvent>; + + #[pallet::constant] + type StringLimit: Get; + + #[pallet::constant] + type UrlStringLimit: Get; + } + + #[pallet::storage] + pub type Name = StorageValue<_, BoundedVec, ValueQuery>; + + #[pallet::storage] + pub type Version = StorageValue<_, BoundedVec, ValueQuery>; + + #[pallet::storage] + pub type ChainId = StorageValue<_, u64, ValueQuery>; + + #[pallet::storage] + pub type DecodeUrl = StorageValue<_, BoundedVec, ValueQuery>; + + #[pallet::event] + #[pallet::generate_deposit(pub(super) fn deposit_event)] + pub enum Event { + MetadataUpdated { + name: Option>, + version: Option>, + chain_id: Option, + decode_url: Option>, + }, + } + + #[pallet::error] + pub enum Error { + /// there should be some updates + NothingToUpdate, + } + + #[pallet::call] + impl Pallet { + #[pallet::call_index(0)] + #[pallet::weight(T::DbWeight::get().reads_writes(1, 1).saturating_add(Weight::from_parts(40_000_000, 0)))] + pub fn update( + origin: OriginFor, + name: Option>, + version: Option>, + chain_id: Option, + decode_url: Option>, + ) -> DispatchResult { + ensure_root(origin)?; + + ensure!( + name.is_some() || version.is_some() || chain_id.is_some() || decode_url.is_some(), + Error::::NothingToUpdate + ); + + let mut new_name: Option> = None; + let mut new_version: Option> = None; + let mut new_chain_id: Option = None; + let mut new_decode_url: Option> = None; + + if let Some(v) = name { + new_name = Some(v.clone()); + Name::::put(v); + } + + if let Some(v) = version { + new_version = Some(v.clone()); + Version::::put(v); + } + + if let Some(v) = chain_id.clone() { + new_chain_id = Some(v); + ChainId::::put(v); + } + + if let Some(v) = decode_url { + new_decode_url = Some(v.clone()); + DecodeUrl::::put(v); + } + + >::deposit_event(Event::MetadataUpdated { + name: new_name, + version: new_version, + chain_id: new_chain_id, + decode_url: new_decode_url, + }); + + Ok(()) + } + } + + impl Default for GenesisConfig { + fn default() -> Self { + Self { + name: Default::default(), + version: Default::default(), + chain_id: Default::default(), + decode_url: Default::default(), + _phantom: Default::default(), + } + } + } + + #[pallet::genesis_config] + pub struct GenesisConfig { + pub name: String, + pub version: String, + pub chain_id: u64, + pub decode_url: String, + pub _phantom: PhantomData, + } + + #[pallet::genesis_build] + impl BuildGenesisConfig for GenesisConfig { + fn build(&self) { + Name::::put( + TryInto::>::try_into(self.name.clone().into_bytes()) + .expect("name is required"), + ); + Version::::put( + TryInto::>::try_into( + self.version.clone().into_bytes(), + ) + .expect("version is required"), + ); + ChainId::::put(self.chain_id); + DecodeUrl::::put( + TryInto::>::try_into( + self.decode_url.clone().into_bytes(), + ) + .expect("decode url is required"), + ); + } + } + + impl Pallet { + pub fn get_decode_url() -> Option { + String::from_utf8(DecodeUrl::::get().into_inner()).ok() + } + + pub fn get_eip_metadata() -> Option { + use codec::alloc::string::String; + let r: sp_runtime::generic::Eip712Domain = sp_runtime::generic::eip712_domain! { + name: String::from_utf8(Name::::get().into_inner()).ok()?, + version: String::from_utf8(Version::::get().into_inner()).ok()?, + chain_id: ChainId::::get(), + }; + Some(r) + } + + pub fn eip712_payload(call: String) -> String { + let input = r#"{ + "types": { + "EIP712Domain": [ + { + "name": "name", + "type": "string" + }, + { + "name": "version", + "type": "string" + }, + { + "name": "chainId", + "type": "uint256" + } + ], + "Message": [ + { + "name": "call", + "type": "string" + }, + { + "name": "tx", + "type": "string" + } + ] + }, + "primaryType": "Message", + "domain": { + "name": "", + "version": "", + "chainId": "" + }, + "message": { + "call": "", + "tx": "" + } + }"#; + + if let Ok(ref mut v) = serde_json::from_str::(input) { + v["domain"]["name"] = serde_json::Value::String( + String::from_utf8(Name::::get().into_inner()).unwrap_or_default(), + ); + v["domain"]["chainId"] = serde_json::Value::Number(ChainId::::get().into()); + v["domain"]["version"] = serde_json::Value::String( + String::from_utf8(Version::::get().into_inner()).unwrap_or_default(), + ); + v["message"]["call"] = serde_json::Value::String(call); + serde_json::to_string_pretty(v).unwrap_or_default() + } else { + Default::default() + } + } + } +} diff --git a/gasp-node/pallets/metamask-signature/src/weights.rs b/gasp-node/pallets/metamask-signature/src/weights.rs new file mode 100644 index 000000000..7c42936e0 --- /dev/null +++ b/gasp-node/pallets/metamask-signature/src/weights.rs @@ -0,0 +1,90 @@ + +//! Autogenerated weights for pallet_template +//! +//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev +//! DATE: 2023-04-06, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! WORST CASE MAP SIZE: `1000000` +//! HOSTNAME: `Alexs-MacBook-Pro-2.local`, CPU: `` +//! EXECUTION: Some(Wasm), WASM-EXECUTION: Compiled, CHAIN: Some("dev"), DB CACHE: 1024 + +// Executed Command: +// ../../target/release/node-template +// benchmark +// pallet +// --chain +// dev +// --pallet +// pallet_template +// --extrinsic +// * +// --steps=50 +// --repeat=20 +// --wasm-execution=compiled +// --output +// pallets/template/src/weights.rs +// --template +// ../../.maintain/frame-weight-template.hbs + +#![cfg_attr(rustfmt, rustfmt_skip)] +#![allow(unused_parens)] +#![allow(unused_imports)] + +use frame_support::{traits::Get, weights::{Weight, constants::RocksDbWeight}}; +use core::marker::PhantomData; + +/// Weight functions needed for pallet_template. +pub trait WeightInfo { + fn do_something() -> Weight; + fn cause_error() -> Weight; +} + +/// Weights for pallet_template using the Substrate node and recommended hardware. +pub struct SubstrateWeight(PhantomData); +impl WeightInfo for SubstrateWeight { + /// Storage: TemplateModule Something (r:0 w:1) + /// Proof: TemplateModule Something (max_values: Some(1), max_size: Some(4), added: 499, mode: MaxEncodedLen) + fn do_something() -> Weight { + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `0` + // Minimum execution time: 8_000_000 picoseconds. + Weight::from_parts(9_000_000, 0) + .saturating_add(T::DbWeight::get().writes(1_u64)) + } + /// Storage: TemplateModule Something (r:1 w:1) + /// Proof: TemplateModule Something (max_values: Some(1), max_size: Some(4), added: 499, mode: MaxEncodedLen) + fn cause_error() -> Weight { + // Proof Size summary in bytes: + // Measured: `32` + // Estimated: `1489` + // Minimum execution time: 6_000_000 picoseconds. + Weight::from_parts(6_000_000, 1489) + .saturating_add(T::DbWeight::get().reads(1_u64)) + .saturating_add(T::DbWeight::get().writes(1_u64)) + } +} + +// For backwards compatibility and tests +impl WeightInfo for () { + /// Storage: TemplateModule Something (r:0 w:1) + /// Proof: TemplateModule Something (max_values: Some(1), max_size: Some(4), added: 499, mode: MaxEncodedLen) + fn do_something() -> Weight { + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `0` + // Minimum execution time: 8_000_000 picoseconds. + Weight::from_parts(9_000_000, 0) + .saturating_add(RocksDbWeight::get().writes(1_u64)) + } + /// Storage: TemplateModule Something (r:1 w:1) + /// Proof: TemplateModule Something (max_values: Some(1), max_size: Some(4), added: 499, mode: MaxEncodedLen) + fn cause_error() -> Weight { + // Proof Size summary in bytes: + // Measured: `32` + // Estimated: `1489` + // Minimum execution time: 6_000_000 picoseconds. + Weight::from_parts(6_000_000, 1489) + .saturating_add(RocksDbWeight::get().reads(1_u64)) + .saturating_add(RocksDbWeight::get().writes(1_u64)) + } +} diff --git a/gasp-node/pallets/multipurpose-liquidity/Cargo.toml b/gasp-node/pallets/multipurpose-liquidity/Cargo.toml new file mode 100644 index 000000000..fbeacd07e --- /dev/null +++ b/gasp-node/pallets/multipurpose-liquidity/Cargo.toml @@ -0,0 +1,74 @@ +[package] +authors = ["Mangata team"] +edition = "2018" +name = "pallet-multipurpose-liquidity" +version = "0.1.0" + +[package.metadata.docs.rs] +targets = ["x86_64-unknown-linux-gnu"] + +[dependencies] +codec = { workspace = true, default-features = false } +hex = { workspace = true, default-features = false } +hex-literal = { workspace = true, default-features = false } +log = { workspace = true, default-features = false } +serde = { workspace = true, optional = true } +scale-info = { workspace = true, default-features = false, features = ["derive"] } + +parachain-staking = { path = "../parachain-staking", default-features = false } + +frame-benchmarking = { workspace = true, default-features = false } +frame-executive = { workspace = true, default-features = false } +frame-support = { workspace = true, default-features = false } +frame-system = { workspace = true, default-features = false } +frame-try-runtime = { workspace = true, default-features = false, optional = true } +mangata-support = { workspace = true, default-features = false } +mangata-types = { workspace = true, default-features = false } +pallet-vesting-mangata = { workspace = true, default-features = false } +sp-core = { workspace = true, default-features = false } +sp-runtime = { workspace = true, default-features = false } +sp-std = { workspace = true, default-features = false } + +orml-tokens = { workspace = true, default-features = false } + + +[dev-dependencies] +env_logger.workspace = true +lazy_static.workspace = true +serial_test.workspace = true + +sp-io = { workspace = true, default-features = false } + +orml-traits = { workspace = true, default-features = false } + +[features] +default = ["std"] +enable-trading = [] +std = [ + "codec/std", + "hex/std", + "frame-benchmarking/std", + "frame-support/std", + "frame-system/std", + "mangata-support/std", + "orml-tokens/std", + "pallet-vesting-mangata/std", + "parachain-staking/std", + "serde", + "sp-std/std", + "sp-core/std", + "sp-runtime/std", +] + +runtime-benchmarks = ["frame-benchmarking/runtime-benchmarks", "parachain-staking/runtime-benchmarks"] + +try-runtime = [ + "frame-executive/try-runtime", + "frame-support/try-runtime", + "frame-system/try-runtime", + "frame-try-runtime", + "orml-tokens/try-runtime", + "pallet-vesting-mangata/try-runtime", + "parachain-staking/try-runtime", + "sp-runtime/try-runtime", +] diff --git a/gasp-node/pallets/multipurpose-liquidity/src/benchmarking.rs b/gasp-node/pallets/multipurpose-liquidity/src/benchmarking.rs new file mode 100644 index 000000000..076b7a3f8 --- /dev/null +++ b/gasp-node/pallets/multipurpose-liquidity/src/benchmarking.rs @@ -0,0 +1,111 @@ +// This file is part of Substrate. + +// Copyright (C) 2020-2022 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. + +//! Balances pallet benchmarking. + +#![cfg(feature = "runtime-benchmarks")] + +use super::*; + +use frame_benchmarking::{benchmarks, whitelisted_caller}; +use frame_support::assert_ok; +use frame_system::RawOrigin; +use orml_tokens::MultiTokenCurrencyExtended; + +use crate::Pallet as MultiPurposeLiquidity; + +benchmarks! { + + reserve_vesting_liquidity_tokens{ + let caller: T::AccountId = whitelisted_caller(); + let initial_amount: BalanceOf = 2_000_000__u32.into(); + let asset_id_1 = ::Tokens::create(&caller, initial_amount).unwrap(); + let asset_id_2 = ::Tokens::create(&caller, initial_amount).unwrap(); + let asset_id = asset_id_2 + 1_u32.into(); + + ::Xyk::create_pool(caller.clone(), asset_id_1, initial_amount, asset_id_2, initial_amount).unwrap(); + + let locked_amount: BalanceOf = 500_000__u32.into(); + let lock_ending_block_as_balance: BalanceOf = 1_000__u32.into(); + + let reserve_amount: BalanceOf = 200_000__u32.into(); + + // Assuming max locks is 50 + // Let's add 49 dummy ones for worst case + + let n = 49; + let dummy_lock_amount: BalanceOf = 1000u32.into(); + let dummy_end_block: BalanceOf = 10_u32.into(); + + for _ in 0..n{ + ::VestingProvider::lock_tokens(&caller, asset_id, dummy_lock_amount, None, dummy_end_block).unwrap(); + } + ::VestingProvider::lock_tokens(&caller, asset_id, locked_amount, None, lock_ending_block_as_balance).unwrap(); + let now: BlockNumberFor = >::block_number(); + + }: {assert_ok!(MultiPurposeLiquidity::::reserve_vesting_liquidity_tokens(RawOrigin::Signed(caller.clone().into()).into(), asset_id, reserve_amount));} + verify{ + assert_eq!(::Tokens::locked_balance(asset_id, &caller), 343600_u32.into()); + assert_eq!(::Tokens::reserved_balance(asset_id, &caller), 200000_u32.into()); + assert_eq!(MultiPurposeLiquidity::::get_reserve_status(caller.clone(), asset_id).relock_amount, reserve_amount); + assert_eq!(MultiPurposeLiquidity::::get_relock_status(caller, asset_id)[0], RelockStatusInfo{amount: reserve_amount, starting_block: now + 1_u32.into(), ending_block_as_balance: lock_ending_block_as_balance}); + } + + unreserve_and_relock_instance{ + let caller: T::AccountId = whitelisted_caller(); + let initial_amount: BalanceOf = 2_000_000__u32.into(); + let asset_id_1 = ::Tokens::create(&caller, initial_amount).unwrap(); + let asset_id_2 = ::Tokens::create(&caller, initial_amount).unwrap(); + let asset_id = asset_id_2 + 1_u32.into(); + + ::Xyk::create_pool(caller.clone(), asset_id_1, initial_amount, asset_id_2, initial_amount).unwrap(); + + let locked_amount: BalanceOf = 500_000__u32.into(); + let lock_ending_block_as_balance: BalanceOf = 1_000__u32.into(); + + let reserve_amount: BalanceOf = 200_000__u32.into(); + + // Assuming max locks is 50 + // Let's add 48 dummy ones for worst case + + let n = 48; + let dummy_lock_amount: BalanceOf = 1000u32.into(); + let dummy_end_block: BalanceOf = 10_u32.into(); + + for _ in 0..n{ + ::VestingProvider::lock_tokens(&caller, asset_id, dummy_lock_amount, None, dummy_end_block).unwrap(); + } + ::VestingProvider::lock_tokens(&caller, asset_id, locked_amount, None, lock_ending_block_as_balance).unwrap(); + + let now: BlockNumberFor = >::block_number(); + + MultiPurposeLiquidity::::reserve_vesting_liquidity_tokens(RawOrigin::Signed(caller.clone().into()).into(), asset_id, reserve_amount).unwrap(); + assert_eq!(::Tokens::locked_balance(asset_id, &caller), 348000_u32.into()); + assert_eq!(::Tokens::reserved_balance(asset_id, &caller), 200000_u32.into()); + assert_eq!(MultiPurposeLiquidity::::get_reserve_status(caller.clone(), asset_id).relock_amount, reserve_amount); + assert_eq!(MultiPurposeLiquidity::::get_relock_status(caller.clone(), asset_id)[0], RelockStatusInfo{amount: reserve_amount, starting_block: now, ending_block_as_balance: lock_ending_block_as_balance}); + + }: {assert_ok!(MultiPurposeLiquidity::::unreserve_and_relock_instance(RawOrigin::Signed(caller.clone().into()).into(), asset_id, 0u32));} + verify{ + assert_eq!(::Tokens::locked_balance(asset_id, &caller), 542700_u32.into()); + assert_eq!(::Tokens::reserved_balance(asset_id, &caller), 0_u32.into()); + assert_eq!(MultiPurposeLiquidity::::get_reserve_status(caller.clone(), asset_id).relock_amount, 0_u32.into()); + assert_eq!(MultiPurposeLiquidity::::get_relock_status(caller, asset_id), vec![]); + } + + // impl_benchmark_test_suite!(MultiPurposeLiquidity, crate::mock::new_test_ext(), crate::mock::Test) +} diff --git a/gasp-node/pallets/multipurpose-liquidity/src/lib.rs b/gasp-node/pallets/multipurpose-liquidity/src/lib.rs new file mode 100644 index 000000000..a288c42b7 --- /dev/null +++ b/gasp-node/pallets/multipurpose-liquidity/src/lib.rs @@ -0,0 +1,699 @@ +#![cfg_attr(not(feature = "std"), no_std)] + +use frame_support::{ + dispatch::DispatchResult, + ensure, + pallet_prelude::*, + traits::{ + tokens::currency::{MultiTokenCurrency, MultiTokenVestingLocks}, + Get, StorageVersion, WithdrawReasons, + }, + transactional, +}; +use frame_system::{ensure_signed, pallet_prelude::*}; +use mangata_support::traits::{ + ActivationReservesProviderTrait, StakingReservesProviderTrait, XykFunctionsTrait, +}; +use mangata_types::multipurpose_liquidity::{ActivateKind, BondKind}; +use orml_tokens::{MultiTokenCurrencyExtended, MultiTokenReservableCurrency}; +use sp_runtime::traits::{Bounded, CheckedAdd, CheckedSub, Saturating, Zero}; +use sp_std::{convert::TryInto, prelude::*}; + +#[cfg(feature = "std")] +use serde::{Deserialize, Serialize}; + +#[cfg(test)] +mod mock; +#[cfg(test)] +mod tests; + +mod benchmarking; + +pub mod weights; +pub use weights::WeightInfo; + +pub(crate) const LOG_TARGET: &'static str = "mpl"; + +// syntactic sugar for logging. +#[macro_export] +macro_rules! log { + ($level:tt, $patter:expr $(, $values:expr)* $(,)?) => { + log::$level!( + target: crate::LOG_TARGET, + concat!("[{:?}] 💸 ", $patter), >::block_number() $(, $values)* + ) + }; +} + +pub use pallet::*; + +type BalanceOf = + <::Tokens as MultiTokenCurrency<::AccountId>>::Balance; + +type CurrencyIdOf = <::Tokens as MultiTokenCurrency< + ::AccountId, +>>::CurrencyId; + +#[frame_support::pallet] +pub mod pallet { + + use super::*; + + const STORAGE_VERSION: StorageVersion = StorageVersion::new(1); + + #[pallet::pallet] + #[pallet::storage_version(STORAGE_VERSION)] + pub struct Pallet(PhantomData); + + #[pallet::hooks] + impl Hooks> for Pallet {} + + #[pallet::config] + pub trait Config: frame_system::Config { + type RuntimeEvent: From> + IsType<::RuntimeEvent>; + type MaxRelocks: Get; + type Tokens: MultiTokenCurrencyExtended + + MultiTokenReservableCurrency; + type NativeCurrencyId: Get>; + type VestingProvider: MultiTokenVestingLocks< + Self::AccountId, + Currency = Self::Tokens, + Moment = BlockNumberFor, + >; + type Xyk: XykFunctionsTrait, CurrencyIdOf>; + type WeightInfo: WeightInfo; + } + + #[pallet::error] + /// Errors + pub enum Error { + /// The token is not a liquidity token + NotALiquidityToken, + /// The limit on the maximum number of relocks was exceeded + RelockCountLimitExceeded, + /// Provided index for relock is out of bounds + RelockInstanceIndexOOB, + /// Not enough unspend reserves + NotEnoughUnspentReserves, + /// Not enough tokens + NotEnoughTokens, + /// Math error + MathError, + } + + #[pallet::event] + #[pallet::generate_deposit(pub(super) fn deposit_event)] + pub enum Event { + VestingTokensReserved(T::AccountId, CurrencyIdOf, BalanceOf), + TokensRelockedFromReserve(T::AccountId, CurrencyIdOf, BalanceOf, BalanceOf), + } + + #[cfg_attr(feature = "std", derive(Serialize, Deserialize))] + #[derive( + Eq, PartialEq, Clone, Encode, Decode, RuntimeDebug, MaxEncodedLen, TypeInfo, Default, + )] + pub struct ReserveStatusInfo { + pub staked_unactivated_reserves: Balance, + pub activated_unstaked_reserves: Balance, + pub staked_and_activated_reserves: Balance, + pub unspent_reserves: Balance, + pub relock_amount: Balance, + } + + #[cfg_attr(feature = "std", derive(Serialize, Deserialize))] + #[derive( + Eq, PartialEq, Clone, Encode, Decode, RuntimeDebug, MaxEncodedLen, TypeInfo, Default, + )] + pub struct RelockStatusInfo { + pub amount: Balance, + pub starting_block: BlockNumber, + pub ending_block_as_balance: Balance, + } + + #[pallet::storage] + #[pallet::getter(fn get_reserve_status)] + pub type ReserveStatus = StorageDoubleMap< + _, + Blake2_128Concat, + T::AccountId, + Twox64Concat, + CurrencyIdOf, + ReserveStatusInfo>, + ValueQuery, + >; + + #[pallet::storage] + #[pallet::getter(fn get_relock_status)] + pub type RelockStatus = StorageDoubleMap< + _, + Blake2_128Concat, + T::AccountId, + Twox64Concat, + CurrencyIdOf, + BoundedVec, BlockNumberFor>, T::MaxRelocks>, + ValueQuery, + >; + + // MPL extrinsics. + #[pallet::call] + impl Pallet { + #[transactional] + #[pallet::call_index(0)] + #[pallet::weight(T::WeightInfo::reserve_vesting_liquidity_tokens())] + /// Migrates vested liquidity tokens from Vested pallet to MPL. Information about + /// unlock schedule is preserved, so whenever one decides to move tokens back to + /// Vested pallet tokens can be unlocked. + pub fn reserve_vesting_liquidity_tokens_by_vesting_index( + origin: OriginFor, + liquidity_token_id: CurrencyIdOf, + liquidity_token_vesting_index: u32, + liquidity_token_unlock_some_amount_or_all: Option>, + ) -> DispatchResultWithPostInfo { + let sender = ensure_signed(origin)?; + + ensure!(T::Xyk::is_liquidity_token(liquidity_token_id), Error::::NotALiquidityToken); + Self::do_reserve_tokens_by_vesting_index( + sender, + liquidity_token_id, + liquidity_token_vesting_index, + liquidity_token_unlock_some_amount_or_all, + ) + } + + #[transactional] + #[pallet::call_index(1)] + #[pallet::weight(T::WeightInfo::reserve_vesting_liquidity_tokens())] + /// Migrates vested MGX from Vested pallet to MPL. Information about unlock schedule is + /// preserved, so whenever one decides to move tokens back to Vested pallet tokens can be + /// unlocked. + pub fn reserve_vesting_native_tokens_by_vesting_index( + origin: OriginFor, + liquidity_token_vesting_index: u32, + liquidity_token_unlock_some_amount_or_all: Option>, + ) -> DispatchResultWithPostInfo { + let sender = ensure_signed(origin)?; + + Self::do_reserve_tokens_by_vesting_index( + sender, + T::NativeCurrencyId::get(), + liquidity_token_vesting_index, + liquidity_token_unlock_some_amount_or_all, + ) + } + + #[transactional] + #[pallet::call_index(2)] + #[pallet::weight(T::WeightInfo::reserve_vesting_liquidity_tokens())] + // This extrinsic has to be transactional + pub fn reserve_vesting_liquidity_tokens( + origin: OriginFor, + liquidity_token_id: CurrencyIdOf, + liquidity_token_amount: BalanceOf, + ) -> DispatchResultWithPostInfo { + let sender = ensure_signed(origin)?; + + ensure!(T::Xyk::is_liquidity_token(liquidity_token_id), Error::::NotALiquidityToken); + + let (vesting_starting_block, vesting_ending_block_as_balance): ( + BlockNumberFor, + BalanceOf, + ) = T::VestingProvider::unlock_tokens(&sender, liquidity_token_id, liquidity_token_amount) + .map(|x| (x.0, x.1))?; + + let mut reserve_status = Pallet::::get_reserve_status(&sender, liquidity_token_id); + + reserve_status.relock_amount = reserve_status + .relock_amount + .checked_add(&liquidity_token_amount) + .ok_or(Error::::MathError)?; + reserve_status.unspent_reserves = reserve_status + .unspent_reserves + .checked_add(&liquidity_token_amount) + .ok_or(Error::::MathError)?; + + ReserveStatus::::insert(&sender, liquidity_token_id, reserve_status); + + RelockStatus::::try_append( + &sender, + liquidity_token_id, + RelockStatusInfo { + amount: liquidity_token_amount, + starting_block: vesting_starting_block, + ending_block_as_balance: vesting_ending_block_as_balance, + }, + ) + .map_err(|_| Error::::RelockCountLimitExceeded)?; + + T::Tokens::reserve(liquidity_token_id.into(), &sender, liquidity_token_amount)?; + + Pallet::::deposit_event(Event::VestingTokensReserved( + sender, + liquidity_token_id, + liquidity_token_amount, + )); + + Ok(().into()) + } + + #[transactional] + #[pallet::call_index(3)] + #[pallet::weight(T::WeightInfo::unreserve_and_relock_instance())] + // This extrinsic has to be transactional + pub fn unreserve_and_relock_instance( + origin: OriginFor, + liquidity_token_id: CurrencyIdOf, + relock_instance_index: u32, + ) -> DispatchResultWithPostInfo { + let sender = ensure_signed(origin)?; + + let relock_instances: Vec, BlockNumberFor>> = + Self::get_relock_status(&sender, liquidity_token_id).into(); + + let selected_relock_instance: RelockStatusInfo, BlockNumberFor> = + relock_instances + .get(relock_instance_index as usize) + .ok_or(Error::::RelockInstanceIndexOOB)? + .clone(); + + let updated_relock_instances: BoundedVec< + RelockStatusInfo, BlockNumberFor>, + T::MaxRelocks, + > = relock_instances + .into_iter() + .enumerate() + .filter_map(move |(index, relock_instance)| { + if index == relock_instance_index as usize { + None + } else { + Some(relock_instance) + } + }) + .collect::>() + .try_into() + .map_err(|_| Error::::RelockCountLimitExceeded)?; + + let mut reserve_status = Pallet::::get_reserve_status(&sender, liquidity_token_id); + + reserve_status.relock_amount = reserve_status + .relock_amount + .checked_sub(&selected_relock_instance.amount) + .ok_or(Error::::MathError)?; + reserve_status.unspent_reserves = reserve_status + .unspent_reserves + .checked_sub(&selected_relock_instance.amount) + .ok_or(Error::::NotEnoughUnspentReserves)?; + + ensure!( + T::Tokens::unreserve( + liquidity_token_id.into(), + &sender, + selected_relock_instance.amount + ) + .is_zero(), + Error::::MathError + ); + + T::VestingProvider::lock_tokens( + &sender, + liquidity_token_id.into(), + selected_relock_instance.amount, + Some(selected_relock_instance.starting_block), + selected_relock_instance.ending_block_as_balance, + )?; + + ReserveStatus::::insert(&sender, liquidity_token_id, reserve_status); + + RelockStatus::::insert(&sender, liquidity_token_id, updated_relock_instances); + + Pallet::::deposit_event(Event::TokensRelockedFromReserve( + sender, + liquidity_token_id, + selected_relock_instance.amount, + selected_relock_instance.ending_block_as_balance, + )); + + Ok(().into()) + } + } +} + +impl StakingReservesProviderTrait, CurrencyIdOf> + for Pallet +{ + fn can_bond( + token_id: CurrencyIdOf, + account_id: &T::AccountId, + amount: BalanceOf, + use_balance_from: Option, + ) -> bool { + let reserve_status = Pallet::::get_reserve_status(account_id, token_id); + + let use_balance_from = use_balance_from.unwrap_or(BondKind::AvailableBalance); + + match use_balance_from { + BondKind::AvailableBalance => + T::Tokens::ensure_can_withdraw( + token_id.into(), + &account_id, + amount, + WithdrawReasons::all(), + Default::default(), + ) + .is_ok() && reserve_status + .staked_unactivated_reserves + .checked_add(&amount) + .is_some(), + BondKind::ActivatedUnstakedReserves => + reserve_status.activated_unstaked_reserves.checked_sub(&amount).is_some() && + reserve_status.staked_and_activated_reserves.checked_add(&amount).is_some(), + BondKind::UnspentReserves => + reserve_status.unspent_reserves.checked_sub(&amount).is_some() && + reserve_status.staked_unactivated_reserves.checked_add(&amount).is_some(), + } + } + + fn bond( + token_id: CurrencyIdOf, + account_id: &T::AccountId, + amount: BalanceOf, + use_balance_from: Option, + ) -> DispatchResult { + let mut reserve_status = Pallet::::get_reserve_status(account_id, token_id); + + let use_balance_from = use_balance_from.unwrap_or(BondKind::AvailableBalance); + + match use_balance_from { + BondKind::AvailableBalance => { + reserve_status.staked_unactivated_reserves = reserve_status + .staked_unactivated_reserves + .checked_add(&amount) + .ok_or(Error::::MathError)?; + T::Tokens::reserve(token_id.into(), &account_id, amount)?; + }, + BondKind::ActivatedUnstakedReserves => { + reserve_status.activated_unstaked_reserves = reserve_status + .activated_unstaked_reserves + .checked_sub(&amount) + .ok_or(Error::::NotEnoughTokens)?; + reserve_status.staked_and_activated_reserves = reserve_status + .staked_and_activated_reserves + .checked_add(&amount) + .ok_or(Error::::MathError)?; + }, + BondKind::UnspentReserves => { + reserve_status.unspent_reserves = reserve_status + .unspent_reserves + .checked_sub(&amount) + .ok_or(Error::::NotEnoughTokens)?; + reserve_status.staked_unactivated_reserves = reserve_status + .staked_unactivated_reserves + .checked_add(&amount) + .ok_or(Error::::MathError)?; + }, + } + + ReserveStatus::::insert(account_id, token_id, reserve_status); + Ok(()) + } + + fn unbond( + token_id: CurrencyIdOf, + account_id: &T::AccountId, + amount: BalanceOf, + ) -> BalanceOf { + // From staked_unactivated_reserves goes to either free balance or unspent reserves depending on relock_amount + + // From staked_and_activated_reserves goes to activated always. + + let mut reserve_status = Pallet::::get_reserve_status(account_id, token_id); + let mut working_amount = amount; + let mut unreserve_amount = working_amount.min(reserve_status.staked_unactivated_reserves); + + working_amount = working_amount.saturating_sub(unreserve_amount); + reserve_status.staked_unactivated_reserves = + reserve_status.staked_unactivated_reserves.saturating_sub(unreserve_amount); + + let mut move_reserve = working_amount.min(reserve_status.staked_and_activated_reserves); + // This is just to prevent overflow. + move_reserve = BalanceOf::::max_value() + .saturating_sub(reserve_status.activated_unstaked_reserves) + .min(move_reserve); + reserve_status.staked_and_activated_reserves = + reserve_status.staked_and_activated_reserves.saturating_sub(move_reserve); + reserve_status.activated_unstaked_reserves = + reserve_status.activated_unstaked_reserves.saturating_add(move_reserve); + working_amount = working_amount.saturating_sub(move_reserve); + + // Now we will attempt to unreserve the amount on the basis of the relock_amount + let total_remaining_reserve = reserve_status + .staked_unactivated_reserves + .saturating_add(reserve_status.activated_unstaked_reserves) + .saturating_add(reserve_status.staked_and_activated_reserves) + .saturating_add(reserve_status.unspent_reserves); + + let mut add_to_unspent = + reserve_status.relock_amount.saturating_sub(total_remaining_reserve); + if add_to_unspent > unreserve_amount { + log::warn!( + "Unbond witnessed prior state of relock_amount being higher than mpl reserves {:?} {:?}", + add_to_unspent, + unreserve_amount + ); + } + add_to_unspent = add_to_unspent.min(unreserve_amount); + unreserve_amount = unreserve_amount.saturating_sub(add_to_unspent); + reserve_status.unspent_reserves = + reserve_status.unspent_reserves.saturating_add(add_to_unspent); + + let unreserve_result: BalanceOf = + T::Tokens::unreserve(token_id.into(), account_id, unreserve_amount); + + if !unreserve_result.is_zero() { + log::warn!("Unbond resulted in non-zero unreserve_result {:?}", unreserve_result); + } + + if !working_amount.is_zero() { + log::warn!("Unbond resulted in left-over amount {:?}", working_amount); + } + + ReserveStatus::::insert(account_id, token_id, reserve_status); + working_amount.saturating_add(unreserve_result) + } +} + +impl ActivationReservesProviderTrait, CurrencyIdOf> + for Pallet +{ + fn get_max_instant_unreserve_amount( + token_id: CurrencyIdOf, + account_id: &T::AccountId, + ) -> BalanceOf { + let reserve_status = Pallet::::get_reserve_status(account_id, token_id); + + let total_remaining_reserve = reserve_status + .staked_unactivated_reserves + .saturating_add(reserve_status.staked_and_activated_reserves) + .saturating_add(reserve_status.unspent_reserves); + + let amount_held_back_by_relock = + reserve_status.relock_amount.saturating_sub(total_remaining_reserve); + + // We assume here that the actual unreserve will ofcoures go fine returning 0. + reserve_status + .activated_unstaked_reserves + .saturating_sub(amount_held_back_by_relock) + } + + fn can_activate( + token_id: CurrencyIdOf, + account_id: &T::AccountId, + amount: BalanceOf, + use_balance_from: Option, + ) -> bool { + let reserve_status = Pallet::::get_reserve_status(account_id, token_id); + + let use_balance_from = use_balance_from.unwrap_or(ActivateKind::AvailableBalance); + + match use_balance_from { + ActivateKind::AvailableBalance => + T::Tokens::ensure_can_withdraw( + token_id.into(), + &account_id, + amount, + WithdrawReasons::all(), + Default::default(), + ) + .is_ok() && reserve_status + .activated_unstaked_reserves + .checked_add(&amount) + .is_some(), + ActivateKind::StakedUnactivatedReserves => + reserve_status.staked_unactivated_reserves.checked_sub(&amount).is_some() && + reserve_status.staked_and_activated_reserves.checked_add(&amount).is_some(), + ActivateKind::UnspentReserves => + reserve_status.unspent_reserves.checked_sub(&amount).is_some() && + reserve_status.activated_unstaked_reserves.checked_add(&amount).is_some(), + } + } + + fn activate( + token_id: CurrencyIdOf, + account_id: &T::AccountId, + amount: BalanceOf, + use_balance_from: Option, + ) -> DispatchResult { + let mut reserve_status = Pallet::::get_reserve_status(account_id, token_id); + + let use_balance_from = use_balance_from.unwrap_or(ActivateKind::AvailableBalance); + + match use_balance_from { + ActivateKind::AvailableBalance => { + reserve_status.activated_unstaked_reserves = reserve_status + .activated_unstaked_reserves + .checked_add(&amount) + .ok_or(Error::::MathError)?; + T::Tokens::reserve(token_id.into(), &account_id, amount)?; + }, + ActivateKind::StakedUnactivatedReserves => { + reserve_status.staked_unactivated_reserves = reserve_status + .staked_unactivated_reserves + .checked_sub(&amount) + .ok_or(Error::::NotEnoughTokens)?; + reserve_status.staked_and_activated_reserves = reserve_status + .staked_and_activated_reserves + .checked_add(&amount) + .ok_or(Error::::MathError)?; + }, + ActivateKind::UnspentReserves => { + reserve_status.unspent_reserves = reserve_status + .unspent_reserves + .checked_sub(&amount) + .ok_or(Error::::NotEnoughTokens)?; + reserve_status.activated_unstaked_reserves = reserve_status + .activated_unstaked_reserves + .checked_add(&amount) + .ok_or(Error::::MathError)?; + }, + } + + ReserveStatus::::insert(account_id, token_id, reserve_status); + Ok(()) + } + + fn deactivate( + token_id: CurrencyIdOf, + account_id: &T::AccountId, + amount: BalanceOf, + ) -> BalanceOf { + // From ActivatedUnstakedReserves goes to either free balance or unspent reserves depending on relock_amount + // From staked_and_activated_reserves goes to staked always. + + let mut reserve_status = Pallet::::get_reserve_status(account_id, token_id); + let mut working_amount = amount; + let mut unreserve_amount = working_amount.min(reserve_status.activated_unstaked_reserves); + + working_amount = working_amount.saturating_sub(unreserve_amount); + reserve_status.activated_unstaked_reserves = + reserve_status.activated_unstaked_reserves.saturating_sub(unreserve_amount); + + let mut move_reserve = working_amount.min(reserve_status.staked_and_activated_reserves); + // This is just to prevent overflow. + move_reserve = BalanceOf::::max_value() + .saturating_sub(reserve_status.staked_unactivated_reserves) + .min(move_reserve); + reserve_status.staked_and_activated_reserves = + reserve_status.staked_and_activated_reserves.saturating_sub(move_reserve); + reserve_status.staked_unactivated_reserves = + reserve_status.staked_unactivated_reserves.saturating_add(move_reserve); + working_amount = working_amount.saturating_sub(move_reserve); + + // Now we will attempt to unreserve the amount on the basis of the relock_amount + let total_remaining_reserve = reserve_status + .staked_unactivated_reserves + .saturating_add(reserve_status.activated_unstaked_reserves) + .saturating_add(reserve_status.staked_and_activated_reserves) + .saturating_add(reserve_status.unspent_reserves); + + let mut add_to_unspent = + reserve_status.relock_amount.saturating_sub(total_remaining_reserve); + if add_to_unspent > unreserve_amount { + log::warn!( + "Unbond witnessed prior state of relock_amount being higher than mpl reserves {:?} {:?}", + add_to_unspent, + unreserve_amount + ); + } + add_to_unspent = add_to_unspent.min(unreserve_amount); + unreserve_amount = unreserve_amount.saturating_sub(add_to_unspent); + reserve_status.unspent_reserves = + reserve_status.unspent_reserves.saturating_add(add_to_unspent); + + let unreserve_result: BalanceOf = + T::Tokens::unreserve(token_id.into(), account_id, unreserve_amount); + + if !unreserve_result.is_zero() { + log::warn!("Unbond resulted in non-zero unreserve_result {:?}", unreserve_result); + } + + if !working_amount.is_zero() { + log::warn!("Unbond resulted in left-over amount {:?}", working_amount); + } + + ReserveStatus::::insert(account_id, token_id, reserve_status); + working_amount.saturating_add(unreserve_result) + } +} + +impl Pallet { + fn do_reserve_tokens_by_vesting_index( + account: T::AccountId, + liquidity_token_id: CurrencyIdOf, + liquidity_token_vesting_index: u32, + liquidity_token_unlock_some_amount_or_all: Option>, + ) -> DispatchResultWithPostInfo { + let (unlocked_amount, vesting_starting_block, vesting_ending_block_as_balance): ( + BalanceOf, + BlockNumberFor, + BalanceOf, + ) = T::VestingProvider::unlock_tokens_by_vesting_index( + &account, + liquidity_token_id, + liquidity_token_vesting_index, + liquidity_token_unlock_some_amount_or_all, + ) + .map(|x| (x.0, x.1, x.2))?; + + let mut reserve_status = Pallet::::get_reserve_status(&account, liquidity_token_id); + + reserve_status.relock_amount = reserve_status + .relock_amount + .checked_add(&unlocked_amount) + .ok_or(Error::::MathError)?; + reserve_status.unspent_reserves = reserve_status + .unspent_reserves + .checked_add(&unlocked_amount) + .ok_or(Error::::MathError)?; + + ReserveStatus::::insert(&account, liquidity_token_id, reserve_status); + + RelockStatus::::try_append( + &account, + liquidity_token_id, + RelockStatusInfo { + amount: unlocked_amount, + starting_block: vesting_starting_block, + ending_block_as_balance: vesting_ending_block_as_balance, + }, + ) + .map_err(|_| Error::::RelockCountLimitExceeded)?; + + T::Tokens::reserve(liquidity_token_id.into(), &account, unlocked_amount)?; + + Pallet::::deposit_event(Event::VestingTokensReserved( + account, + liquidity_token_id, + unlocked_amount, + )); + Ok(().into()) + } +} diff --git a/gasp-node/pallets/multipurpose-liquidity/src/migration.rs b/gasp-node/pallets/multipurpose-liquidity/src/migration.rs new file mode 100644 index 000000000..0ce0dc0a4 --- /dev/null +++ b/gasp-node/pallets/multipurpose-liquidity/src/migration.rs @@ -0,0 +1,341 @@ +use super::*; +use frame_support::{ + migration::storage_key_iter, + traits::{Get, GetStorageVersion, PalletInfoAccess, StorageVersion}, + weights::Weight, +}; +use parachain_staking::{CollatorCandidate, Delegator}; +use sp_std::collections::btree_map::BTreeMap; + +#[cfg(feature = "try-runtime")] +use frame_support::traits::OnRuntimeUpgradeHelpersExt; + +#[cfg(feature = "try-runtime")] +pub fn migrate_from_v0_pre_runtime_upgrade( +) -> Result<(), &'static str> { + let on_chain_storage_version =

::on_chain_storage_version(); + if on_chain_storage_version != 0 { + log::info!( + target: "mpl", + "Attempted to apply xyk-staking-mpl consistency pre-migration to mpl but failed because storage version is {:?}, and not 0", + on_chain_storage_version, + ); + return Ok(()) + } + + log::info!( + target: "mpl", + "Running xyk-staking-mpl consistency pre-migration to mpl with storage version {:?}", + on_chain_storage_version, + ); + + Pallet::::set_temp_storage(true, "is_pre_migration"); + + // Check consistency of xyk storage, staking storage and orml reserves + // Ensure reserve and relock status is zero + + // Get all required storage from xyk and staking + + let collator_storage = storage_key_iter::< + T::AccountId, + CollatorCandidate, + Twox64Concat, + >(b"ParachainStaking", b"CandidateState") + .collect::>(); + + let delegator_storage = + storage_key_iter::, Twox64Concat>( + b"ParachainStaking", + b"DelegatorState", + ) + .collect::>(); + + let activation_storage = storage_key_iter::<(T::AccountId, TokenId), Balance, Twox64Concat>( + b"Xyk", + b"LiquidityMiningActiveUser", + ) + .collect::>(); + + // Get all users list + + // (Balance, Balance, Balance) + // represents reserves + // xyk, staking, total + let mut user_reserve_info: BTreeMap<(T::AccountId, TokenId), (Balance, Balance, Balance)> = + BTreeMap::new(); + + for (collator_account, collator_info) in collator_storage.iter() { + if let Some((xyk_reserve, staking_reserve, total_reserve)) = + user_reserve_info.get_mut(&(collator_account.clone(), collator_info.liquidity_token)) + { + *staking_reserve = staking_reserve.saturating_add(collator_info.bond); + *total_reserve = total_reserve.saturating_add(collator_info.bond); + } else { + user_reserve_info.insert( + (collator_account.clone(), collator_info.liquidity_token), + (Balance::zero(), collator_info.bond, collator_info.bond), + ); + }; + } + + for (delegator_account, delegator_info) in delegator_storage.iter() { + for delegation in delegator_info.delegations.0.iter() { + if let Some((xyk_reserve, staking_reserve, total_reserve)) = + user_reserve_info.get_mut(&(delegator_account.clone(), delegation.liquidity_token)) + { + *staking_reserve = staking_reserve.saturating_add(delegation.amount); + *total_reserve = total_reserve.saturating_add(delegation.amount); + } else { + user_reserve_info.insert( + (delegator_account.clone(), delegation.liquidity_token), + (Balance::zero(), delegation.amount, delegation.amount), + ); + }; + } + } + + for ((account, liquidity_token), amount) in activation_storage.iter() { + if let Some((xyk_reserve, staking_reserve, total_reserve)) = + user_reserve_info.get_mut(&(account.clone(), *liquidity_token)) + { + *xyk_reserve = xyk_reserve.saturating_add(*amount); + *total_reserve = total_reserve.saturating_add(*amount); + } else { + user_reserve_info + .insert((account.clone(), *liquidity_token), (*amount, Balance::zero(), *amount)); + }; + } + + for ((account, liquidity_token), (xyk_reserve, staking_reserve, total_reserve)) in + user_reserve_info.iter() + { + assert_eq!(xyk_reserve.saturating_add(*staking_reserve), *total_reserve); + assert_eq!( + T::Tokens::reserved_balance((*liquidity_token).into(), account).into(), + *total_reserve + ); + let reserve_status = Pallet::::get_reserve_status(account, liquidity_token); + assert!(reserve_status.staked_and_activated_reserves.is_zero()); + assert!(reserve_status.activated_unstaked_reserves.is_zero()); + assert!(reserve_status.staked_unactivated_reserves.is_zero()); + assert!(reserve_status.unspent_reserves.is_zero()); + assert!(reserve_status.relock_amount.is_zero()); + assert!(Pallet::::get_relock_status(account, liquidity_token).is_empty()); + } + + Pallet::::set_temp_storage(user_reserve_info, "user_reserve_info"); + + log::info!( + target: "mpl", + "xyk-staking-mpl consistency pre-migration to mpl with storage version {:?} completed successfully", + on_chain_storage_version, + ); + + Ok(()) +} + +/// Migrate the pallet storage to v1. +pub fn migrate_from_v0( +) -> frame_support::weights::Weight { + let on_chain_storage_version =

::on_chain_storage_version(); + + if on_chain_storage_version != 0 { + log::info!( + target: "mpl", + "Attempted to apply xyk-staking-mpl consistency migration to mpl but failed because storage version is {:?}, and not 0", + on_chain_storage_version, + ); + return T::DbWeight::get().reads(1) + } + + log::info!( + target: "mpl", + "Running xyk-staking-mpl consistency migration to mpl with storage version {:?}", + on_chain_storage_version, + ); + + // Apply storage migration from StorageVersion 0 to 1 + + ////////////////////////////////////////////////////// + + let collator_storage = storage_key_iter::< + T::AccountId, + CollatorCandidate, + Twox64Concat, + >(b"ParachainStaking", b"CandidateState") + .collect::>(); + + let delegator_storage = + storage_key_iter::, Twox64Concat>( + b"ParachainStaking", + b"DelegatorState", + ) + .collect::>(); + + let activation_storage = storage_key_iter::<(T::AccountId, TokenId), Balance, Twox64Concat>( + b"Xyk", + b"LiquidityMiningActiveUser", + ) + .collect::>(); + + // Now we have all the info in memory + + let mut collators_processed_count = u32::zero(); + + let mut delegations_processed_count = u32::zero(); + + let mut activation_processed_count = u32::zero(); + + for (collator_account, collator_info) in collator_storage.iter() { + let mut reserve_status = + Pallet::::get_reserve_status(collator_account, collator_info.liquidity_token); + reserve_status.staked_unactivated_reserves = + reserve_status.staked_unactivated_reserves.saturating_add(collator_info.bond); + + log::info!( + target: "mpl", + "Reserve status after processing collator: {:?} on liquidity token: {:?} with amount: {:?} := {:?}", + collator_account, + collator_info.liquidity_token, + collator_info.bond, + reserve_status, + ); + + collators_processed_count = collators_processed_count.saturating_add(1u32); + + ReserveStatus::::insert(collator_account, collator_info.liquidity_token, reserve_status); + } + + for (delegator_account, delegator_info) in delegator_storage.iter() { + for delegation in delegator_info.delegations.0.iter() { + let mut reserve_status = + Pallet::::get_reserve_status(delegator_account, delegation.liquidity_token); + + reserve_status.staked_unactivated_reserves = + reserve_status.staked_unactivated_reserves.saturating_add(delegation.amount); + + log::info!( + target: "mpl", + "Reserve status after processing delegation of delegator: {:?} on liquidity token: {:?} with amount: {:?} := {:?}", + delegator_account, + delegation.liquidity_token, + delegation.amount, + reserve_status, + ); + + delegations_processed_count = delegations_processed_count.saturating_add(1u32); + + ReserveStatus::::insert( + delegator_account, + delegation.liquidity_token, + reserve_status, + ); + } + } + + for ((account, liquidity_token), amount) in activation_storage.iter() { + let mut reserve_status = Pallet::::get_reserve_status(account, liquidity_token); + + reserve_status.activated_unstaked_reserves = + reserve_status.activated_unstaked_reserves.saturating_add(*amount); + + log::info!( + target: "mpl", + "Reserve status after processing activation of account: {:?} on liquidity token: {:?} with amount: {:?} := {:?}", + account, + liquidity_token, + amount, + reserve_status, + ); + + activation_processed_count = activation_processed_count.saturating_add(1u32); + + ReserveStatus::::insert(account, liquidity_token, reserve_status); + } + + StorageVersion::new(1).put::

(); + + log::info!( + target: "mpl", + "xyk-staking-mpl consistency migration to mpl completed successfully, storage version is now {:?}", +

::on_chain_storage_version(), + ); + + let reads_to_collect_info = collator_storage + .len() + .saturating_add(delegator_storage.len()) + .saturating_add(activation_storage.len()); + let reads_to_modify_state = collators_processed_count + .saturating_add(delegations_processed_count) + .saturating_add(activation_processed_count); + let writes_to_modify_state = collators_processed_count + .saturating_add(delegations_processed_count) + .saturating_add(activation_processed_count); + + T::DbWeight::get().reads_writes( + reads_to_collect_info.saturating_add(reads_to_modify_state as usize) as Weight + 1, + writes_to_modify_state as Weight + 1, + ) +} + +#[cfg(feature = "try-runtime")] +pub fn migrate_from_v0_post_runtime_upgrade( +) -> Result<(), &'static str> { + match Pallet::::get_temp_storage("is_pre_migration") { + None | Some(false) => { + log::info!( + target: "mpl", + "Attempted to apply xyk-staking-mpl consistency post-migration to mpl but failed because pre-migration failed to run" + ); + return Ok(()) + }, + Some(true) => {}, + }; + + let on_chain_storage_version =

::on_chain_storage_version(); + + // If on chain version is still 0 then the migration failed + if on_chain_storage_version == 0 { + log::info!( + target: "mpl", + "Attempted to apply xyk-staking-mpl consistency post-migration to mpl but failed because storage version is still 0" + ); + return Ok(()) + } + + log::info!( + target: "mpl", + "Running xyk-staking-mpl consistency post-migration to mpl with storage version {:?}", + on_chain_storage_version, + ); + + // Check consistency of xyk storage, staking storage and orml reserves + + let user_reserve_info: BTreeMap<(T::AccountId, TokenId), (Balance, Balance, Balance)> = + Pallet::::get_temp_storage("user_reserve_info").expect("temp storage was set"); + + for ((account, liquidity_token), (xyk_reserve, staking_reserve, total_reserve)) in + user_reserve_info.iter() + { + assert_eq!(xyk_reserve.saturating_add(*staking_reserve), *total_reserve); + assert_eq!( + T::Tokens::reserved_balance({ *liquidity_token }.into(), account).into(), + *total_reserve + ); + let reserve_status = Pallet::::get_reserve_status(account, liquidity_token); + assert!(reserve_status.staked_and_activated_reserves.is_zero()); + assert_eq!(reserve_status.activated_unstaked_reserves, *xyk_reserve); + assert_eq!(reserve_status.staked_unactivated_reserves, *staking_reserve); + assert!(reserve_status.unspent_reserves.is_zero()); + assert!(reserve_status.relock_amount.is_zero()); + assert!(Pallet::::get_relock_status(account, liquidity_token).is_empty()); + } + + log::info!( + target: "mpl", + "xyk-staking-mpl consistency post-migration to mpl with storage version {:?} completed successfully", + on_chain_storage_version, + ); + + Ok(()) +} diff --git a/gasp-node/pallets/multipurpose-liquidity/src/mock.rs b/gasp-node/pallets/multipurpose-liquidity/src/mock.rs new file mode 100644 index 000000000..654e4a9cc --- /dev/null +++ b/gasp-node/pallets/multipurpose-liquidity/src/mock.rs @@ -0,0 +1,239 @@ +// Copyright (C) 2020 Mangata team + +use super::*; + +use crate as pallet_multipurpose_liquidity; +use frame_support::{ + construct_runtime, derive_impl, parameter_types, + traits::{Contains, Nothing}, + PalletId, +}; +use frame_system as system; +use orml_tokens::MultiTokenCurrencyAdapter; +use orml_traits::parameter_type_with_key; +use sp_runtime::{traits::AccountIdConversion, BuildStorage, Permill}; +use sp_std::convert::TryFrom; + +pub const NATIVE_CURRENCY_ID: u32 = 0; + +pub(crate) type AccountId = u64; +pub(crate) type Amount = i128; +pub(crate) type Balance = u128; +pub(crate) type TokenId = u32; + +type Block = frame_system::mocking::MockBlock; + +construct_runtime!( + pub enum Test { + System: frame_system, + Tokens: orml_tokens, + Vesting: pallet_vesting_mangata, + MultiPurposeLiquidity: pallet_multipurpose_liquidity, + } +); + +#[derive_impl(frame_system::config_preludes::TestDefaultConfig as frame_system::DefaultConfig)] +impl frame_system::Config for Test { + type Block = Block; +} + +parameter_type_with_key! { + pub ExistentialDeposits: |currency_id: TokenId| -> Balance { + match currency_id { + _ => 0, + } + }; +} + +pub struct DustRemovalWhitelist; +impl Contains for DustRemovalWhitelist { + fn contains(a: &AccountId) -> bool { + *a == TreasuryAccount::get() + } +} + +parameter_types! { + pub TreasuryAccount: AccountId = TreasuryPalletId::get().into_account_truncating(); + pub const MaxLocks: u32 = 50; +} + +impl orml_tokens::Config for Test { + type RuntimeEvent = RuntimeEvent; + type Balance = Balance; + type Amount = Amount; + type CurrencyId = TokenId; + type WeightInfo = (); + type ExistentialDeposits = ExistentialDeposits; + type MaxLocks = MaxLocks; + type DustRemovalWhitelist = DustRemovalWhitelist; + type MaxReserves = (); + type ReserveIdentifier = [u8; 8]; + type CurrencyHooks = (); + type NontransferableTokens = Nothing; + type NontransferableTokensAllowList = Nothing; +} + +parameter_types! { + pub const NativeCurrencyId: u32 = NATIVE_CURRENCY_ID; + pub const TreasuryPalletId: PalletId = PalletId(*b"py/trsry"); + pub const BnbTreasurySubAccDerive: [u8; 4] = *b"bnbt"; +} + +parameter_types! { + pub const MinVestedTransfer: Balance = 0; + pub UnvestedFundsAllowedWithdrawReasons: WithdrawReasons = + WithdrawReasons::except(WithdrawReasons::TRANSFER | WithdrawReasons::RESERVE); +} + +impl pallet_vesting_mangata::Config for Test { + type RuntimeEvent = RuntimeEvent; + type Tokens = MultiTokenCurrencyAdapter; + type BlockNumberToBalance = sp_runtime::traits::ConvertInto; + type MinVestedTransfer = MinVestedTransfer; + type WeightInfo = pallet_vesting_mangata::weights::SubstrateWeight; + type UnvestedFundsAllowedWithdrawReasons = UnvestedFundsAllowedWithdrawReasons; + type BlockNumberProvider = System; + // `VestingInfo` encode length is 36bytes. 28 schedules gets encoded as 1009 bytes, which is the + // highest number of schedules that encodes less than 2^10. + const MAX_VESTING_SCHEDULES: u32 = 50; +} + +impl Config for Test { + type RuntimeEvent = RuntimeEvent; + type MaxRelocks = MaxLocks; + type Tokens = MultiTokenCurrencyAdapter; + type NativeCurrencyId = NativeCurrencyId; + type VestingProvider = Vesting; + type Xyk = MockXyk; + type WeightInfo = (); +} + +pub struct MockXyk(PhantomData); +impl XykFunctionsTrait for MockXyk { + fn create_pool( + _sender: AccountId, + _first_asset_id: TokenId, + _first_asset_amount: Balance, + _second_asset_id: TokenId, + _second_asset_amount: Balance, + ) -> Result { + unimplemented!() + } + + fn sell_asset( + _sender: AccountId, + _sold_asset_id: TokenId, + _bought_asset_id: TokenId, + _sold_asset_amount: Balance, + _min_amount_out: Balance, + _err_upon_bad_slippage: bool, + ) -> Result { + unimplemented!() + } + + fn do_multiswap_sell_asset( + _sender: AccountId, + _swap_token_list: Vec, + _sold_asset_amount: Balance, + _min_amount_out: Balance, + ) -> Result { + unimplemented!() + } + fn do_multiswap_buy_asset( + _sender: AccountId, + _swap_token_list: Vec, + _bought_asset_amount: Balance, + _max_amount_in: Balance, + ) -> Result { + unimplemented!() + } + + fn buy_asset( + _sender: AccountId, + _sold_asset_id: TokenId, + _bought_asset_id: TokenId, + _bought_asset_amount: Balance, + _max_amount_in: Balance, + _err_upon_bad_slippage: bool, + ) -> Result { + unimplemented!() + } + + fn multiswap_sell_asset( + _sender: AccountId, + _swap_token_list: Vec, + _sold_asset_amount: Balance, + _min_amount_out: Balance, + _err_upon_bad_slippage: bool, + _err_upon_non_slippage_fail: bool, + ) -> Result { + unimplemented!() + } + + fn multiswap_buy_asset( + _sender: AccountId, + _swap_token_list: Vec, + _bought_asset_amount: Balance, + _max_amount_in: Balance, + _err_upon_bad_slippage: bool, + _err_upon_non_slippage_fail: bool, + ) -> Result { + unimplemented!() + } + + fn mint_liquidity( + _sender: AccountId, + _first_asset_id: TokenId, + _second_asset_id: TokenId, + _first_asset_amount: Balance, + _expected_second_asset_amount: Balance, + _activate_minted_liquidity: bool, + ) -> Result<(TokenId, Balance, Balance), DispatchError> { + unimplemented!() + } + + fn provide_liquidity_with_conversion( + _sender: AccountId, + _first_asset_id: TokenId, + _second_asset_id: TokenId, + _provided_asset_id: TokenId, + _provided_asset_amount: Balance, + _activate_minted_liquidity: bool, + ) -> Result<(TokenId, Balance), DispatchError> { + unimplemented!() + } + + fn burn_liquidity( + _sender: AccountId, + _first_asset_id: TokenId, + _second_asset_id: TokenId, + _liquidity_asset_amount: Balance, + ) -> Result<(Balance, Balance), DispatchError> { + unimplemented!() + } + + fn get_tokens_required_for_minting( + _liquidity_asset_id: TokenId, + _liquidity_token_amount: Balance, + ) -> Result<(TokenId, Balance, TokenId, Balance), DispatchError> { + unimplemented!() + } + + fn is_liquidity_token(_liquidity_asset_id: TokenId) -> bool { + true + } + + fn do_compound_rewards( + _sender: AccountId, + _liquidity_asset_id: TokenId, + _amount_permille: Permill, + ) -> DispatchResult { + unimplemented!() + } +} + +// This function basically just builds a genesis storage key/value store according to +// our desired mockup. +pub fn new_test_ext() -> sp_io::TestExternalities { + system::GenesisConfig::::default().build_storage().unwrap().into() +} diff --git a/gasp-node/pallets/multipurpose-liquidity/src/tests.rs b/gasp-node/pallets/multipurpose-liquidity/src/tests.rs new file mode 100644 index 000000000..153d1decd --- /dev/null +++ b/gasp-node/pallets/multipurpose-liquidity/src/tests.rs @@ -0,0 +1,752 @@ +use super::*; +use crate::mock::*; +use frame_support::assert_ok; +use frame_system::RawOrigin; +use pallet_vesting_mangata::VestingInfo; + +#[test] +fn reserve_vesting_liquidity_tokens_works() { + new_test_ext().execute_with(|| { + let caller = 0u64; + let initial_amount: Balance = 2_000_000__u128; + let asset_id: TokenId = ::Tokens::create(&caller, initial_amount).unwrap(); + let locked_amount: Balance = 500_000__u128; + let lock_ending_block_as_balance: Balance = 1_000__u128; + + let reserve_amount: Balance = 200_000__u128; + + // Assuming max locks is 50 + // Let's add 49 dummy ones for worst case + + let n = 49; + let dummy_lock_amount = 1000u128; + let dummy_end_block = 10_u128; + + for _ in 0..n { + ::VestingProvider::lock_tokens( + &caller, + asset_id, + dummy_lock_amount, + None, + dummy_end_block, + ) + .unwrap(); + } + ::VestingProvider::lock_tokens( + &caller, + asset_id, + locked_amount, + None, + lock_ending_block_as_balance, + ) + .unwrap(); + + let now = >::block_number(); + assert_ok!(MultiPurposeLiquidity::reserve_vesting_liquidity_tokens( + RawOrigin::Signed(caller.clone()).into(), + asset_id, + reserve_amount + )); + + assert_eq!( + ::Tokens::free_balance(asset_id, &caller) as Balance, + 1800000u128 + ); + assert_eq!( + ::Tokens::locked_balance(asset_id, &caller) as Balance, + 349000u128 + ); + assert_eq!( + ::Tokens::reserved_balance(asset_id, &caller) as Balance, + 200000u128 + ); + assert_eq!( + Vesting::vesting(caller.clone(), asset_id as TokenId) + .unwrap() + .into_inner() + .pop() + .unwrap() + .locked(), + 300000 + ); + assert_eq!( + MultiPurposeLiquidity::get_reserve_status(caller.clone(), asset_id).relock_amount, + reserve_amount + ); + assert_eq!( + MultiPurposeLiquidity::get_relock_status(caller, asset_id)[0], + RelockStatusInfo::> { + amount: reserve_amount, + starting_block: now, + ending_block_as_balance: lock_ending_block_as_balance + } + ); + }) +} + +#[test] +fn unreserve_and_relock_instance_works() { + new_test_ext().execute_with(|| { + let caller = 0u64; + let initial_amount: Balance = 2_000_000__u128; + let asset_id: TokenId = ::Tokens::create(&caller, initial_amount).unwrap(); + let locked_amount: Balance = 500_000__u128; + let lock_ending_block_as_balance: Balance = 1_000__u128; + + let reserve_amount: Balance = 200_000__u128; + + // Assuming max locks is 50 + // Let's add 49 dummy ones for worst case + + let n = 48; + let dummy_lock_amount = 1000u128; + let dummy_end_block = 10_u128; + + for _ in 0..n { + ::VestingProvider::lock_tokens( + &caller, + asset_id, + dummy_lock_amount, + None, + dummy_end_block, + ) + .unwrap(); + } + ::VestingProvider::lock_tokens( + &caller, + asset_id, + locked_amount, + None, + lock_ending_block_as_balance, + ) + .unwrap(); + + let now = >::block_number(); + assert_ok!(MultiPurposeLiquidity::reserve_vesting_liquidity_tokens( + RawOrigin::Signed(caller.clone()).into(), + asset_id, + reserve_amount + )); + + assert_eq!( + ::Tokens::free_balance(asset_id, &caller) as Balance, + 1_800_000__u128 + ); + assert_eq!( + ::Tokens::locked_balance(asset_id, &caller) as Balance, + 348000u128 + ); + assert_eq!( + ::Tokens::reserved_balance(asset_id, &caller) as Balance, + 200000u128 + ); + assert_eq!( + Vesting::vesting(caller.clone(), asset_id as TokenId) + .unwrap() + .into_inner() + .pop() + .unwrap() + .locked(), + 300000 + ); + assert_eq!( + MultiPurposeLiquidity::get_reserve_status(caller.clone(), asset_id).relock_amount, + reserve_amount + ); + assert_eq!( + MultiPurposeLiquidity::get_relock_status(caller, asset_id)[0], + RelockStatusInfo::> { + amount: reserve_amount, + starting_block: now, + ending_block_as_balance: lock_ending_block_as_balance + } + ); + + assert_ok!(MultiPurposeLiquidity::unreserve_and_relock_instance( + RawOrigin::Signed(caller.clone()).into(), + asset_id, + 0u32 + )); + + assert_eq!( + ::Tokens::free_balance(asset_id, &caller) as Balance, + 2_000_000__u128 + ); + assert_eq!(::Tokens::locked_balance(asset_id, &caller) as Balance, 548000); + assert_eq!(::Tokens::reserved_balance(asset_id, &caller) as Balance, 0); + assert_eq!( + Vesting::vesting(caller.clone(), asset_id as TokenId) + .unwrap() + .into_inner() + .pop() + .unwrap() + .locked(), + 200000 + ); + assert_eq!( + MultiPurposeLiquidity::get_reserve_status(caller.clone(), asset_id).relock_amount, + Balance::zero() + ); + assert_eq!(MultiPurposeLiquidity::get_relock_status(caller, asset_id), vec![]); + }) +} + +#[test] +fn bond_from_available_balance_works() { + new_test_ext().execute_with(|| { + let caller = 0u64; + let initial_amount: Balance = 1_000_000__u128; + let asset_id: TokenId = ::Tokens::create(&caller, initial_amount).unwrap(); + let bond_amount: Balance = 100_000__u128; + + let reserve_status = Pallet::::get_reserve_status(caller.clone(), asset_id); + let relock_status = Pallet::::get_relock_status(caller.clone(), asset_id); + assert_eq!(reserve_status.staked_unactivated_reserves, Balance::zero()); + assert_eq!(reserve_status.activated_unstaked_reserves, Balance::zero()); + assert_eq!(reserve_status.staked_and_activated_reserves, Balance::zero()); + assert_eq!(reserve_status.unspent_reserves, Balance::zero()); + assert_eq!(reserve_status.relock_amount, Balance::zero()); + assert_eq!(relock_status, Vec::>>::new()); + assert_eq!( + ::Tokens::free_balance(asset_id, &caller) as Balance, + initial_amount + ); + assert_eq!(::Tokens::locked_balance(asset_id, &caller) as Balance, 0); + assert_eq!(::Tokens::reserved_balance(asset_id, &caller) as Balance, 0); + assert_eq!(Vesting::vesting(caller.clone(), asset_id as TokenId), None); + + assert_ok!( + as StakingReservesProviderTrait>::bond( + asset_id, + &caller, + bond_amount, + Some(BondKind::AvailableBalance) + ) + ); + + let reserve_status = Pallet::::get_reserve_status(caller.clone(), asset_id); + let relock_status = Pallet::::get_relock_status(caller.clone(), asset_id); + assert_eq!(reserve_status.staked_unactivated_reserves, bond_amount); + assert_eq!(reserve_status.activated_unstaked_reserves, Balance::zero()); + assert_eq!(reserve_status.staked_and_activated_reserves, Balance::zero()); + assert_eq!(reserve_status.unspent_reserves, Balance::zero()); + assert_eq!(reserve_status.relock_amount, Balance::zero()); + assert_eq!(relock_status, Vec::>>::new()); + assert_eq!( + ::Tokens::free_balance(asset_id, &caller) as Balance, + initial_amount - bond_amount + ); + assert_eq!(::Tokens::locked_balance(asset_id, &caller) as Balance, 0); + assert_eq!( + ::Tokens::reserved_balance(asset_id, &caller) as Balance, + bond_amount + ); + assert_eq!(Vesting::vesting(caller.clone(), asset_id as TokenId), None); + }) +} + +#[test] +fn bond_from_activated_unstaked_liquidity_works() { + new_test_ext().execute_with(|| { + let caller = 0u64; + let initial_amount: Balance = 1_000_000__u128; + let asset_id: TokenId = ::Tokens::create(&caller, initial_amount).unwrap(); + let activated_amount: Balance = 200_000__u128; + let bond_amount: Balance = 100_000__u128; + + assert_ok!(::Tokens::reserve(asset_id, &caller, activated_amount)); + let mut updated_reserve_status = + Pallet::::get_reserve_status(caller.clone(), asset_id); + updated_reserve_status.activated_unstaked_reserves = activated_amount; + ReserveStatus::::insert(caller, asset_id, updated_reserve_status); + + let reserve_status = Pallet::::get_reserve_status(caller.clone(), asset_id); + let relock_status = Pallet::::get_relock_status(caller.clone(), asset_id); + assert_eq!(reserve_status.staked_unactivated_reserves, Balance::zero()); + assert_eq!(reserve_status.activated_unstaked_reserves, activated_amount); + assert_eq!(reserve_status.staked_and_activated_reserves, Balance::zero()); + assert_eq!(reserve_status.unspent_reserves, Balance::zero()); + assert_eq!(reserve_status.relock_amount, Balance::zero()); + assert_eq!(relock_status, Vec::>>::new()); + assert_eq!( + ::Tokens::free_balance(asset_id, &caller) as Balance, + initial_amount - activated_amount + ); + assert_eq!(::Tokens::locked_balance(asset_id, &caller) as Balance, 0); + assert_eq!( + ::Tokens::reserved_balance(asset_id, &caller) as Balance, + activated_amount + ); + assert_eq!(Vesting::vesting(caller.clone(), asset_id as TokenId), None); + + assert_ok!( + as StakingReservesProviderTrait>::bond( + asset_id, + &caller, + bond_amount, + Some(BondKind::ActivatedUnstakedReserves) + ) + ); + + let reserve_status = Pallet::::get_reserve_status(caller.clone(), asset_id); + let relock_status = Pallet::::get_relock_status(caller.clone(), asset_id); + assert_eq!(reserve_status.staked_unactivated_reserves, Balance::zero()); + assert_eq!(reserve_status.activated_unstaked_reserves, activated_amount - bond_amount); + assert_eq!(reserve_status.staked_and_activated_reserves, bond_amount); + assert_eq!(reserve_status.unspent_reserves, Balance::zero()); + assert_eq!(reserve_status.relock_amount, Balance::zero()); + assert_eq!(relock_status, Vec::>>::new()); + assert_eq!( + ::Tokens::free_balance(asset_id, &caller) as Balance, + initial_amount - activated_amount + ); + assert_eq!(::Tokens::locked_balance(asset_id, &caller) as Balance, 0); + assert_eq!( + ::Tokens::reserved_balance(asset_id, &caller) as Balance, + activated_amount + ); + assert_eq!(Vesting::vesting(caller.clone(), asset_id as TokenId), None); + }) +} + +#[test] +fn bond_from_unspent_works() { + new_test_ext().execute_with(|| { + let caller = 0u64; + let initial_amount: Balance = 1_000_000__u128; + let asset_id: TokenId = ::Tokens::create(&caller, initial_amount).unwrap(); + let unspent_amount: Balance = 200_000__u128; + let bond_amount: Balance = 100_000__u128; + + assert_ok!(::Tokens::reserve(asset_id, &caller, unspent_amount)); + let mut updated_reserve_status = + Pallet::::get_reserve_status(caller.clone(), asset_id); + updated_reserve_status.unspent_reserves = unspent_amount; + ReserveStatus::::insert(caller, asset_id, updated_reserve_status); + + let reserve_status = Pallet::::get_reserve_status(caller.clone(), asset_id); + let relock_status = Pallet::::get_relock_status(caller.clone(), asset_id); + assert_eq!(reserve_status.staked_unactivated_reserves, Balance::zero()); + assert_eq!(reserve_status.activated_unstaked_reserves, Balance::zero()); + assert_eq!(reserve_status.staked_and_activated_reserves, Balance::zero()); + assert_eq!(reserve_status.unspent_reserves, unspent_amount); + assert_eq!(reserve_status.relock_amount, Balance::zero()); + assert_eq!(relock_status, Vec::>>::new()); + assert_eq!( + ::Tokens::free_balance(asset_id, &caller) as Balance, + initial_amount - unspent_amount + ); + assert_eq!(::Tokens::locked_balance(asset_id, &caller) as Balance, 0); + assert_eq!( + ::Tokens::reserved_balance(asset_id, &caller) as Balance, + unspent_amount + ); + assert_eq!(Vesting::vesting(caller.clone(), asset_id as TokenId), None); + + assert_ok!( + as StakingReservesProviderTrait>::bond( + asset_id, + &caller, + bond_amount, + Some(BondKind::UnspentReserves) + ) + ); + + let reserve_status = Pallet::::get_reserve_status(caller.clone(), asset_id); + let relock_status = Pallet::::get_relock_status(caller.clone(), asset_id); + assert_eq!(reserve_status.staked_unactivated_reserves, bond_amount); + assert_eq!(reserve_status.activated_unstaked_reserves, Balance::zero()); + assert_eq!(reserve_status.staked_and_activated_reserves, Balance::zero()); + assert_eq!(reserve_status.unspent_reserves, unspent_amount - bond_amount); + assert_eq!(reserve_status.relock_amount, Balance::zero()); + assert_eq!(relock_status, Vec::>>::new()); + assert_eq!( + ::Tokens::free_balance(asset_id, &caller) as Balance, + initial_amount - unspent_amount + ); + assert_eq!(::Tokens::locked_balance(asset_id, &caller) as Balance, 0); + assert_eq!( + ::Tokens::reserved_balance(asset_id, &caller) as Balance, + unspent_amount + ); + assert_eq!(Vesting::vesting(caller.clone(), asset_id as TokenId), None); + }) +} + +#[test] +fn activate_from_available_balance_works() { + new_test_ext().execute_with(|| { + let caller = 0u64; + let initial_amount: Balance = 1_000_000__u128; + let asset_id: TokenId = ::Tokens::create(&caller, initial_amount).unwrap(); + let activate_amount: Balance = 100_000__u128; + + let reserve_status = Pallet::::get_reserve_status(caller.clone(), asset_id); + let relock_status = Pallet::::get_relock_status(caller.clone(), asset_id); + assert_eq!(reserve_status.staked_unactivated_reserves, Balance::zero()); + assert_eq!(reserve_status.activated_unstaked_reserves, Balance::zero()); + assert_eq!(reserve_status.staked_and_activated_reserves, Balance::zero()); + assert_eq!(reserve_status.unspent_reserves, Balance::zero()); + assert_eq!(reserve_status.relock_amount, Balance::zero()); + assert_eq!(relock_status, Vec::>>::new()); + assert_eq!( + ::Tokens::free_balance(asset_id, &caller) as Balance, + initial_amount + ); + assert_eq!(::Tokens::locked_balance(asset_id, &caller) as Balance, 0); + assert_eq!(::Tokens::reserved_balance(asset_id, &caller) as Balance, 0); + assert_eq!(Vesting::vesting(caller.clone(), asset_id as TokenId), None); + + assert_ok!( as ActivationReservesProviderTrait< + AccountId, + Balance, + TokenId, + >>::activate( + asset_id, &caller, activate_amount, Some(ActivateKind::AvailableBalance) + )); + + let reserve_status = Pallet::::get_reserve_status(caller.clone(), asset_id); + let relock_status = Pallet::::get_relock_status(caller.clone(), asset_id); + assert_eq!(reserve_status.staked_unactivated_reserves, Balance::zero()); + assert_eq!(reserve_status.activated_unstaked_reserves, activate_amount); + assert_eq!(reserve_status.staked_and_activated_reserves, Balance::zero()); + assert_eq!(reserve_status.unspent_reserves, Balance::zero()); + assert_eq!(reserve_status.relock_amount, Balance::zero()); + assert_eq!(relock_status, Vec::>>::new()); + assert_eq!( + ::Tokens::free_balance(asset_id, &caller) as Balance, + initial_amount - activate_amount + ); + assert_eq!(::Tokens::locked_balance(asset_id, &caller) as Balance, 0); + assert_eq!( + ::Tokens::reserved_balance(asset_id, &caller) as Balance, + activate_amount + ); + assert_eq!(Vesting::vesting(caller.clone(), asset_id as TokenId), None); + }) +} + +#[test] +fn activate_from_staked_unactivated_liquidity_works() { + new_test_ext().execute_with(|| { + let caller = 0u64; + let initial_amount: Balance = 1_000_000__u128; + let asset_id: TokenId = ::Tokens::create(&caller, initial_amount).unwrap(); + let bonded_amount: Balance = 200_000__u128; + let activate_amount: Balance = 100_000__u128; + + assert_ok!(::Tokens::reserve(asset_id, &caller, bonded_amount)); + let mut updated_reserve_status = + Pallet::::get_reserve_status(caller.clone(), asset_id); + updated_reserve_status.staked_unactivated_reserves = bonded_amount; + ReserveStatus::::insert(caller, asset_id, updated_reserve_status); + + let reserve_status = Pallet::::get_reserve_status(caller.clone(), asset_id); + let relock_status = Pallet::::get_relock_status(caller.clone(), asset_id); + assert_eq!(reserve_status.staked_unactivated_reserves, bonded_amount); + assert_eq!(reserve_status.activated_unstaked_reserves, Balance::zero()); + assert_eq!(reserve_status.staked_and_activated_reserves, Balance::zero()); + assert_eq!(reserve_status.unspent_reserves, Balance::zero()); + assert_eq!(reserve_status.relock_amount, Balance::zero()); + assert_eq!(relock_status, Vec::>>::new()); + assert_eq!( + ::Tokens::free_balance(asset_id, &caller) as Balance, + initial_amount - bonded_amount + ); + assert_eq!(::Tokens::locked_balance(asset_id, &caller) as Balance, 0); + assert_eq!( + ::Tokens::reserved_balance(asset_id, &caller) as Balance, + bonded_amount + ); + assert_eq!(Vesting::vesting(caller.clone(), asset_id as TokenId), None); + + assert_ok!( as ActivationReservesProviderTrait< + AccountId, + Balance, + TokenId, + >>::activate( + asset_id, + &caller, + activate_amount, + Some(ActivateKind::StakedUnactivatedReserves) + )); + + let reserve_status = Pallet::::get_reserve_status(caller.clone(), asset_id); + let relock_status = Pallet::::get_relock_status(caller.clone(), asset_id); + assert_eq!(reserve_status.staked_unactivated_reserves, bonded_amount - activate_amount); + assert_eq!(reserve_status.activated_unstaked_reserves, Balance::zero()); + assert_eq!(reserve_status.staked_and_activated_reserves, activate_amount); + assert_eq!(reserve_status.unspent_reserves, Balance::zero()); + assert_eq!(reserve_status.relock_amount, Balance::zero()); + assert_eq!(relock_status, Vec::>>::new()); + assert_eq!( + ::Tokens::free_balance(asset_id, &caller) as Balance, + initial_amount - bonded_amount + ); + assert_eq!(::Tokens::locked_balance(asset_id, &caller) as Balance, 0); + assert_eq!( + ::Tokens::reserved_balance(asset_id, &caller) as Balance, + bonded_amount + ); + assert_eq!(Vesting::vesting(caller.clone(), asset_id as TokenId), None); + }) +} + +#[test] +fn activate_from_unspent_works() { + new_test_ext().execute_with(|| { + let caller = 0u64; + let initial_amount: Balance = 1_000_000__u128; + let asset_id: TokenId = ::Tokens::create(&caller, initial_amount).unwrap(); + let unspent_amount: Balance = 200_000__u128; + let activate_amount: Balance = 100_000__u128; + + assert_ok!(::Tokens::reserve(asset_id, &caller, unspent_amount)); + let mut updated_reserve_status = + Pallet::::get_reserve_status(caller.clone(), asset_id); + updated_reserve_status.unspent_reserves = unspent_amount; + ReserveStatus::::insert(caller, asset_id, updated_reserve_status); + + let reserve_status = Pallet::::get_reserve_status(caller.clone(), asset_id); + let relock_status = Pallet::::get_relock_status(caller.clone(), asset_id); + assert_eq!(reserve_status.staked_unactivated_reserves, Balance::zero()); + assert_eq!(reserve_status.activated_unstaked_reserves, Balance::zero()); + assert_eq!(reserve_status.staked_and_activated_reserves, Balance::zero()); + assert_eq!(reserve_status.unspent_reserves, unspent_amount); + assert_eq!(reserve_status.relock_amount, Balance::zero()); + assert_eq!(relock_status, Vec::>>::new()); + assert_eq!( + ::Tokens::free_balance(asset_id, &caller) as Balance, + initial_amount - unspent_amount + ); + assert_eq!(::Tokens::locked_balance(asset_id, &caller) as Balance, 0); + assert_eq!( + ::Tokens::reserved_balance(asset_id, &caller) as Balance, + unspent_amount + ); + assert_eq!(Vesting::vesting(caller.clone(), asset_id as TokenId), None); + + assert_ok!( as ActivationReservesProviderTrait< + AccountId, + Balance, + TokenId, + >>::activate( + asset_id, &caller, activate_amount, Some(ActivateKind::UnspentReserves) + )); + + let reserve_status = Pallet::::get_reserve_status(caller.clone(), asset_id); + let relock_status = Pallet::::get_relock_status(caller.clone(), asset_id); + assert_eq!(reserve_status.staked_unactivated_reserves, Balance::zero()); + assert_eq!(reserve_status.activated_unstaked_reserves, activate_amount); + assert_eq!(reserve_status.staked_and_activated_reserves, Balance::zero()); + assert_eq!(reserve_status.unspent_reserves, unspent_amount - activate_amount); + assert_eq!(reserve_status.relock_amount, Balance::zero()); + assert_eq!(relock_status, Vec::>>::new()); + assert_eq!( + ::Tokens::free_balance(asset_id, &caller) as Balance, + initial_amount - unspent_amount + ); + assert_eq!(::Tokens::locked_balance(asset_id, &caller) as Balance, 0); + assert_eq!( + ::Tokens::reserved_balance(asset_id, &caller) as Balance, + unspent_amount + ); + assert_eq!(Vesting::vesting(caller.clone(), asset_id as TokenId), None); + }) +} + +#[test] +fn unbond_works() { + new_test_ext().execute_with(|| { + let caller = 0u64; + let initial_amount: Balance = 1_000_000__u128; + let asset_id: TokenId = ::Tokens::create(&caller, initial_amount).unwrap(); + let staked_unactivated_amount: Balance = 50_000__u128; + let staked_and_activated_amount: Balance = 85_000__u128; + let relock_amount: Balance = 100_000__u128; + let unbond_amount: Balance = 90_000_u128; + + assert_ok!(::Tokens::reserve( + asset_id, + &caller, + staked_unactivated_amount + staked_and_activated_amount + )); + let mut updated_reserve_status = + Pallet::::get_reserve_status(caller.clone(), asset_id); + updated_reserve_status.staked_unactivated_reserves = staked_unactivated_amount; + updated_reserve_status.staked_and_activated_reserves = staked_and_activated_amount; + updated_reserve_status.relock_amount = relock_amount; + ReserveStatus::::insert(caller, asset_id, updated_reserve_status); + + let reserve_status = Pallet::::get_reserve_status(caller.clone(), asset_id); + let relock_status = Pallet::::get_relock_status(caller.clone(), asset_id); + assert_eq!(reserve_status.staked_unactivated_reserves, staked_unactivated_amount); + assert_eq!(reserve_status.activated_unstaked_reserves, Balance::zero()); + assert_eq!(reserve_status.staked_and_activated_reserves, staked_and_activated_amount); + assert_eq!(reserve_status.unspent_reserves, Balance::zero()); + assert_eq!(reserve_status.relock_amount, relock_amount); + assert_eq!(relock_status, Vec::>>::new()); + assert_eq!( + ::Tokens::free_balance(asset_id, &caller) as Balance, + initial_amount - (staked_unactivated_amount + staked_and_activated_amount) + ); + assert_eq!(::Tokens::locked_balance(asset_id, &caller) as Balance, 0); + assert_eq!( + ::Tokens::reserved_balance(asset_id, &caller) as Balance, + (staked_unactivated_amount + staked_and_activated_amount) + ); + assert_eq!(Vesting::vesting(caller.clone(), asset_id as TokenId), None); + + assert_eq!( + as StakingReservesProviderTrait>::unbond( + asset_id, + &caller, + unbond_amount + ), + Balance::zero() + ); + + let reserve_status = Pallet::::get_reserve_status(caller.clone(), asset_id); + let relock_status = Pallet::::get_relock_status(caller.clone(), asset_id); + assert_eq!(reserve_status.staked_unactivated_reserves, Balance::zero()); + assert_eq!(reserve_status.activated_unstaked_reserves, 40_000__u128); + assert_eq!(reserve_status.staked_and_activated_reserves, 45_000__u128); + assert_eq!(reserve_status.unspent_reserves, 15_000__u128); + assert_eq!(reserve_status.relock_amount, relock_amount); + assert_eq!(relock_status, Vec::>>::new()); + assert_eq!( + ::Tokens::free_balance(asset_id, &caller) as Balance, + initial_amount - 135_000__u128 + 35_000__u128 + ); + assert_eq!(::Tokens::locked_balance(asset_id, &caller) as Balance, 0); + assert_eq!( + ::Tokens::reserved_balance(asset_id, &caller) as Balance, + 135_000__u128 - 35_000__u128 + ); + assert_eq!(Vesting::vesting(caller.clone(), asset_id as TokenId), None); + }) +} + +#[test] +fn deactivate_works() { + new_test_ext().execute_with(|| { + let caller = 0u64; + let initial_amount: Balance = 1_000_000__u128; + let asset_id: TokenId = ::Tokens::create(&caller, initial_amount).unwrap(); + let activated_unstaked_amount: Balance = 50_000__u128; + let staked_and_activated_amount: Balance = 85_000__u128; + let relock_amount: Balance = 100_000__u128; + let deactivate_amount: Balance = 90_000_u128; + + assert_ok!(::Tokens::reserve( + asset_id, + &caller, + activated_unstaked_amount + staked_and_activated_amount + )); + let mut updated_reserve_status = + Pallet::::get_reserve_status(caller.clone(), asset_id); + updated_reserve_status.activated_unstaked_reserves = activated_unstaked_amount; + updated_reserve_status.staked_and_activated_reserves = staked_and_activated_amount; + updated_reserve_status.relock_amount = relock_amount; + ReserveStatus::::insert(caller, asset_id, updated_reserve_status); + + let reserve_status = Pallet::::get_reserve_status(caller.clone(), asset_id); + let relock_status = Pallet::::get_relock_status(caller.clone(), asset_id); + assert_eq!(reserve_status.staked_unactivated_reserves, Balance::zero()); + assert_eq!(reserve_status.activated_unstaked_reserves, activated_unstaked_amount); + assert_eq!(reserve_status.staked_and_activated_reserves, staked_and_activated_amount); + assert_eq!(reserve_status.unspent_reserves, Balance::zero()); + assert_eq!(reserve_status.relock_amount, relock_amount); + assert_eq!(relock_status, Vec::>>::new()); + assert_eq!( + ::Tokens::free_balance(asset_id, &caller) as Balance, + initial_amount - (activated_unstaked_amount + staked_and_activated_amount) + ); + assert_eq!(::Tokens::locked_balance(asset_id, &caller) as Balance, 0); + assert_eq!( + ::Tokens::reserved_balance(asset_id, &caller) as Balance, + (activated_unstaked_amount + staked_and_activated_amount) + ); + assert_eq!(Vesting::vesting(caller.clone(), asset_id as TokenId), None); + + assert_eq!( + as ActivationReservesProviderTrait>::deactivate( + asset_id, + &caller, + deactivate_amount + ), + Balance::zero() + ); + + let reserve_status = Pallet::::get_reserve_status(caller.clone(), asset_id); + let relock_status = Pallet::::get_relock_status(caller.clone(), asset_id); + assert_eq!(reserve_status.staked_unactivated_reserves, 40_000__u128); + assert_eq!(reserve_status.activated_unstaked_reserves, Balance::zero()); + assert_eq!(reserve_status.staked_and_activated_reserves, 45_000__u128); + assert_eq!(reserve_status.unspent_reserves, 15_000__u128); + assert_eq!(reserve_status.relock_amount, relock_amount); + assert_eq!(relock_status, Vec::>>::new()); + assert_eq!( + ::Tokens::free_balance(asset_id, &caller) as Balance, + initial_amount - 135_000__u128 + 35_000__u128 + ); + assert_eq!(::Tokens::locked_balance(asset_id, &caller) as Balance, 0); + assert_eq!( + ::Tokens::reserved_balance(asset_id, &caller) as Balance, + 135_000__u128 - 35_000__u128 + ); + assert_eq!(Vesting::vesting(caller.clone(), asset_id as TokenId), None); + }) +} + +type TokensOf = ::Tokens; + +#[test] +fn vested_mpl_vested_transition_works_for_native_tokens() { + new_test_ext().execute_with(|| { + const MGX: u32 = ::NativeCurrencyId::get(); + const ALICE: u64 = 0u64; + const MILLION: u128 = 1_000_000__000_000_000_000_000_000u128; + const PER_BLOCK: u128 = 1; + const STARTING_BLOCK: u64 = 10; + + let token_id = ::Tokens::create(&ALICE, MILLION).unwrap(); + assert_eq!(token_id, MGX); + assert_eq!(TokensOf::::free_balance(MGX, &ALICE), MILLION); + assert_eq!(orml_tokens::Accounts::::get(ALICE, MGX).frozen, 0u128); + + // vest tokens + pallet_vesting_mangata::Pallet::::force_vested_transfer( + RawOrigin::Root.into(), + MGX, + ALICE, + ALICE, + VestingInfo::new(MILLION, PER_BLOCK, STARTING_BLOCK), + ) + .unwrap(); + let infos = pallet_vesting_mangata::Pallet::::vesting(ALICE, MGX).unwrap(); + assert_eq!(infos.get(0).unwrap().locked(), MILLION); + assert_eq!(infos.get(0).unwrap().per_block(), PER_BLOCK); + assert_eq!(infos.get(0).unwrap().starting_block(), STARTING_BLOCK); + assert_eq!(orml_tokens::Accounts::::get(ALICE, MGX).frozen, MILLION); + + // move vested tokens to MPL + Pallet::::reserve_vesting_native_tokens_by_vesting_index( + RawOrigin::Signed(ALICE).into(), + 0, + Some(MILLION), + ) + .unwrap(); + assert_eq!(orml_tokens::Accounts::::get(ALICE, MGX).frozen, 0u128); + assert!(pallet_vesting_mangata::Pallet::::vesting(ALICE, MGX).is_none()); + + // move tokens from MPL to vested + Pallet::::unreserve_and_relock_instance(RawOrigin::Signed(ALICE).into(), MGX, 0) + .unwrap(); + let infos = pallet_vesting_mangata::Pallet::::vesting(ALICE, MGX).unwrap(); + assert_eq!(infos.get(0).unwrap().locked(), MILLION); + assert_eq!(infos.get(0).unwrap().per_block(), PER_BLOCK); + assert_eq!(infos.get(0).unwrap().starting_block(), STARTING_BLOCK); + assert_eq!(orml_tokens::Accounts::::get(ALICE, MGX).frozen, MILLION); + }) +} diff --git a/gasp-node/pallets/multipurpose-liquidity/src/weights.rs b/gasp-node/pallets/multipurpose-liquidity/src/weights.rs new file mode 100644 index 000000000..0085188af --- /dev/null +++ b/gasp-node/pallets/multipurpose-liquidity/src/weights.rs @@ -0,0 +1,73 @@ +// This file is part of Mangata. + +// Copyright (C) 2020-2022 Mangata Foundation. +// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 + +// This program 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. + +// This program 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 this program. If not, see . + +//! Autogenerated weights for pallet_multipurpose_liquidity +//! +//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev +//! DATE: 2022-06-26, STEPS: `1`, REPEAT: 1, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! EXECUTION: Some(Wasm), WASM-EXECUTION: Compiled, CHAIN: Some("dev"), DB CACHE: 1024 + +// Executed Command: +// /home/striker/work/mangata-ws/mangata-node/scripts/..//target/release/mangata-node +// benchmark +// --chain +// dev +// --execution +// wasm +// --wasm-execution +// compiled +// --pallet +// pallet_multipurpose_liquidity +// --extrinsic +// * +// --steps +// 1 +// --repeat +// 1 +// --output +// ./benchmarks/pallet_multipurpose_liquidity_weights.rs +// --template +// ./templates/module-weight-template.hbs + +#![cfg_attr(rustfmt, rustfmt_skip)] +#![allow(unused_parens)] +#![allow(unused_imports)] +#![allow(clippy::unnecessary_cast)] + +use frame_support::{traits::Get, weights::{Weight, constants::RocksDbWeight}}; +use sp_std::marker::PhantomData; + +/// Weight functions needed for pallet_multipurpose_liquidity. +pub trait WeightInfo { + fn reserve_vesting_liquidity_tokens() -> Weight; + fn unreserve_and_relock_instance() -> Weight; +} + +// For backwards compatibility and tests +impl WeightInfo for () { + fn reserve_vesting_liquidity_tokens() -> Weight { + Weight::from_parts(82_618_000, 0) + .saturating_add(RocksDbWeight::get().reads(6 as u64)) + .saturating_add(RocksDbWeight::get().writes(5 as u64)) + } + fn unreserve_and_relock_instance() -> Weight { + Weight::from_parts(68_582_000, 0) + .saturating_add(RocksDbWeight::get().reads(5 as u64)) + .saturating_add(RocksDbWeight::get().writes(5 as u64)) + } +} diff --git a/gasp-node/pallets/parachain-staking/Cargo.toml b/gasp-node/pallets/parachain-staking/Cargo.toml new file mode 100644 index 000000000..7af34a05b --- /dev/null +++ b/gasp-node/pallets/parachain-staking/Cargo.toml @@ -0,0 +1,68 @@ +[package] +name = "parachain-staking" +authors = ["PureStake"] +description = "parachain staking pallet for collator selection and reward distribution" +edition = "2021" +version = "3.0.0" + +[dependencies] +aquamarine.workspace = true +codec = { workspace = true, default-features = false } +itertools = { workspace = true, default-features = false, features = ["use_alloc"] } +log = { workspace = true, default-features = false } +scale-info = { workspace = true, default-features = false, features = ["derive"] } +serde = { workspace = true, optional = true } + +pallet-issuance = { path = "../issuance", default-features = false, optional = true } + +# Substrate +frame-benchmarking = { workspace = true, default-features = false, optional = true } +frame-support = { workspace = true, default-features = false } +frame-system = { workspace = true, default-features = false } +mangata-support = { workspace = true, default-features = false } +mangata-types = { workspace = true, default-features = false } +pallet-authorship = { workspace = true, default-features = false } +pallet-collective-mangata = { workspace = true, default-features = false } +pallet-session = { workspace = true, default-features = false } +sp-arithmetic = { workspace = true, default-features = false } +sp-runtime = { workspace = true, default-features = false } +sp-staking = { workspace = true, default-features = false } +sp-std = { workspace = true, default-features = false } + +orml-tokens = { workspace = true, default-features = false } + +[dev-dependencies] +similar-asserts.workspace = true + +pallet-vesting-mangata = { workspace = true, default-features = false } +sp-core = { workspace = true, default-features = false } +sp-io = { workspace = true, default-features = false } + +orml-traits = { workspace = true, default-features = false } + +[features] +default = ["std"] +std = [ + "codec/std", + "frame-benchmarking/std", + "frame-support/std", + "frame-system/std", + "mangata-support/std", + "mangata-types/std", + "orml-tokens/std", + "pallet-authorship/std", + "pallet-session/std", + "scale-info/std", + "serde", + "sp-runtime/std", + "sp-staking/std", + "sp-std/std", +] +runtime-benchmarks = [ + "frame-benchmarking/runtime-benchmarks", + "frame-support/runtime-benchmarks", + "frame-system/runtime-benchmarks", + "pallet-collective-mangata/runtime-benchmarks", + "pallet-issuance", +] +try-runtime = ["frame-support/try-runtime", "frame-system/try-runtime"] diff --git a/gasp-node/pallets/parachain-staking/README.md b/gasp-node/pallets/parachain-staking/README.md new file mode 100644 index 000000000..ff83118cd --- /dev/null +++ b/gasp-node/pallets/parachain-staking/README.md @@ -0,0 +1,26 @@ +# DPoS Pallet for Parachain Staking + +## Formatting Rules + +- dependencies in alphabetical order in the `Cargo.toml` and at the top of each file +- prefer explicit imports to glob import syntax i.e. prefer `use::crate::{Ex1, Ex2, ..};` to `use super::*;` + +## Description + +Implements Delegated Proof of Stake to + +1. select the active set of eligible block producers +2. reward block authors +3. enable delegators and collators to participate in inflationary rewards + +Links: + +- [Rust Documentation](https://purestake.github.io/moonbeam/parachain_staking/index.html) +- [Unofficial Documentation](https://meta5.world/parachain-staking-docs/) +- [(Outdated) Blog Post with Justification](https://meta5.world/posts/parachain-staking) + +## History + +Since January 2021, Moonbeam's team has maintained this Delegated Proof of Stake (DPoS) pallet designed specifically for parachains. + +Since April 2021, the development of this pallet has been supported by [a Web3 Foundation grant](https://github.com/w3f/Grants-Program/pull/389). The [first milestone](https://github.com/w3f/Grant-Milestone-Delivery/pull/218) was approved in June 2021. diff --git a/gasp-node/pallets/parachain-staking/migrations.md b/gasp-node/pallets/parachain-staking/migrations.md new file mode 100644 index 000000000..d2277ffcd --- /dev/null +++ b/gasp-node/pallets/parachain-staking/migrations.md @@ -0,0 +1,42 @@ +# Migration History + +## Calculate outgoing rewards based on pending revoke and decrease changes + +- [Migration PR `#1408`](https://github.com/PureStake/moonbeam/pull/1408) + +## Patch delegations total mismatch + +- [Migration PR `#1291`](https://github.com/PureStake/moonbeam/pull/1291) + +## Split candidate state for PoV optimization + +- [Migration PR `#1117`](https://github.com/PureStake/moonbeam/pull/1117) + +## Increase max delegations per candidate + +- [Migration PR `#1096`](https://github.com/PureStake/moonbeam/pull/1096) +- [Migratio bugfix `#1112`](https://github.com/PureStake/moonbeam/pull/1112) + +## Manual Exits and Patch Lack of Delay for bond\_{more, less} + +- [Migration PR `#810`](https://github.com/PureStake/moonbeam/pull/810) +- [Migration Removal PR `#?`]() + +## Purge Stale Storage + +- [Migration PR `#970`](https://github.com/PureStake/moonbeam/pull/970) + +## Delay nominator exits by changing NominatorState and ExitQueue + +- [Migration PR `#610`](https://github.com/PureStake/moonbeam/pull/610) +- [Migration Removal PR `#662`](https://github.com/PureStake/moonbeam/pull/662) + +## Patch nomination DOS attack vector by changing CollatorState + +- [Migration PR `#505`](https://github.com/PureStake/moonbeam/pull/505) +- [Migration Removal PR `#553`](https://github.com/PureStake/moonbeam/pull/553) + +## Patch underflow bug and correct Total storage item + +- [Migration PR `#502`](https://github.com/PureStake/moonbeam/pull/502) +- [Migration Removal PR `#553`](https://github.com/PureStake/moonbeam/pull/553) diff --git a/gasp-node/pallets/parachain-staking/src/benchmarks.rs b/gasp-node/pallets/parachain-staking/src/benchmarks.rs new file mode 100644 index 000000000..b3b12c43c --- /dev/null +++ b/gasp-node/pallets/parachain-staking/src/benchmarks.rs @@ -0,0 +1,1925 @@ +#![cfg(feature = "runtime-benchmarks")] + +//! Benchmarking +// use crate::{ +// BalanceOf, Call, CandidateBondChange, CandidateBondRequest, Config, DelegationChange, +// DelegationRequest, Pallet, Range, +// }; +use crate::*; +use frame_benchmarking::{account, benchmarks}; +use frame_support::{ + assert_ok, + traits::{ExistenceRequirement, Get}, +}; +use frame_system::RawOrigin; +use itertools::Itertools; +use mangata_support::traits::{ProofOfStakeRewardsApi, XykFunctionsTrait}; +use orml_tokens::MultiTokenCurrencyExtended; +use pallet_authorship::EventHandler; +use sp_runtime::Perbill; +use sp_std::vec::Vec; + +const DOLLAR: u128 = 1__000_000_000_000_000_000u128; +const MGA_TOKEN_ID: u32 = 0u32; + +trait ToBalance { + fn to_balance(self) -> BalanceOf; +} + +impl ToBalance for u128 { + fn to_balance(self) -> BalanceOf { + self.try_into().ok().expect("u128 should fit into Balance type") + } +} + +/// We assume +/// Mga token is token id 0 +/// Not more than 100 curated tokens +/// Not more than 1000 candidates + +/// To maintain simplicity, creating a pool and using resulting liqudity tokens to stake have been separated +/// To do this we mint tokens and create pools using one user, the funding account +/// And then distribute the liquidity tokens to various users +/// For each liquidity token, two additional tokens must be created +/// (Token n, Token n+1) <=> Token n+2 +/// Starting from n0=5 as the first 4 are taken by the genesis config, but the starting n0 will be determined at the start of each bench by checking tokens +/// Any set of tokens x, x0=0, will have token_id, (3x+5, 3x+6) <=> 3x+7 +/// Since we are creating new tokens every time we can simply just use (v, v+1) as the pooled token amounts, to mint v liquidity tokens + +// pub(crate) fn payout_collator_for_round( +// n: u32, +// ) { +// let dummy_user: T::AccountId = account("dummy", 0u32, 0u32); +// let collators: Vec<::AccountId> = +// RoundCollatorRewardInfo::::iter_key_prefix(n).collect(); +// for collator in collators.iter() { +// Pallet::::payout_collator_rewards( +// RawOrigin::Signed(dummy_user.clone()).into(), +// n.try_into().unwrap(), +// collator.clone(), +// <::MaxDelegatorsPerCandidate as Get>::get(), +// ); +// } +// } + +/// Mint v liquidity tokens of token set x to funding account +fn create_non_staking_liquidity_for_funding< + T: Config, CurrencyId = CurrencyIdOf>, +>( + v: Option>, +) -> Result, DispatchError> { + let funding_account: T::AccountId = account("funding", 0u32, 0u32); + let x = T::Currency::get_next_currency_id(); + let v = v.unwrap_or((1_000_000_000_000_000_000 * DOLLAR).to_balance::()); + T::Currency::create(&funding_account, v)?; + T::Currency::create(&funding_account, v + 1u32.into())?; + + assert!( + T::Xyk::create_pool(funding_account.clone(), x, v, x + 1_u32.into(), v + 1_u32.into(),) + .is_ok() + ); + + assert_eq!(T::Currency::total_balance(x + 2u32.into(), &funding_account), v); + Ok(x + 2u32.into()) +} + +/// Mint v liquidity tokens of token set x to funding account +fn create_staking_liquidity_for_funding< + T: Config, CurrencyId = CurrencyIdOf>, +>( + v: Option>, +) -> Result, DispatchError> { + let funding_account: T::AccountId = account("funding", 0u32, 0u32); + let x = T::Currency::get_next_currency_id(); + let v = v.unwrap_or((1_000_000_000_000_000_000 * DOLLAR).to_balance::()); + T::Currency::mint(MGA_TOKEN_ID.into(), &funding_account, v)?; + T::Currency::create(&funding_account, v + 1u32.into())?; + + assert!(T::Xyk::create_pool( + funding_account.clone(), + MGA_TOKEN_ID.into(), + v, + x, + v + 1_u32.into(), + ) + .is_ok()); + + assert_eq!(T::Currency::total_balance(x + 1u32.into(), &funding_account), v); + Ok(x + 1u32.into()) +} + +/// Create a funded user. +/// Extra + min_candidate_stk is total minted funds +/// Returns tuple (id, balance) +fn create_funded_user, CurrencyId = CurrencyIdOf>>( + string: &'static str, + n: u32, + token_id: CurrencyIdOf, + v: Option>, +) -> (T::AccountId, CurrencyIdOf, BalanceOf) { + let funding_account: T::AccountId = account("funding", 0u32, 0u32); + const SEED: u32 = 0; + let user = account(string, n, SEED); + log::debug!("create user {}-{}-{}", string, n, SEED); + let v = v.unwrap_or((1_000_000_000 * DOLLAR).to_balance::()); + assert_ok!(T::Currency::transfer( + token_id.into(), + &funding_account, + &user, + v, + ExistenceRequirement::AllowDeath + )); + (user, token_id, v) +} + +/// Create a funded delegator. +fn create_funded_delegator, CurrencyId = CurrencyIdOf>>( + string: &'static str, + n: u32, + collator: T::AccountId, + collator_token_id: CurrencyIdOf, + v: Option>, + collator_delegator_count: u32, +) -> Result { + let (user, _, v) = create_funded_user::(string, n, collator_token_id, v); + Pallet::::delegate( + RawOrigin::Signed(user.clone()).into(), + collator, + v, + None, + collator_delegator_count, + 0u32, // first delegation for all calls + )?; + Ok(user) +} + +/// Create a funded collator. +fn create_funded_collator, CurrencyId = CurrencyIdOf>>( + string: &'static str, + n: u32, + token_id: CurrencyIdOf, + v: Option>, + candidate_count: u32, + liquidity_token_count: u32, +) -> Result { + let (user, token_id, v) = create_funded_user::(string, n, token_id, v); + Pallet::::join_candidates( + RawOrigin::Signed(user.clone()).into(), + v, + token_id, + None, + candidate_count, + liquidity_token_count, + )?; + Ok(user) +} + +pub(crate) fn roll_to_round_and_author( + n: u32, + author: Option, +) { + let current_round: u32 = Pallet::::round().current; + + while !(Pallet::::round().current >= n + current_round as u32 + 1u32) { + as frame_support::traits::Hooks<_>>::on_finalize(>::block_number()); + as frame_support::traits::Hooks<_>>::on_finalize( + >::block_number(), + ); + >::set_block_number( + >::block_number() + 1u32.into(), + ); + as frame_support::traits::Hooks<_>>::on_initialize( + >::block_number(), + ); + if author.clone().is_some() { + Pallet::::note_author(author.clone().unwrap().clone()); + } + as frame_support::traits::Hooks<_>>::on_initialize( + >::block_number(), + ); + if as pallet_session::ShouldEndSession<_>>::should_end_session( + >::block_number(), + ) { + // This doesn't really use pallet_session::Pallet::::current_index() + // especially since pallet_session's on_initialize is not triggered (session index will always be 0) + // But Staking's start session doesn't care as long as it isn't session 0 + as pallet_session::SessionManager<_>>::start_session( + pallet_session::Pallet::::current_index() as u32 + 1u32, + ); + } + } + + // Assumes round is atleast 3 blocks + // Roll to 2 blocks into the given round + for _i in 0..2 { + as frame_support::traits::Hooks<_>>::on_finalize(>::block_number()); + as frame_support::traits::Hooks<_>>::on_finalize( + >::block_number(), + ); + >::set_block_number( + >::block_number() + 1u32.into(), + ); + as frame_support::traits::Hooks<_>>::on_initialize( + >::block_number(), + ); + if author.clone().is_some() { + Pallet::::note_author(author.clone().unwrap().clone()); + } + as frame_support::traits::Hooks<_>>::on_initialize( + >::block_number(), + ); + } +} + +const USER_SEED: u32 = 999666; +const DUMMY_COUNT: u32 = 999666; + +benchmarks! { + where_clause { where T: Config, CurrencyId = CurrencyIdOf> } + // ROOT DISPATCHABLES + + set_total_selected {}: _(RawOrigin::Root, 100u32) + verify { + assert_eq!(Pallet::::total_selected(), 100u32); + } + + set_collator_commission {}: _(RawOrigin::Root, Perbill::from_percent(33)) + verify { + assert_eq!(Pallet::::collator_commission(), Perbill::from_percent(33)); + } + + // USER DISPATCHABLES + + join_candidates { + let x in 3_u32..(<::MaxCollatorCandidates as Get>::get() - 1u32); + let y in 3_u32..100; + + // Worst Case Complexity is search into a list so \exists full list before call + let liquidity_token_count: u32 = Pallet::::staking_liquidity_tokens().len().try_into().unwrap(); + assert!(y > liquidity_token_count); + for i in liquidity_token_count..(y - 1u32){ + let liquidity_token_id = create_staking_liquidity_for_funding::(Some(T::MinCandidateStk::get())).unwrap(); + Pallet::::add_staking_liquidity_token(RawOrigin::Root.into(), PairedOrLiquidityToken::Liquidity(liquidity_token_id), i)?; + } + + let created_liquidity_token = + create_staking_liquidity_for_funding::(Some(T::MinCandidateStk::get() * x.into())).unwrap(); + + assert_ok!(Pallet::::add_staking_liquidity_token(RawOrigin::Root.into(), PairedOrLiquidityToken::Liquidity(created_liquidity_token), y - 1)); + + + let candidate_count: u32 = Pallet::::candidate_pool().0.len().try_into().unwrap(); + assert!(x >= candidate_count); + + // Worst Case Complexity is insertion into an ordered list so \exists full list before call + + for i in candidate_count..x { + let seed = USER_SEED - i; + let res = create_funded_collator::( + "collator", + seed, + created_liquidity_token, + Some(T::MinCandidateStk::get()), + candidate_count + i, + y + ); + if res.is_err(){ + let res_str: &str = res.unwrap_err().try_into().unwrap(); + log::debug!("res_str: {:?}", res_str); + } else { + let collator = res.unwrap(); + } + } + let (caller, _, _) = create_funded_user::("caller", USER_SEED, created_liquidity_token, Some(T::MinCandidateStk::get())); + }: _(RawOrigin::Signed(caller.clone()), T::MinCandidateStk::get(), created_liquidity_token, None, x, y) + verify { + assert!(Pallet::::is_candidate(&caller)); + } + + // This call schedules the collator's exit and removes them from the candidate pool + // -> it retains the self-bond and delegator bonds + schedule_leave_candidates { + let x in 3..(<::MaxCollatorCandidates as Get>::get() - 1u32); + + let created_liquidity_token = + create_staking_liquidity_for_funding::(None).unwrap(); + + let mut liquidity_token_count: u32 = Pallet::::staking_liquidity_tokens().len().try_into().unwrap(); + + assert_ok!(Pallet::::add_staking_liquidity_token(RawOrigin::Root.into(), PairedOrLiquidityToken::Liquidity(created_liquidity_token), liquidity_token_count)); + + liquidity_token_count = liquidity_token_count + 1u32; + + let candidate_count: u32 = Pallet::::candidate_pool().0.len().try_into().unwrap(); + assert!(x >= candidate_count); + + // Worst Case Complexity is insertion into an ordered list so \exists full list before call + + for i in candidate_count..(x - 1u32) { + let seed = USER_SEED - i; + let collator = create_funded_collator::( + "collator", + seed, + created_liquidity_token, + None, + i, + liquidity_token_count + )?; + } + let caller = create_funded_collator::("caller", USER_SEED, created_liquidity_token, None, x - 1u32, liquidity_token_count)?; + + }: _(RawOrigin::Signed(caller.clone()), x) + verify { + assert!(Pallet::::candidate_state(&caller).unwrap().is_leaving()); + } + + execute_leave_candidates { + // x is total number of delegations for the candidate + let x in 2..(<::MaxTotalDelegatorsPerCandidate as Get>::get()); + + let created_liquidity_token = + create_staking_liquidity_for_funding::(None).unwrap(); + + let mut liquidity_token_count: u32 = Pallet::::staking_liquidity_tokens().len().try_into().unwrap(); + + assert_ok!(Pallet::::add_staking_liquidity_token(RawOrigin::Root.into(), PairedOrLiquidityToken::Liquidity(created_liquidity_token), liquidity_token_count)); + + liquidity_token_count = liquidity_token_count + 1u32; + + let candidate_count: u32 = Pallet::::candidate_pool().0.len().try_into().unwrap(); + + let candidate: T::AccountId = create_funded_collator::( + "unique_caller", + USER_SEED - 100, + created_liquidity_token, + None, + candidate_count + 1u32, + liquidity_token_count, + )?; + // 2nd delegation required for all delegators to ensure DelegatorState updated not removed + let second_candidate: T::AccountId = create_funded_collator::( + "unique__caller", + USER_SEED - 99, + created_liquidity_token, + None, + candidate_count + 2u32, + liquidity_token_count, + )?; + + let mut delegators: Vec = Vec::new(); + let mut col_del_count = 0u32; + for i in 1..x { + let seed = USER_SEED + i; + let delegator = create_funded_delegator::( + "delegator", + seed, + candidate.clone(), + created_liquidity_token, + None, + col_del_count, + )?; + assert_ok!(T::Currency::transfer(created_liquidity_token, &account("funding", 0u32, 0u32), &delegator, (100*DOLLAR).to_balance::(), ExistenceRequirement::AllowDeath)); + assert_ok!(Pallet::::delegate( + RawOrigin::Signed(delegator.clone()).into(), + second_candidate.clone(), + (100*DOLLAR).to_balance::(), + None, + col_del_count, + 1u32 + )); + assert_ok!(Pallet::::schedule_revoke_delegation( + RawOrigin::Signed(delegator.clone()).into(), + candidate.clone() + )); + delegators.push(delegator); + col_del_count += 1u32; + } + assert_ok!(Pallet::::schedule_leave_candidates( + RawOrigin::Signed(candidate.clone()).into(), + candidate_count + 3u32 + )); + roll_to_round_and_author::(2, Some(candidate.clone())); + }: _(RawOrigin::Signed(candidate.clone()), candidate.clone(), col_del_count) + verify { + assert!(Pallet::::candidate_state(&candidate).is_none()); + assert!(Pallet::::candidate_state(&second_candidate).is_some()); + for delegator in delegators { + assert!(Pallet::::is_delegator(&delegator)); + } + } + + cancel_leave_candidates { + let x in 3..(<::MaxCollatorCandidates as Get>::get() - 1u32); + let created_liquidity_token = + create_staking_liquidity_for_funding::(None).unwrap(); + + let mut liquidity_token_count: u32 = Pallet::::staking_liquidity_tokens().len().try_into().unwrap(); + + assert_ok!(Pallet::::add_staking_liquidity_token(RawOrigin::Root.into(), PairedOrLiquidityToken::Liquidity(created_liquidity_token), liquidity_token_count)); + + liquidity_token_count = liquidity_token_count + 1u32; + + // Worst Case Complexity is removal from an ordered list so \exists full list before call + let mut candidate_count = Pallet::::candidate_pool().0.len().try_into().unwrap(); + for i in 2..x { + let seed = USER_SEED - i; + let collator = create_funded_collator::( + "collator", + seed, + created_liquidity_token, + None, + candidate_count, + liquidity_token_count, + )?; + candidate_count += 1u32; + } + let caller: T::AccountId = create_funded_collator::( + "caller", + USER_SEED, + created_liquidity_token, + None, + candidate_count, + liquidity_token_count, + )?; + candidate_count += 1u32; + Pallet::::schedule_leave_candidates( + RawOrigin::Signed(caller.clone()).into(), + candidate_count + )?; + candidate_count -= 1u32; + }: _(RawOrigin::Signed(caller.clone()), candidate_count) + verify { + assert!(Pallet::::candidate_state(&caller).unwrap().is_active()); + } + + go_offline { + let created_liquidity_token = + create_staking_liquidity_for_funding::(None).unwrap(); + + let mut liquidity_token_count: u32 = Pallet::::staking_liquidity_tokens().len().try_into().unwrap(); + + assert_ok!(Pallet::::add_staking_liquidity_token(RawOrigin::Root.into(), PairedOrLiquidityToken::Liquidity(created_liquidity_token), liquidity_token_count)); + + liquidity_token_count = liquidity_token_count + 1u32; + let candidate_count: u32 = Pallet::::candidate_pool().0.len().try_into().unwrap(); + + let caller: T::AccountId = create_funded_collator::( + "collator", + USER_SEED, + created_liquidity_token, + None, + candidate_count, + liquidity_token_count, + )?; + }: _(RawOrigin::Signed(caller.clone())) + verify { + assert!(!Pallet::::candidate_state(&caller).unwrap().is_active()); + } + + go_online { + let created_liquidity_token = + create_staking_liquidity_for_funding::(None).unwrap(); + + let mut liquidity_token_count: u32 = Pallet::::staking_liquidity_tokens().len().try_into().unwrap(); + + assert_ok!(Pallet::::add_staking_liquidity_token(RawOrigin::Root.into(), PairedOrLiquidityToken::Liquidity(created_liquidity_token), liquidity_token_count)); + + liquidity_token_count = liquidity_token_count + 1u32; + + let candidate_count: u32 = Pallet::::candidate_pool().0.len().try_into().unwrap(); + + let caller: T::AccountId = create_funded_collator::( + "collator", + USER_SEED, + created_liquidity_token, + None, + candidate_count, + liquidity_token_count, + )?; + Pallet::::go_offline(RawOrigin::Signed(caller.clone()).into())?; + }: _(RawOrigin::Signed(caller.clone())) + verify { + assert!(Pallet::::candidate_state(&caller).unwrap().is_active()); + } + + schedule_candidate_bond_more { + let created_liquidity_token = + create_staking_liquidity_for_funding::(None).unwrap(); + + let mut liquidity_token_count: u32 = Pallet::::staking_liquidity_tokens().len().try_into().unwrap(); + + assert_ok!(Pallet::::add_staking_liquidity_token(RawOrigin::Root.into(), PairedOrLiquidityToken::Liquidity(created_liquidity_token), liquidity_token_count)); + + liquidity_token_count = liquidity_token_count + 1u32; + + let candidate_count: u32 = Pallet::::candidate_pool().0.len().try_into().unwrap(); + + let more = (10*DOLLAR).to_balance::(); + let caller: T::AccountId = create_funded_collator::( + "collator", + USER_SEED, + created_liquidity_token, + None, + candidate_count, + liquidity_token_count, + )?; + assert_ok!(T::Currency::transfer(created_liquidity_token, &account("funding", 0u32, 0u32), &caller, more, ExistenceRequirement::AllowDeath)); + + }: _(RawOrigin::Signed(caller.clone()), more, None) + verify { + let state = Pallet::::candidate_state(&caller).expect("request bonded more so exists"); + assert_eq!( + state.request, + Some(CandidateBondRequest { + amount: more, + change: CandidateBondChange::Increase, + when_executable: 2, + }) + ); + } + + schedule_candidate_bond_less { + let created_liquidity_token = + create_staking_liquidity_for_funding::(None).unwrap(); + + let mut liquidity_token_count: u32 = Pallet::::staking_liquidity_tokens().len().try_into().unwrap(); + + assert_ok!(Pallet::::add_staking_liquidity_token(RawOrigin::Root.into(), PairedOrLiquidityToken::Liquidity(created_liquidity_token), liquidity_token_count)); + + liquidity_token_count = liquidity_token_count + 1u32; + + let candidate_count: u32 = Pallet::::candidate_pool().0.len().try_into().unwrap(); + + let less = (10*DOLLAR).to_balance::(); + let caller: T::AccountId = create_funded_collator::( + "collator", + USER_SEED, + created_liquidity_token, + None, + candidate_count, + liquidity_token_count, + )?; + }: _(RawOrigin::Signed(caller.clone()), less) + verify { + let state = Pallet::::candidate_state(&caller).expect("request bonded less so exists"); + assert_eq!( + state.request, + Some(CandidateBondRequest { + amount: less, + change: CandidateBondChange::Decrease, + when_executable: 2, + }) + ); + } + + execute_candidate_bond_more { + let created_liquidity_token = + create_staking_liquidity_for_funding::(None).unwrap(); + + let mut liquidity_token_count: u32 = Pallet::::staking_liquidity_tokens().len().try_into().unwrap(); + + assert_ok!(Pallet::::add_staking_liquidity_token(RawOrigin::Root.into(), PairedOrLiquidityToken::Liquidity(created_liquidity_token), liquidity_token_count)); + + liquidity_token_count = liquidity_token_count + 1u32; + + let candidate_count: u32 = Pallet::::candidate_pool().0.len().try_into().unwrap(); + + let more = (10*DOLLAR).to_balance::(); + let caller: T::AccountId = create_funded_collator::( + "collator", + USER_SEED, + created_liquidity_token, + None, + candidate_count, + liquidity_token_count, + )?; + assert_ok!(T::Currency::transfer(created_liquidity_token, &account("funding", 0u32, 0u32), &caller, more, ExistenceRequirement::AllowDeath)); + + Pallet::::schedule_candidate_bond_more( + RawOrigin::Signed(caller.clone()).into(), + more, + None + )?; + roll_to_round_and_author::(2, Some(caller.clone())); + }: { + Pallet::::execute_candidate_bond_request( + RawOrigin::Signed(caller.clone()).into(), + caller.clone(), + None + )?; + } verify { + let expected_bond = 1000000010* DOLLAR; + assert_eq!(::Currency::reserved_balance(created_liquidity_token, &caller).into(), expected_bond); + } + + execute_candidate_bond_less { + let created_liquidity_token = + create_staking_liquidity_for_funding::(None).unwrap(); + + let mut liquidity_token_count: u32 = Pallet::::staking_liquidity_tokens().len().try_into().unwrap(); + + assert_ok!(Pallet::::add_staking_liquidity_token(RawOrigin::Root.into(), PairedOrLiquidityToken::Liquidity(created_liquidity_token), liquidity_token_count)); + + liquidity_token_count = liquidity_token_count + 1u32; + + let candidate_count: u32 = Pallet::::candidate_pool().0.len().try_into().unwrap(); + + let less = (10*DOLLAR).to_balance::(); + let caller: T::AccountId = create_funded_collator::( + "collator", + USER_SEED, + created_liquidity_token, + None, + candidate_count, + liquidity_token_count, + )?; + Pallet::::schedule_candidate_bond_less( + RawOrigin::Signed(caller.clone()).into(), + less + )?; + roll_to_round_and_author::(2, Some(caller.clone())); + }: { + Pallet::::execute_candidate_bond_request( + RawOrigin::Signed(caller.clone()).into(), + caller.clone(), + None + )?; + } verify { + assert_eq!(::Currency::reserved_balance(created_liquidity_token.into(), &caller).into(), 999999990*DOLLAR); + } + + cancel_candidate_bond_more { + let created_liquidity_token = + create_staking_liquidity_for_funding::(None).unwrap(); + + let mut liquidity_token_count: u32 = Pallet::::staking_liquidity_tokens().len().try_into().unwrap(); + + assert_ok!(Pallet::::add_staking_liquidity_token(RawOrigin::Root.into(), PairedOrLiquidityToken::Liquidity(created_liquidity_token), liquidity_token_count)); + + liquidity_token_count = liquidity_token_count + 1u32; + + let candidate_count: u32 = Pallet::::candidate_pool().0.len().try_into().unwrap(); + + let more = (10*DOLLAR).to_balance::(); + let caller: T::AccountId = create_funded_collator::( + "collator", + USER_SEED, + created_liquidity_token, + None, + candidate_count, + liquidity_token_count, + )?; + assert_ok!(T::Currency::transfer(created_liquidity_token, &account("funding", 0u32, 0u32), &caller, more, ExistenceRequirement::AllowDeath)); + + Pallet::::schedule_candidate_bond_more( + RawOrigin::Signed(caller.clone()).into(), + more, + None + )?; + }: { + Pallet::::cancel_candidate_bond_request( + RawOrigin::Signed(caller.clone()).into(), + )?; + } verify { + assert!( + Pallet::::candidate_state(&caller).unwrap().request.is_none() + ); + } + + cancel_candidate_bond_less { + let created_liquidity_token = + create_staking_liquidity_for_funding::(None).unwrap(); + + let mut liquidity_token_count: u32 = Pallet::::staking_liquidity_tokens().len().try_into().unwrap(); + + assert_ok!(Pallet::::add_staking_liquidity_token(RawOrigin::Root.into(), PairedOrLiquidityToken::Liquidity(created_liquidity_token), liquidity_token_count)); + + liquidity_token_count = liquidity_token_count + 1u32; + + let candidate_count: u32 = Pallet::::candidate_pool().0.len().try_into().unwrap(); + + let less = (10*DOLLAR).to_balance::(); + let caller: T::AccountId = create_funded_collator::( + "collator", + USER_SEED, + created_liquidity_token, + None, + candidate_count, + liquidity_token_count, + )?; + Pallet::::schedule_candidate_bond_less( + RawOrigin::Signed(caller.clone()).into(), + less + )?; + }: { + Pallet::::cancel_candidate_bond_request( + RawOrigin::Signed(caller.clone()).into(), + )?; + } verify { + assert!( + Pallet::::candidate_state(&caller).unwrap().request.is_none() + ); + } + + delegate { + let x in 3..(<::MaxDelegationsPerDelegator as Get>::get().min(<::MaxCollatorCandidates as Get>::get() - 2u32)); + let y in 2..<::MaxDelegatorsPerCandidate as Get>::get(); + + let created_liquidity_token = + create_staking_liquidity_for_funding::(None).unwrap(); + + let mut liquidity_token_count: u32 = Pallet::::staking_liquidity_tokens().len().try_into().unwrap(); + + assert_ok!(Pallet::::add_staking_liquidity_token(RawOrigin::Root.into(), PairedOrLiquidityToken::Liquidity(created_liquidity_token), liquidity_token_count)); + + liquidity_token_count = liquidity_token_count + 1u32; + + let candidate_count: u32 = Pallet::::candidate_pool().0.len().try_into().unwrap(); + + // Worst Case is full of delegations before calling `delegate` + let mut collators: Vec = Vec::new(); + // Initialize MaxDelegationsPerDelegator collator candidates + for i in 2..x { + let seed = USER_SEED - i; + let collator = create_funded_collator::( + "collator", + seed, + created_liquidity_token, + None, + collators.len() as u32 + candidate_count, + liquidity_token_count, + )?; + collators.push(collator.clone()); + } + + let (caller, _, _) = create_funded_user::("caller", USER_SEED, created_liquidity_token, Some((100 * DOLLAR * (collators.len() as u128 + 1u128) + 1u128).to_balance::())); + // Delegation count + let mut del_del_count = 0u32; + // Nominate MaxDelegationsPerDelegators collator candidates + for col in collators.clone() { + Pallet::::delegate( + RawOrigin::Signed(caller.clone()).into(), col, (100 * DOLLAR).to_balance::(), None, 0u32, del_del_count + )?; + del_del_count += 1u32; + } + // Last collator to be delegated + let collator: T::AccountId = create_funded_collator::( + "collator", + USER_SEED, + created_liquidity_token, + None, + collators.len() as u32 + candidate_count + 1, + liquidity_token_count, + )?; + // Worst Case Complexity is insertion into an almost full collator + let mut col_del_count = 0u32; + for i in 1..y { + let seed = USER_SEED + i; + let _ = create_funded_delegator::( + "delegator", + seed, + collator.clone(), + created_liquidity_token, + None, + col_del_count, + )?; + col_del_count += 1u32; + } + }: _(RawOrigin::Signed(caller.clone()), collator, (100*DOLLAR + 1u128).to_balance::(), None, col_del_count, del_del_count) + verify { + assert!(Pallet::::is_delegator(&caller)); + } + + schedule_leave_delegators { + let created_liquidity_token = + create_staking_liquidity_for_funding::(None).unwrap(); + + let mut liquidity_token_count: u32 = Pallet::::staking_liquidity_tokens().len().try_into().unwrap(); + + assert_ok!(Pallet::::add_staking_liquidity_token(RawOrigin::Root.into(), PairedOrLiquidityToken::Liquidity(created_liquidity_token), liquidity_token_count)); + + liquidity_token_count = liquidity_token_count + 1u32; + + let candidate_count: u32 = Pallet::::candidate_pool().0.len().try_into().unwrap(); + + let collator: T::AccountId = create_funded_collator::( + "collator", + USER_SEED, + created_liquidity_token, + None, + candidate_count, + liquidity_token_count, + )?; + let (caller, _, _) = create_funded_user::("caller", USER_SEED, created_liquidity_token, None); + Pallet::::delegate(RawOrigin::Signed( + caller.clone()).into(), + collator.clone(), + (100*DOLLAR).to_balance::(), + None, + 0u32, + 0u32 + )?; + }: _(RawOrigin::Signed(caller.clone())) + verify { + assert!(Pallet::::delegator_state(&caller).unwrap().is_leaving()); + } + + execute_leave_delegators { + let x in 2..(<::MaxDelegationsPerDelegator as Get>::get().min(<::MaxCollatorCandidates as Get>::get() - 2u32)); + let created_liquidity_token = + create_staking_liquidity_for_funding::(None).unwrap(); + + let mut liquidity_token_count: u32 = Pallet::::staking_liquidity_tokens().len().try_into().unwrap(); + + assert_ok!(Pallet::::add_staking_liquidity_token(RawOrigin::Root.into(), PairedOrLiquidityToken::Liquidity(created_liquidity_token), liquidity_token_count)); + + liquidity_token_count = liquidity_token_count + 1u32; + + let candidate_count: u32 = Pallet::::candidate_pool().0.len().try_into().unwrap(); + + // Worst Case is full of delegations before execute exit + let mut collators: Vec = Vec::new(); + // Initialize MaxDelegationsPerDelegator collator candidates + for i in 1..x { + let seed = USER_SEED - i; + let collator = create_funded_collator::( + "collator", + seed, + created_liquidity_token, + None, + collators.len() as u32 + candidate_count, + liquidity_token_count + )?; + collators.push(collator.clone()); + } + // Fund the delegator + let (caller, _, _) = create_funded_user::("caller", USER_SEED, created_liquidity_token, Some((100 * DOLLAR * (collators.len() as u128)).to_balance::())); + // Delegation count + let mut delegation_count = 0u32; + let author = collators[0].clone(); + // Nominate MaxDelegationsPerDelegators collator candidates + for col in collators { + Pallet::::delegate( + RawOrigin::Signed(caller.clone()).into(), + col, + (100*DOLLAR).to_balance::(), + None, + 0u32, + delegation_count + )?; + delegation_count += 1u32; + } + Pallet::::schedule_leave_delegators(RawOrigin::Signed(caller.clone()).into())?; + roll_to_round_and_author::(2, Some(author)); + }: _(RawOrigin::Signed(caller.clone()), caller.clone(), delegation_count) + verify { + assert!(Pallet::::delegator_state(&caller).is_none()); + } + + cancel_leave_delegators { + let created_liquidity_token = + create_staking_liquidity_for_funding::(None).unwrap(); + + let mut liquidity_token_count: u32 = Pallet::::staking_liquidity_tokens().len().try_into().unwrap(); + + assert_ok!(Pallet::::add_staking_liquidity_token(RawOrigin::Root.into(), PairedOrLiquidityToken::Liquidity(created_liquidity_token), liquidity_token_count)); + + liquidity_token_count = liquidity_token_count + 1u32; + + let candidate_count: u32 = Pallet::::candidate_pool().0.len().try_into().unwrap(); + + let collator: T::AccountId = create_funded_collator::( + "collator", + USER_SEED, + created_liquidity_token, + None, + candidate_count, + liquidity_token_count, + )?; + let (caller, _, v) = create_funded_user::("caller", USER_SEED, created_liquidity_token, None); + Pallet::::delegate(RawOrigin::Signed( + caller.clone()).into(), + collator.clone(), + v, + None, + 0u32, + 0u32 + )?; + Pallet::::schedule_leave_delegators(RawOrigin::Signed(caller.clone()).into())?; + }: _(RawOrigin::Signed(caller.clone())) + verify { + assert!(Pallet::::delegator_state(&caller).unwrap().is_active()); + } + + schedule_revoke_delegation { + let created_liquidity_token = + create_staking_liquidity_for_funding::(None).unwrap(); + + let mut liquidity_token_count: u32 = Pallet::::staking_liquidity_tokens().len().try_into().unwrap(); + + assert_ok!(Pallet::::add_staking_liquidity_token(RawOrigin::Root.into(), PairedOrLiquidityToken::Liquidity(created_liquidity_token), liquidity_token_count)); + + liquidity_token_count = liquidity_token_count + 1u32; + + let candidate_count: u32 = Pallet::::candidate_pool().0.len().try_into().unwrap(); + + let collator: T::AccountId = create_funded_collator::( + "collator", + USER_SEED, + created_liquidity_token, + None, + candidate_count, + liquidity_token_count, + )?; + let (caller, _, v) = create_funded_user::("caller", USER_SEED, created_liquidity_token, None); + Pallet::::delegate(RawOrigin::Signed( + caller.clone()).into(), + collator.clone(), + v, + None, + 0u32, + 0u32 + )?; + }: _(RawOrigin::Signed(caller.clone()), collator.clone()) + verify { + assert_eq!( + Pallet::::delegator_state(&caller).unwrap().requests().get(&collator), + Some(&DelegationRequest { + collator, + amount: v, + when_executable: 2, + action: DelegationChange::Revoke + }) + ); + } + + schedule_delegator_bond_more { + let created_liquidity_token = + create_staking_liquidity_for_funding::(None).unwrap(); + + let mut liquidity_token_count: u32 = Pallet::::staking_liquidity_tokens().len().try_into().unwrap(); + + assert_ok!(Pallet::::add_staking_liquidity_token(RawOrigin::Root.into(), PairedOrLiquidityToken::Liquidity(created_liquidity_token), liquidity_token_count)); + + liquidity_token_count = liquidity_token_count + 1u32; + + let candidate_count: u32 = Pallet::::candidate_pool().0.len().try_into().unwrap(); + + let collator: T::AccountId = create_funded_collator::( + "collator", + USER_SEED, + created_liquidity_token, + None, + candidate_count, + liquidity_token_count, + )?; + let (caller, _, v) = create_funded_user::("caller", USER_SEED, created_liquidity_token, None); + Pallet::::delegate( + RawOrigin::Signed(caller.clone()).into(), + collator.clone(), + v - (10*DOLLAR).to_balance::(), + None, + 0u32, + 0u32 + )?; + }: _(RawOrigin::Signed(caller.clone()), collator.clone(), (10*DOLLAR).to_balance::(), None) + verify { + let state = Pallet::::delegator_state(&caller) + .expect("just request bonded less so exists"); + assert_eq!( + state.requests().get(&collator), + Some(&DelegationRequest { + collator, + amount: (10*DOLLAR).to_balance::(), + when_executable: 2, + action: DelegationChange::Increase + }) + ); + } + + schedule_delegator_bond_less { + + let created_liquidity_token = + create_staking_liquidity_for_funding::(None).unwrap(); + + let mut liquidity_token_count: u32 = Pallet::::staking_liquidity_tokens().len().try_into().unwrap(); + + assert_ok!(Pallet::::add_staking_liquidity_token(RawOrigin::Root.into(), PairedOrLiquidityToken::Liquidity(created_liquidity_token), liquidity_token_count)); + + liquidity_token_count = liquidity_token_count + 1u32; + + let candidate_count: u32 = Pallet::::candidate_pool().0.len().try_into().unwrap(); + + let collator: T::AccountId = create_funded_collator::( + "collator", + USER_SEED, + created_liquidity_token, + None, + candidate_count, + liquidity_token_count, + )?; + let (caller, _, v) = create_funded_user::("caller", USER_SEED, created_liquidity_token, None); + Pallet::::delegate(RawOrigin::Signed( + caller.clone()).into(), + collator.clone(), + v, + None, + 0u32, + 0u32 + )?; + }: _(RawOrigin::Signed(caller.clone()), collator.clone(), (10*DOLLAR).to_balance::()) + verify { + let state = Pallet::::delegator_state(&caller) + .expect("just request bonded less so exists"); + assert_eq!( + state.requests().get(&collator), + Some(&DelegationRequest { + collator, + amount: (10*DOLLAR).to_balance::(), + when_executable: 2, + action: DelegationChange::Decrease + }) + ); + } + + execute_revoke_delegation { + + let created_liquidity_token = + create_staking_liquidity_for_funding::(None).unwrap(); + + let mut liquidity_token_count: u32 = Pallet::::staking_liquidity_tokens().len().try_into().unwrap(); + + assert_ok!(Pallet::::add_staking_liquidity_token(RawOrigin::Root.into(), PairedOrLiquidityToken::Liquidity(created_liquidity_token), liquidity_token_count)); + + liquidity_token_count = liquidity_token_count + 1u32; + + let candidate_count: u32 = Pallet::::candidate_pool().0.len().try_into().unwrap(); + + let collator: T::AccountId = create_funded_collator::( + "collator", + USER_SEED, + created_liquidity_token, + None, + candidate_count, + liquidity_token_count, + )?; + let (caller, _, v) = create_funded_user::("caller", USER_SEED, created_liquidity_token, None); + + Pallet::::delegate(RawOrigin::Signed( + caller.clone()).into(), + collator.clone(), + v, + None, + 0u32, + 0u32 + )?; + Pallet::::schedule_revoke_delegation(RawOrigin::Signed( + caller.clone()).into(), + collator.clone() + )?; + roll_to_round_and_author::(2, Some(collator.clone())); + }: { + Pallet::::execute_delegation_request( + RawOrigin::Signed(caller.clone()).into(), + caller.clone(), + collator.clone(), + None + )?; + } verify { + assert!( + !Pallet::::is_delegator(&caller) + ); + } + + execute_delegator_bond_more { + let created_liquidity_token = + create_staking_liquidity_for_funding::(None).unwrap(); + + let mut liquidity_token_count: u32 = Pallet::::staking_liquidity_tokens().len().try_into().unwrap(); + + assert_ok!(Pallet::::add_staking_liquidity_token(RawOrigin::Root.into(), PairedOrLiquidityToken::Liquidity(created_liquidity_token), liquidity_token_count)); + + liquidity_token_count = liquidity_token_count + 1u32; + + let candidate_count: u32 = Pallet::::candidate_pool().0.len().try_into().unwrap(); + + let collator: T::AccountId = create_funded_collator::( + "collator", + USER_SEED, + created_liquidity_token, + None, + candidate_count, + liquidity_token_count, + )?; + let (caller, _, v) = create_funded_user::("caller", USER_SEED, created_liquidity_token, None); + + Pallet::::delegate( + RawOrigin::Signed(caller.clone()).into(), + collator.clone(), + v - (10*DOLLAR).to_balance::(), + None, + 0u32, + 0u32 + )?; + Pallet::::schedule_delegator_bond_more( + RawOrigin::Signed(caller.clone()).into(), + collator.clone(), + (10*DOLLAR).to_balance::(), + None + )?; + roll_to_round_and_author::(2, Some(collator.clone())); + }: { + Pallet::::execute_delegation_request( + RawOrigin::Signed(caller.clone()).into(), + caller.clone(), + collator.clone(), + None + )?; + } verify { + let expected_bond = 1000000000* DOLLAR; + assert_eq!(::Currency::reserved_balance(created_liquidity_token.into(), &caller).into(), expected_bond); + } + + execute_delegator_bond_less { + + let created_liquidity_token = + create_staking_liquidity_for_funding::(None).unwrap(); + + let mut liquidity_token_count: u32 = Pallet::::staking_liquidity_tokens().len().try_into().unwrap(); + + assert_ok!(Pallet::::add_staking_liquidity_token(RawOrigin::Root.into(), PairedOrLiquidityToken::Liquidity(created_liquidity_token), liquidity_token_count)); + + liquidity_token_count = liquidity_token_count + 1u32; + + let candidate_count: u32 = Pallet::::candidate_pool().0.len().try_into().unwrap(); + + let collator: T::AccountId = create_funded_collator::( + "collator", + USER_SEED, + created_liquidity_token, + None, + candidate_count, + liquidity_token_count, + )?; + let (caller, _, v) = create_funded_user::("caller", USER_SEED, created_liquidity_token, None); + Pallet::::delegate(RawOrigin::Signed( + caller.clone()).into(), + collator.clone(), + v, + None, + 0u32, + 0u32 + )?; + let bond_less = (10*DOLLAR).to_balance::(); + Pallet::::schedule_delegator_bond_less( + RawOrigin::Signed(caller.clone()).into(), + collator.clone(), + bond_less + )?; + roll_to_round_and_author::(2, Some(collator.clone())); + }: { + Pallet::::execute_delegation_request( + RawOrigin::Signed(caller.clone()).into(), + caller.clone(), + collator.clone(), + None + )?; + } verify { + let expected = v - bond_less; + assert_eq!(::Currency::reserved_balance(created_liquidity_token.into(), &caller), expected); + } + + cancel_revoke_delegation { + let created_liquidity_token = + create_staking_liquidity_for_funding::(None).unwrap(); + + let mut liquidity_token_count: u32 = Pallet::::staking_liquidity_tokens().len().try_into().unwrap(); + + assert_ok!(Pallet::::add_staking_liquidity_token(RawOrigin::Root.into(), PairedOrLiquidityToken::Liquidity(created_liquidity_token), liquidity_token_count)); + + liquidity_token_count = liquidity_token_count + 1u32; + + let candidate_count: u32 = Pallet::::candidate_pool().0.len().try_into().unwrap(); + + let collator: T::AccountId = create_funded_collator::( + "collator", + USER_SEED, + created_liquidity_token, + None, + candidate_count, + liquidity_token_count, + )?; + let (caller, _, v) = create_funded_user::("caller", USER_SEED, created_liquidity_token, None); + + Pallet::::delegate(RawOrigin::Signed( + caller.clone()).into(), + collator.clone(), + v, + None, + 0u32, + 0u32 + )?; + Pallet::::schedule_revoke_delegation( + RawOrigin::Signed(caller.clone()).into(), + collator.clone() + )?; + }: { + Pallet::::cancel_delegation_request( + RawOrigin::Signed(caller.clone()).into(), + collator.clone() + )?; + } verify { + assert!( + Pallet::::delegator_state(&caller).unwrap().requests().get(&collator).is_none() + ); + } + + cancel_delegator_bond_more { + + let created_liquidity_token = + create_staking_liquidity_for_funding::(None).unwrap(); + + let mut liquidity_token_count: u32 = Pallet::::staking_liquidity_tokens().len().try_into().unwrap(); + + assert_ok!(Pallet::::add_staking_liquidity_token(RawOrigin::Root.into(), PairedOrLiquidityToken::Liquidity(created_liquidity_token), liquidity_token_count)); + + liquidity_token_count = liquidity_token_count + 1u32; + + let candidate_count: u32 = Pallet::::candidate_pool().0.len().try_into().unwrap(); + + let collator: T::AccountId = create_funded_collator::( + "collator", + USER_SEED, + created_liquidity_token, + None, + candidate_count, + liquidity_token_count, + )?; + let (caller, _, v) = create_funded_user::("caller", USER_SEED, created_liquidity_token, None); + + Pallet::::delegate( + RawOrigin::Signed(caller.clone()).into(), + collator.clone(), + v - (10*DOLLAR).to_balance::(), + None, + 0u32, + 0u32 + )?; + Pallet::::schedule_delegator_bond_more( + RawOrigin::Signed(caller.clone()).into(), + collator.clone(), + (10*DOLLAR).to_balance::(), + None + )?; + roll_to_round_and_author::(2, Some(collator.clone())); + }: { + Pallet::::cancel_delegation_request( + RawOrigin::Signed(caller.clone()).into(), + collator.clone() + )?; + } verify { + assert!( + Pallet::::delegator_state(&caller) + .unwrap() + .requests() + .get(&collator) + .is_none() + ); + } + + cancel_delegator_bond_less { + let created_liquidity_token = + create_staking_liquidity_for_funding::(None).unwrap(); + + let mut liquidity_token_count: u32 = Pallet::::staking_liquidity_tokens().len().try_into().unwrap(); + + assert_ok!(Pallet::::add_staking_liquidity_token(RawOrigin::Root.into(), PairedOrLiquidityToken::Liquidity(created_liquidity_token), liquidity_token_count)); + + liquidity_token_count = liquidity_token_count + 1u32; + + let candidate_count: u32 = Pallet::::candidate_pool().0.len().try_into().unwrap(); + + let collator: T::AccountId = create_funded_collator::( + "collator", + USER_SEED, + created_liquidity_token, + None, + candidate_count, + liquidity_token_count, + )?; + let (caller, _, total) = create_funded_user::("caller", USER_SEED, created_liquidity_token, None); + Pallet::::delegate(RawOrigin::Signed( + caller.clone()).into(), + collator.clone(), + total - (10*DOLLAR).to_balance::(), + None, + 0u32, + 0u32 + )?; + let bond_less = (10*DOLLAR).to_balance::(); + Pallet::::schedule_delegator_bond_less( + RawOrigin::Signed(caller.clone()).into(), + collator.clone(), + bond_less + )?; + roll_to_round_and_author::(2, Some(collator.clone())); + }: { + Pallet::::cancel_delegation_request( + RawOrigin::Signed(caller.clone()).into(), + collator.clone() + )?; + } verify { + assert!( + Pallet::::delegator_state(&caller) + .unwrap() + .requests() + .get(&collator) + .is_none() + ); + } + + add_staking_liquidity_token { + let x in 3..100; + + let liquidity_token_count: u32 = Pallet::::staking_liquidity_tokens().len().try_into().unwrap(); + assert!(x > liquidity_token_count); + for i in liquidity_token_count..(x){ + let liquidity_token_id = create_staking_liquidity_for_funding::(Some(T::MinCandidateStk::get())).unwrap(); + assert_ok!(Pallet::::add_staking_liquidity_token(RawOrigin::Root.into(), PairedOrLiquidityToken::Liquidity(liquidity_token_id), i)); + } + + let liquidity_token_id = create_staking_liquidity_for_funding::(Some(T::MinCandidateStk::get())).unwrap(); + + }: _(RawOrigin::Root, PairedOrLiquidityToken::Liquidity(liquidity_token_id), x) + verify { + assert!( + Pallet::::staking_liquidity_tokens() + .contains_key(&liquidity_token_id) + ); + } + + remove_staking_liquidity_token { + let x in 3..100; + + let liquidity_token_count: u32 = Pallet::::staking_liquidity_tokens().len().try_into().unwrap(); + assert!(x > liquidity_token_count); + for i in liquidity_token_count..(x - 1u32){ + let token_id = create_staking_liquidity_for_funding::(Some(T::MinCandidateStk::get())).unwrap(); + assert_ok!(Pallet::::add_staking_liquidity_token(RawOrigin::Root.into(), PairedOrLiquidityToken::Liquidity(token_id), i)); + } + + let token_id = create_staking_liquidity_for_funding::(Some(T::MinCandidateStk::get())).unwrap(); + assert_ok!(Pallet::::add_staking_liquidity_token(RawOrigin::Root.into(), PairedOrLiquidityToken::Liquidity(token_id), x - 1u32)); + + }: _(RawOrigin::Root, PairedOrLiquidityToken::Liquidity(token_id), x) + verify { + assert!( + !Pallet::::staking_liquidity_tokens() + .contains_key(&(token_id)) + ); + } + + aggregator_update_metadata { + + let x = <::MaxCollatorCandidates as Get>::get() - 2u32; // to account for the two candidates we start with + + + let start_liquidity_token_count: u32 = Pallet::::staking_liquidity_tokens().len().try_into().unwrap(); + + let initial_candidates: Vec = Pallet::::candidate_pool().0.into_iter().map(|x| x.owner).collect::<_>(); + let base_candidate_count: u32 = Pallet::::candidate_pool().0.len().try_into().unwrap(); + assert_eq!(base_candidate_count, 2); + + let mut candidates: Vec = Vec::::new(); + + + const SEED: u32 = 0; + + for i in 0u32..x{ + + let created_liquidity_token = + create_staking_liquidity_for_funding::(None).unwrap(); + + assert_ok!(Pallet::::add_staking_liquidity_token(RawOrigin::Root.into(), PairedOrLiquidityToken::Liquidity(created_liquidity_token), 1000)); + + let seed = USER_SEED - i; + let collator = create_funded_collator::( + "collator", + seed, + created_liquidity_token, + None, + candidates.len() as u32 + base_candidate_count, + 1000 + )?; + + candidates.push(collator.clone()); + + } + + let aggregator: T::AccountId = account("aggregator", 0u32, SEED); + assert_ok!(Pallet::::aggregator_update_metadata(RawOrigin::Signed( + aggregator.clone()).into(), + candidates.clone(), + MetadataUpdateAction::ExtendApprovedCollators + )); + + for i in 0u32..(x){ + + let seed = USER_SEED - i; + + let collator: T::AccountId = account("collator", seed, SEED); + assert_ok!(Pallet::::update_candidate_aggregator(RawOrigin::Signed( + collator.clone()).into(), + Some(aggregator.clone()), + )); + + } + + for i in 0u32..(x){ + + let seed = USER_SEED - i; + + let collator: T::AccountId = account("collator", seed, SEED); + assert_eq!(CandidateAggregator::::get() + .get(&collator).cloned(), + Some(aggregator.clone()), + ); + + } + + assert_eq!(AggregatorMetadata::::get(&aggregator).unwrap().token_collator_map.len(), x as usize); + assert_eq!(AggregatorMetadata::::get(&aggregator).unwrap().approved_candidates.len(), x as usize); + + }: _(RawOrigin::Signed(aggregator.clone()), candidates.clone(), MetadataUpdateAction::RemoveApprovedCollators) + verify { + + for i in 0u32..(x){ + + let seed = USER_SEED - i; + + let collator = account("collator", seed, SEED); + assert_eq!(CandidateAggregator::::get() + .get(&collator).cloned(), + None, + ); + + } + + assert_eq!(AggregatorMetadata::::get(&aggregator), None); + assert_eq!(AggregatorMetadata::::get(&aggregator), None); + + } + + update_candidate_aggregator { + + let x = <::MaxCollatorCandidates as Get>::get() - 2u32; // to account for the two candidates we start with + + + let start_liquidity_token_count: u32 = Pallet::::staking_liquidity_tokens().len().try_into().unwrap(); + + let initial_candidates: Vec = Pallet::::candidate_pool().0.into_iter().map(|x| x.owner).collect::<_>(); + let base_candidate_count: u32 = Pallet::::candidate_pool().0.len().try_into().unwrap(); + assert_eq!(base_candidate_count, 2); + + let mut candidates: Vec = Vec::::new(); + + const SEED: u32 = 0; + + for i in 0u32..x{ + + let created_liquidity_token = + create_staking_liquidity_for_funding::(None).unwrap(); + + assert_ok!(Pallet::::add_staking_liquidity_token(RawOrigin::Root.into(), PairedOrLiquidityToken::Liquidity(created_liquidity_token), 1000)); + + let seed = USER_SEED - i; + let collator = create_funded_collator::( + "collator", + seed, + created_liquidity_token, + None, + candidates.len() as u32 + base_candidate_count, + 1000 + )?; + + candidates.push(collator.clone()); + + } + + let aggregator: T::AccountId = account("aggregator", 0u32, SEED); + assert_ok!(Pallet::::aggregator_update_metadata(RawOrigin::Signed( + aggregator.clone()).into(), + candidates.clone(), + MetadataUpdateAction::ExtendApprovedCollators + )); + + for i in 1u32..(x){ + + let seed = USER_SEED - i; + + let collator: T::AccountId = account("collator", seed, SEED); + assert_ok!(Pallet::::update_candidate_aggregator(RawOrigin::Signed( + collator.clone()).into(), + Some(aggregator.clone()), + )); + + } + + for i in 1u32..(x){ + + let seed = USER_SEED - i; + + let collator: T::AccountId = account("collator", seed, SEED); + assert_eq!(CandidateAggregator::::get() + .get(&collator).cloned(), + Some(aggregator.clone()), + ); + + } + + let collator_switching: T::AccountId = account("collator", USER_SEED, SEED); + let aggregator_old: T::AccountId = account("aggregator", 1u32, SEED); + assert_ok!(Pallet::::aggregator_update_metadata(RawOrigin::Signed( + aggregator_old.clone()).into(), + vec![collator_switching.clone()], + MetadataUpdateAction::ExtendApprovedCollators + )); + + assert_ok!(Pallet::::update_candidate_aggregator(RawOrigin::Signed( + collator_switching.clone()).into(), + Some(aggregator_old.clone()), + )); + + assert_eq!(CandidateAggregator::::get() + .get(&collator_switching).cloned(), + Some(aggregator_old.clone()), + ); + + assert_eq!(AggregatorMetadata::::get(&aggregator).unwrap().token_collator_map.len(), (x - 1) as usize); + assert_eq!(AggregatorMetadata::::get(&aggregator).unwrap().approved_candidates.len(), x as usize); + assert_eq!(AggregatorMetadata::::get(&aggregator_old).unwrap().token_collator_map.len(), 1 as usize); + assert_eq!(AggregatorMetadata::::get(&aggregator_old).unwrap().approved_candidates.len(), 1 as usize); + + }: _(RawOrigin::Signed(collator_switching.clone()), Some(aggregator.clone())) + verify { + + for i in 0u32..(x){ + + let seed = USER_SEED - i; + + let collator = account("collator", seed, SEED); + assert_eq!(CandidateAggregator::::get() + .get(&collator).cloned(), + Some(aggregator.clone()), + ); + + } + + assert_eq!(AggregatorMetadata::::get(&aggregator).unwrap().token_collator_map.len(), x as usize); + assert_eq!(AggregatorMetadata::::get(&aggregator).unwrap().approved_candidates.len(), x as usize); + assert_eq!(AggregatorMetadata::::get(&aggregator_old).unwrap().token_collator_map.len(), 0 as usize); + assert_eq!(AggregatorMetadata::::get(&aggregator_old).unwrap().approved_candidates.len(), 1 as usize); + + } + + payout_collator_rewards { + + let funding_account: T::AccountId = account("funding", 0u32, 0u32); + assert_ok!(T::Currency::mint(MGA_TOKEN_ID.into(), &<::StakingIssuanceVault as Get>::get(), (1_000_000*DOLLAR).to_balance::())); + + const SEED: u32 = 0; + let collator: T::AccountId = account("collator", 0u32, SEED); + + let mut round_collator_reward_info = RoundCollatorRewardInfoType::>::default(); + round_collator_reward_info.collator_reward = (1*DOLLAR).to_balance::(); + + for i in 0u32..<::MaxDelegatorsPerCandidate as Get>::get() { + let delegator: T::AccountId = account("delegator", USER_SEED - i, SEED); + round_collator_reward_info.delegator_rewards.insert(delegator, (1*DOLLAR).to_balance::()); + } + + RoundCollatorRewardInfo::::insert(collator.clone(), 1000, round_collator_reward_info); + + assert_eq!(RoundCollatorRewardInfo::::get(&collator, 1000).unwrap().collator_reward, (1*DOLLAR).to_balance::()); + + }: _(RawOrigin::Signed(collator.clone()), collator.clone(), Some(1)) + verify { + + assert_eq!(RoundCollatorRewardInfo::::get(&collator, 1000), None); + + } + + + payout_delegator_reward { + + let funding_account: T::AccountId = account("funding", 0u32, 0u32); + assert_ok!(T::Currency::mint(MGA_TOKEN_ID.into(), &<::StakingIssuanceVault as Get>::get(), (1_000_000*DOLLAR).to_balance::())); + + const SEED: u32 = 0; + let collator: T::AccountId = account("collator", 0u32, SEED); + + let mut round_collator_reward_info = RoundCollatorRewardInfoType::>::default(); + round_collator_reward_info.collator_reward = (1*DOLLAR).to_balance::(); + + for i in 0u32..(<::MaxDelegatorsPerCandidate as Get>::get()) { + let delegator: T::AccountId = account("delegator", USER_SEED - i, SEED); + round_collator_reward_info.delegator_rewards.insert(delegator, (1*DOLLAR).to_balance::()); + } + + RoundCollatorRewardInfo::::insert(collator.clone(), 1000, round_collator_reward_info); + + assert_eq!(RoundCollatorRewardInfo::::get(&collator, 1000).unwrap().collator_reward, (1*DOLLAR).to_balance::()); + assert_eq!(RoundCollatorRewardInfo::::get(&collator, 1000).unwrap().delegator_rewards.len(), <::MaxDelegatorsPerCandidate as Get>::get() as usize); + + let delegator_target: T::AccountId = account("delegator", USER_SEED, SEED); + + }: _(RawOrigin::Signed(delegator_target.clone()), 1000, collator.clone(), delegator_target.clone()) + verify { + + assert_eq!(RoundCollatorRewardInfo::::get(&collator, 1000).unwrap().collator_reward, (1*DOLLAR).to_balance::()); + assert_eq!(RoundCollatorRewardInfo::::get(&collator, 1000).unwrap().delegator_rewards.len(), (<::MaxDelegatorsPerCandidate as Get>::get() - 1) as usize); + + } + + // Session Change + + // The session pallet's on initialize is called but should_end_session returns false + // This essentially just benhcmarks should_end_session + passive_session_change { + // Move on by a block + // Assuming we start at (say) 0, and that round is atleast 3 blocks. + + as frame_support::traits::Hooks<_>>::on_finalize(>::block_number()); + as frame_support::traits::Hooks<_>>::on_finalize(>::block_number()); + as frame_support::traits::Hooks<_>>::on_finalize(>::block_number()); + >::set_block_number(>::block_number() + 1u32.into()); + as frame_support::traits::Hooks<_>>::on_initialize(>::block_number()); + as frame_support::traits::Hooks<_>>::on_initialize(>::block_number()); + + assert_eq!(pallet_session::Pallet::::current_index() as u32, 0u32); + + assert!(! as pallet_session::ShouldEndSession<_>>::should_end_session(>::block_number())); + + }: { as frame_support::traits::Hooks<_>>::on_initialize(>::block_number());} + verify { + assert_eq!(pallet_session::Pallet::::current_index() as u32, 0u32); + } + + active_session_change { + + // liquidity tokens + let x in 3..100; + // candidate_count + let y in (<::MinSelectedCandidates as Get>::get() + 1u32)..(<::MaxCollatorCandidates as Get>::get() - 2u32); // to account for the two candidates we start with + // MaxDelegatorsPerCandidate + let z in 3..<::MaxDelegatorsPerCandidate as Get>::get(); + + // // Since now an aggregator can have multiple collators each of whose rewards will be written to the storage individually + // // Total selected + let w = <::MinSelectedCandidates as Get>::get() + 1u32; + + // // liquidity tokens + // let x = 100; + // // candidate_count + // let y = 190; + // // MaxDelegatorsPerCandidate + // let z = 200; + // // Total selected + // let w = 190; + + assert_ok!(>::finalize_tge(RawOrigin::Root.into())); + assert_ok!(>::init_issuance_config(RawOrigin::Root.into())); + assert_ok!(>::calculate_and_store_round_issuance(0u32)); + + assert_ok!(Pallet::::set_total_selected(RawOrigin::Root.into(), w)); + + // We will prepare `x-1` liquidity tokens in loop and then another after + + let start_liquidity_token = Pallet::::staking_liquidity_tokens(); + let start_liquidity_token_count: u32 = start_liquidity_token.len().try_into().unwrap(); + for (token,_) in start_liquidity_token { + // as PoolPromoteApi>::update_pool_promotion(token, Some(1)); + T::RewardsApi::enable(token, 1); + } + + assert!(x > start_liquidity_token_count); + // create X - 1 Tokens now and then remaining one + for i in start_liquidity_token_count..(x-1){ + let created_liquidity_token = create_staking_liquidity_for_funding::(Some(T::MinCandidateStk::get())).unwrap(); + Pallet::::add_staking_liquidity_token(RawOrigin::Root.into(), PairedOrLiquidityToken::Liquidity(created_liquidity_token), i).unwrap(); + // as PoolPromoteApi>::update_pool_promotion(created_liquidity_token, Some(1)); + T::RewardsApi::enable(created_liquidity_token, 1); + } + + // Now to prepare the liquidity token we will use for collator and delegators + let amount = ((z*(y+1)) as u128 * 100 * DOLLAR).to_balance::() + T::MinCandidateStk::get() * DOLLAR.to_balance::(); + let created_liquidity_token = create_staking_liquidity_for_funding::(Some(amount)).unwrap(); + assert_ok!(Pallet::::add_staking_liquidity_token(RawOrigin::Root.into(), PairedOrLiquidityToken::Liquidity(created_liquidity_token), x)); + // as PoolPromoteApi>::update_pool_promotion(created_liquidity_token, Some(1)); + T::RewardsApi::enable(created_liquidity_token, 1); + + + // Now we will create y funded collators + let initial_candidates: Vec = Pallet::::candidate_pool().0.into_iter().map(|x| x.owner).collect::<_>(); + let base_candidate_count: u32 = Pallet::::candidate_pool().0.len().try_into().unwrap(); + + assert_eq!(base_candidate_count, 2); + assert_eq!(x as usize , StakingLiquidityTokens::::get().len()); + + // let pool_rewards = pallet_issuance::PromotedPoolsRewardsV2::::get(); + // assert_eq!(pool_rewards.len(), x as usize); + + + let mut candidates = (0u32..y) + .map(|i|{ + create_funded_collator::( + "collator", + USER_SEED - i, + created_liquidity_token, + Some(T::MinCandidateStk::get()), + i + base_candidate_count, + x + ) + }).collect::, &'static str>>()?; + + // create one aggregator per candidate + for (id, c) in candidates.iter().enumerate() { + let aggregator: T::AccountId = account("aggregator", id as u32, 0); + assert_ok!(Pallet::::aggregator_update_metadata(RawOrigin::Signed( + aggregator.clone()).into(), + vec![c.clone()], + MetadataUpdateAction::ExtendApprovedCollators, + )); + + assert_ok!(Pallet::::update_candidate_aggregator(RawOrigin::Signed( + c.clone()).into(), + Some(aggregator.clone()), + )); + } + + assert_eq!(candidates.len(), y as usize); + // + // // Now we will create `z*y` delegators each with `100*DOLLAR` created_liquidity_token tokens + // + let delegators_count = z*y; + let delegators: Vec<_> = (0u32..delegators_count) + .map(|i| + create_funded_user::("delegator", USER_SEED-i, created_liquidity_token, Some((100*DOLLAR).to_balance::())) + ).map(|(account, _token_id, _amount)| account) + .collect(); + assert_eq!(delegators.len(), (z*y) as usize); + + for (delegators, candidate) in delegators.iter().chunks(z as usize).into_iter() + .zip(candidates.clone()) + { + + for (count, delegator) in delegators.into_iter().enumerate() { + Pallet::::delegate(RawOrigin::Signed( + delegator.clone()).into(), + candidate.clone().into(), + (100*DOLLAR).to_balance::(), + None, + count as u32, + 0u32, + ).unwrap(); + } + + assert_eq!(Pallet::::candidate_state(candidate.clone()).unwrap().delegators.0.len() , z as usize); + assert_eq!(Pallet::::candidate_state(candidate.clone()).unwrap().top_delegations.len() , z as usize); + assert_eq!(Pallet::::candidate_state(candidate.clone()).unwrap().bottom_delegations.len() , 0usize); + + } + + + // + // Remove the initial two collators so that they do not get selected + // We do this as the two collators do not have max delegators and would not be worst case + + for initial_candidate in initial_candidates{ + assert_ok!(Pallet::::go_offline(RawOrigin::Signed( + initial_candidate.clone()).into())); + } + + + + // We would like to move on to the end of round 4 + let session_to_reach = 4u32; + + // Moves to the end of the round + // Infinite loop that breaks when should_end_session is true + loop { + as frame_support::traits::Hooks<_>>::on_finalize(>::block_number()); + as frame_support::traits::Hooks<_>>::on_finalize(>::block_number()); + as frame_support::traits::Hooks<_>>::on_finalize(>::block_number()); + >::set_block_number(>::block_number() + 1u32.into()); + as frame_support::traits::Hooks<_>>::on_initialize(>::block_number()); + as frame_support::traits::Hooks<_>>::on_initialize(>::block_number()); + as frame_support::traits::Hooks<_>>::on_initialize(>::block_number()); + if Pallet::::round().current == session_to_reach { + for i in 0..2{ + as frame_support::traits::Hooks<_>>::on_finalize(>::block_number()); + as frame_support::traits::Hooks<_>>::on_finalize(>::block_number()); + as frame_support::traits::Hooks<_>>::on_finalize(>::block_number()); + >::set_block_number(>::block_number() + 1u32.into()); + as frame_support::traits::Hooks<_>>::on_initialize(>::block_number()); + as frame_support::traits::Hooks<_>>::on_initialize(>::block_number()); + as frame_support::traits::Hooks<_>>::on_initialize(>::block_number()); + } + break; + } + } + + let selected_author = Pallet::::selected_candidates(); + + + // We would like to move on to the end of round 1 + let session_to_reach = 5u32; + + // Moves to the end of the round 0 + // Infinite loop that breaks when should_end_session is true + loop { + as frame_support::traits::Hooks<_>>::on_finalize(>::block_number()); + as frame_support::traits::Hooks<_>>::on_finalize(>::block_number()); + as frame_support::traits::Hooks<_>>::on_finalize(>::block_number()); + >::set_block_number(>::block_number() + 1u32.into()); + as frame_support::traits::Hooks<_>>::on_initialize(>::block_number()); + as frame_support::traits::Hooks<_>>::on_initialize(>::block_number()); + as frame_support::traits::Hooks<_>>::on_initialize(>::block_number()); + if Pallet::::round().current == session_to_reach { + for i in 0..2{ + as frame_support::traits::Hooks<_>>::on_finalize(>::block_number()); + as frame_support::traits::Hooks<_>>::on_finalize(>::block_number()); + as frame_support::traits::Hooks<_>>::on_finalize(>::block_number()); + >::set_block_number(>::block_number() + 1u32.into()); + as frame_support::traits::Hooks<_>>::on_initialize(>::block_number()); + as frame_support::traits::Hooks<_>>::on_initialize(>::block_number()); + as frame_support::traits::Hooks<_>>::on_initialize(>::block_number()); + } + break; + } + } + + + assert_eq!(pallet_session::Pallet::::current_index() as u32, 5u32); + assert_eq!(Pallet::::round().current as u32, 5u32); + assert_eq!(selected_author.len(), (w as usize).min(Pallet::::candidate_pool().0.len() as usize)); + + + let candidate_pool_state = Pallet::::candidate_pool().0; + + for (i, candidate_bond) in candidate_pool_state.into_iter().enumerate() { + if candidate_bond.liquidity_token == created_liquidity_token { + assert_eq!(candidate_bond.amount.into(), (1+(z as u128)*100)*DOLLAR); + } + } + + for author in selected_author.clone() { + Pallet::::note_author(author.clone()); + } + + // We would like to move on to the end of round 1 + let end_of_session_to_reach = 6u32; + // let pool_rewards = pallet_issuance::PromotedPoolsRewardsV2::::get(); + // assert_eq!(pool_rewards.len(), x as usize); + + // Moves to the end of the round 0 + // Infinite loop that breaks when should_end_session is true + loop { + as frame_support::traits::Hooks<_>>::on_finalize(>::block_number()); + as frame_support::traits::Hooks<_>>::on_finalize(>::block_number()); + as frame_support::traits::Hooks<_>>::on_finalize(>::block_number()); + >::set_block_number(>::block_number() + 1u32.into()); + as frame_support::traits::Hooks<_>>::on_initialize(>::block_number()); + as frame_support::traits::Hooks<_>>::on_initialize(>::block_number()); + if as pallet_session::ShouldEndSession<_>>::should_end_session(>::block_number()) + && (Pallet::::round().current == end_of_session_to_reach) { + break; + } else { + as frame_support::traits::Hooks<_>>::on_initialize(>::block_number()); + } + } + + + assert_eq!(pallet_session::Pallet::::current_index() as u32, 6u32); + assert_eq!(Pallet::::round().current as u32, 6u32); + + assert!( as pallet_session::ShouldEndSession<_>>::should_end_session(>::block_number())); + + for author in selected_author.clone() { + for candidate in AggregatorMetadata::::get(&author).unwrap().token_collator_map.iter().map(|x| x.1){ + assert!(T::Currency::total_balance(MGA_TOKEN_ID.into(), &candidate).is_zero()); + } + } + + }: { as frame_support::traits::Hooks<_>>::on_initialize(>::block_number());} + verify { + assert_eq!(pallet_session::Pallet::::current_index() as u32, 7u32); + assert_eq!(Pallet::::round().current as u32, 7u32); + assert_eq!(w as usize, candidates.iter().filter_map(|c| RoundCollatorRewardInfo::::get(c.clone(), 5u32)).count()); + } + +} diff --git a/gasp-node/pallets/parachain-staking/src/lib.rs b/gasp-node/pallets/parachain-staking/src/lib.rs new file mode 100644 index 000000000..3d5b3bb7e --- /dev/null +++ b/gasp-node/pallets/parachain-staking/src/lib.rs @@ -0,0 +1,3554 @@ +// Moonbeam 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. + +// Moonbeam 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 Moonbeam. If not, see . + +//! # Parachain Staking +//! Minimal staking pallet that implements collator selection by total backed stake. +//! The main difference between this pallet and `frame/pallet-staking` is that this pallet +//! uses direct delegation. Delegators choose exactly who they delegate and with what stake. +//! This is different from `frame/pallet-staking` where delegators approval vote and run Phragmen. +//! +//! ### Actors +//! There are multiple functions that can be distinguished: +//! - Collator Candidate - depending on managed stake can or can not bo chosen as collator +//! - Delegator - delegates to collator candidate, if that collator candidate will become +//! collator delegator is eligible for proportional part of the rewards that collator receives for +//! building blocks +//! - Aggregator - A collator candiate may choose to aggregate under an aggregator. If this aggregator +//! gets selected then he becomes an author/collator representing the collator candidates aggregating under him. +//! If a collator candidate does not choose to aggregate under an aggregator and gets selected, then he +//! himself becomes the author/collator. Any account that is not delegator or candidate can become +//! aggregator +//! +//! ### Rules +//! There is a new round every `>::get().length` blocks. +//! +//! At the start of every round, +//! * issuance is assigned to collators (and their delegators) for block authoring +//! `T::RewardPaymentDelay` rounds ago, afterwards it can be claimed using dedicated extrinsics +//! * queued collator and delegator exits are executed +//! * a new set of collators is chosen from the candidates +//! +//! To join the set of candidates, call `join_candidates` with `bond >= MinCandidateStk`. +//! To leave the set of candidates, call `schedule_leave_candidates`. If the call succeeds, +//! the collator is removed from the pool of candidates so they cannot be selected for future +//! collator sets, but they are not unbonded until their exit request is executed. Any signed +//! account may trigger the exit `T::LeaveCandidatesDelay` rounds after the round in which the +//! original request was made. +//! +//! To join the set of delegators, call `delegate` and pass in an account that is +//! already a collator candidate and `bond >= MinDelegatorStk`. Each delegator can delegate up to +//! `T::MaxDelegationsPerDelegator` collator candidates by calling `delegate`. +//! +//! To revoke a delegation, call `revoke_delegation` with the collator candidate's account. +//! To leave the set of delegators and revoke all delegations, call `leave_delegators`. +//! +//! +//! # Aggregation +//! Aggregation feature allows accumulating stake in different liquidity tokens under single aggregator account +//! by assosiating several candidates stake with that (aggregator) account. Each candidate needs to bond different +//! liquidity token +//! ```ignore +//! #################### +//! -------------# Aggregator A #------------- +//! | # # | +//! | #################### | +//! | | | +//! | | | +//! | | | +//! | | | +//! | | | +//! | | | +//! -------------------- -------------------- -------------------- +//! | Candidate B | | Candidate C | | Candidate D | +//! | token: MGX:TUR | | token: MGX:IMBU | | token: MGX:MOVR | +//! -------------------- -------------------- -------------------- +//! ``` +//! If candidate decides to aggregate under Aggregator it cannot be chosen to be collator(the +//! candidate), instead aggregator account can be selected (even though its not present on +//! candidates list). +//! +//! +//! Block authors selection algorithm details [`Pallet::select_top_candidates`] +//! +//!```ignore +//! candidate B MGX valuation +//! Candidate B rewards = ------------------------------------ * Aggregator A total staking rewards +//! candidate ( B + C + D) valuation +//!``` +//! +//! Extrinsics: +//! - [`Pallet::aggregator_update_metadata`] - enable/disable candidates for aggregation +//! - [`Pallet::update_candidate_aggregator`] - assign aggregator for candidate +//! +//! Storage entries: +//! - [`CandidateAggregator`] +//! - [`AggregatorMetadata`] +//! - [`RoundAggregatorInfo`] +//! - [`RoundCollatorRewardInfo`] +//! +//! ## Candidate selection mechanism +//! Aggregation feature modifies how collators are selected. Rules are as follows: +//! - Everything is valuated in `MGX` part of staked liquidity token. So if collator A has X MGX:KSM +//! liquidity tokens. And X MGX:KSM liquidity token is convertible to Y `MGX` and Z `KSM`. Then X +//! MGX:KSM tokens has valuation of Y. +//! - If candidate allows for staking native tokens number of native tokens/2 == candidate valuation. +//! - for aggregator(A) each aggregation account (such that aggregates under A) is valuated in MGX and +//! sumed. +//! - Candidates that aggregates under some other account cannot be selected as collators (but the +//! account they aggregate under can) +//! - Candidates with top MGX valuation are selected as collators +//! +//! # Manual payouts +//! Due to big cost of automatic rewards distribution (N transfers where N is total amount of all +//! rewarded collators & delegators) it was decided to switch to manual payouts mechanism. Instead +//! of automatically transferring all the rewards at the end session only rewards amount per account +//! is stored. Then collators & delegators can claim their rewards manually (after T::RewardPaymentDelay). +//! +//! Extrinsics: +//! - [`Pallet::payout_collator_rewards`] - supposed to be called by collator after every round. +//! - [`Pallet::payout_delegator_reward`] - backup solution for withdrawing rewards when collator +//! +//! Storage entries: +//! - [`RoundCollatorRewardInfo`] +//! +//! is not available. +//! +#![cfg_attr(not(feature = "std"), no_std)] + +#[cfg(doc)] +use aquamarine::aquamarine; + +#[cfg(feature = "runtime-benchmarks")] +mod benchmarks; +#[cfg(test)] +#[cfg(not(feature = "runtime-benchmarks"))] +mod mock; +mod set; +#[cfg(test)] +#[cfg(not(feature = "runtime-benchmarks"))] +mod tests; + +use crate::set::OrderedSet; +use codec::{Decode, Encode}; +use frame_support::{ + pallet, + pallet_prelude::*, + traits::{ + tokens::currency::MultiTokenCurrency, EstimateNextSessionRotation, ExistenceRequirement, + Get, + }, + transactional, +}; +use frame_system::{pallet_prelude::*, RawOrigin}; +pub use mangata_support::{ + pools::ValuateFor, + traits::{ + ComputeIssuance, GetIssuance, PoolCreateApi, ProofOfStakeRewardsApi, + SequencerStakingRewardsTrait, StakingReservesProviderTrait, XykFunctionsTrait, + }, +}; +pub use mangata_types::multipurpose_liquidity::BondKind; +use orml_tokens::{MultiTokenCurrencyExtended, MultiTokenReservableCurrency}; +use pallet_collective_mangata::GetMembers; +use scale_info::TypeInfo; +use sp_arithmetic::per_things::Rounding; +use sp_runtime::{ + helpers_128bit::multiply_by_rational_with_rounding, + traits::{ + Bounded, CheckedAdd, CheckedDiv, CheckedMul, CheckedSub, One, SaturatedConversion, + Saturating, Zero, + }, + Perbill, Permill, RuntimeDebug, +}; +use sp_staking::SessionIndex; +use sp_std::{ + cmp::Ordering, + collections::{btree_map::BTreeMap, btree_set::BTreeSet}, + convert::TryInto, + prelude::*, +}; + +pub use pallet::*; + +pub mod weights; +pub use weights::WeightInfo; + +trait FromInfiniteZeros { + type Output; + fn from_zeros() -> Self::Output; +} + +impl FromInfiniteZeros for D { + type Output = D; + fn from_zeros() -> Self::Output { + D::decode(&mut sp_runtime::traits::TrailingZeroInput::zeroes()).unwrap() + } +} + +#[derive(Eq, PartialEq, Encode, Decode, TypeInfo, Debug, Clone)] +pub enum MetadataUpdateAction { + ExtendApprovedCollators, + RemoveApprovedCollators, +} + +#[derive(Eq, PartialEq, Debug, Clone)] +pub enum RewardKind { + Collator, + Delegator(AccountId), +} + +#[derive(Eq, PartialEq, Encode, Decode, TypeInfo, Debug, Clone)] +pub enum PayoutRounds { + All, + Partial(Vec), +} + +#[pallet] +pub mod pallet { + pub use super::*; + + /// Pallet for parachain staking + #[pallet::pallet] + #[pallet::without_storage_info] + pub struct Pallet(PhantomData); + + #[derive(Eq, PartialEq, Clone, Encode, Decode, RuntimeDebug, TypeInfo)] + pub enum PairedOrLiquidityToken { + Paired(CurrencyId), + Liquidity(CurrencyId), + } + + #[derive(Clone, Encode, Decode, RuntimeDebug, TypeInfo)] + pub struct Bond { + pub owner: AccountId, + pub amount: Balance, + pub liquidity_token: CurrencyId, + } + + impl Default for Bond { + fn default() -> Bond { + Bond { + owner: A::decode(&mut sp_runtime::traits::TrailingZeroInput::zeroes()) + .expect("infinite length input; no invalid inputs for type; qed"), + amount: B::default(), + liquidity_token: C::default(), + } + } + } + + impl Bond { + pub fn from_owner(owner: A) -> Self { + Bond { owner, amount: B::default(), liquidity_token: C::default() } + } + } + + impl Eq for Bond {} + + impl Ord for Bond { + fn cmp(&self, other: &Self) -> Ordering { + self.owner.cmp(&other.owner) + } + } + + impl PartialOrd for Bond { + fn partial_cmp(&self, other: &Self) -> Option { + Some(self.cmp(other)) + } + } + + impl PartialEq for Bond { + fn eq(&self, other: &Self) -> bool { + self.owner == other.owner + } + } + + #[derive(Copy, Clone, PartialEq, Eq, Encode, Decode, RuntimeDebug, TypeInfo)] + /// The activity status of the collator + pub enum CollatorStatus { + /// Committed to be online and producing valid blocks (not equivocating) + Active, + /// Temporarily inactive and excused for inactivity + Idle, + /// Bonded until the inner round + Leaving(RoundIndex), + } + + impl Default for CollatorStatus { + fn default() -> CollatorStatus { + CollatorStatus::Active + } + } + + #[derive(Encode, Decode, RuntimeDebug, TypeInfo)] + /// Snapshot of collator state at the start of the round for which they are selected + pub struct CollatorSnapshot { + pub bond: Balance, + pub delegations: Vec>, + pub total: Balance, + pub liquidity_token: CurrencyId, + } + + impl Default + for CollatorSnapshot + { + fn default() -> CollatorSnapshot { + Self { + delegations: Default::default(), + bond: Default::default(), + total: Default::default(), + liquidity_token: Default::default(), + } + } + } + + #[derive(PartialEq, Clone, Copy, Encode, Decode, RuntimeDebug, TypeInfo)] + /// Changes allowed by an active collator candidate to their self bond + pub enum CandidateBondChange { + Increase, + Decrease, + } + + #[derive(PartialEq, Clone, Copy, Encode, Decode, RuntimeDebug, TypeInfo)] + /// Request scheduled to change the collator candidate self-bond + pub struct CandidateBondRequest { + pub amount: Balance, + pub change: CandidateBondChange, + pub when_executable: RoundIndex, + } + + #[derive(Encode, Decode, RuntimeDebug, TypeInfo)] + /// Collator candidate state with self bond + delegations + pub struct CollatorCandidate { + /// The account of this collator + pub id: AccountId, + /// This collator's self stake. + pub bond: Balance, + /// This is the liquidity_token the collator uses + pub liquidity_token: CurrencyId, + /// Set of all delegator AccountIds (to prevent >1 delegation per AccountId) + pub delegators: OrderedSet, + /// Top T::MaxDelegatorsPerCollator::get() delegations, ordered greatest to least + pub top_delegations: Vec>, + /// Bottom delegations (unbounded), ordered least to greatest + pub bottom_delegations: Vec>, + /// Sum of top delegations + self.bond + pub total_counted: Balance, + /// Sum of all delegations + self.bond = (total_counted + uncounted) + pub total_backing: Balance, + /// Maximum 1 pending request to adjust candidate self bond at any given time + pub request: Option>, + /// Current status of the collator + pub state: CollatorStatus, + } + + /// Convey relevant information describing if a delegator was added to the top or bottom + /// Delegations added to the top yield a new total + #[derive(Clone, Copy, PartialEq, Encode, Decode, RuntimeDebug, TypeInfo)] + pub enum DelegatorAdded { + AddedToTop { new_total: Balance }, + AddedToBottom, + } + + impl CollatorCandidate + where + A: Ord + Clone + sp_std::fmt::Debug, + Balance: Default + PartialOrd + CheckedAdd + CheckedSub + Saturating + Ord + Copy, + CurrencyId: Copy, + { + pub fn new(id: A, bond: Balance, liquidity_token: CurrencyId) -> Self { + CollatorCandidate { + id, + bond, + liquidity_token, + delegators: OrderedSet::new(), + top_delegations: Vec::new(), + bottom_delegations: Vec::new(), + total_counted: bond, + total_backing: bond, + request: None, + state: CollatorStatus::default(), // default active + } + } + pub fn is_active(&self) -> bool { + self.state == CollatorStatus::Active + } + pub fn is_leaving(&self) -> bool { + matches!(self.state, CollatorStatus::Leaving(_)) + } + pub fn can_leave(&self) -> DispatchResult { + if let CollatorStatus::Leaving(when) = self.state { + ensure!(>::get().current >= when, Error::::CandidateCannotLeaveYet); + Ok(()) + } else { + Err(Error::::CandidateNotLeaving.into()) + } + } + /// Schedule executable increase of collator candidate self bond + /// Returns the round at which the collator can execute the pending request + pub fn schedule_bond_more( + &mut self, + more: Balance, + use_balance_from: Option, + ) -> Result + where + T::AccountId: From, + BalanceOf: From, + CurrencyIdOf: From, + { + // ensure no pending request + ensure!(self.request.is_none(), Error::::PendingCandidateRequestAlreadyExists); + let candidate_id: T::AccountId = self.id.clone().into(); + ensure!( + ::StakingReservesProvider::can_bond( + self.liquidity_token.into(), + &candidate_id, + more.into(), + use_balance_from + ), + Error::::InsufficientBalance + ); + let when_executable = + >::get().current.saturating_add(T::CandidateBondDelay::get()); + self.request = Some(CandidateBondRequest { + change: CandidateBondChange::Increase, + amount: more, + when_executable, + }); + Ok(when_executable) + } + /// Schedule executable decrease of collator candidate self bond + /// Returns the round at which the collator can execute the pending request + pub fn schedule_bond_less( + &mut self, + less: Balance, + ) -> Result + where + BalanceOf: From, + CurrencyIdOf: From, + { + // ensure no pending request + ensure!(self.request.is_none(), Error::::PendingCandidateRequestAlreadyExists); + // ensure bond above min after decrease + ensure!(self.bond > less, Error::::CandidateBondBelowMin); + + let bond_valution_after = Pallet::::valuate_bond( + self.liquidity_token.into(), + self.bond.checked_sub(&less).unwrap_or_default().into(), + ); + ensure!( + bond_valution_after >= T::MinCandidateStk::get(), + Error::::CandidateBondBelowMin + ); + let when_executable = + >::get().current.saturating_add(T::CandidateBondDelay::get()); + self.request = Some(CandidateBondRequest { + change: CandidateBondChange::Decrease, + amount: less, + when_executable, + }); + Ok(when_executable) + } + /// Execute pending request to change the collator self bond + /// Returns the event to be emitted + pub fn execute_pending_request( + &mut self, + use_balance_from: Option, + ) -> Result, DispatchError> + where + T::AccountId: From, + BalanceOf: From, + CurrencyIdOf: From, + { + let request = self.request.ok_or(Error::::PendingCandidateRequestsDNE)?; + ensure!( + request.when_executable <= >::get().current, + Error::::PendingCandidateRequestNotDueYet + ); + let caller: T::AccountId = self.id.clone().into(); + let event = match request.change { + CandidateBondChange::Increase => { + self.bond = + self.bond.checked_add(&request.amount).ok_or(Error::::MathError)?; + + self.total_counted = self + .total_counted + .checked_add(&request.amount) + .ok_or(Error::::MathError)?; + self.total_backing = self + .total_backing + .checked_add(&request.amount) + .ok_or(Error::::MathError)?; + ::StakingReservesProvider::bond( + self.liquidity_token.into(), + &caller, + request.amount.into(), + use_balance_from, + )?; + let currency: CurrencyIdOf = self.liquidity_token.into(); + let new_total = >::get(currency).saturating_add(request.amount.into()); + >::insert(currency, new_total); + Event::CandidateBondedMore( + self.id.clone().into(), + request.amount.into(), + self.bond.into(), + ) + }, + CandidateBondChange::Decrease => { + // Arithmetic assumptions are self.bond > less && self.bond - less > CollatorMinBond + // (assumptions enforced by `schedule_bond_less`; if storage corrupts, must re-verify) + self.bond = + self.bond.checked_sub(&request.amount).ok_or(Error::::MathError)?; + + self.total_counted = self + .total_counted + .checked_sub(&request.amount) + .ok_or(Error::::MathError)?; + self.total_backing = self + .total_backing + .checked_sub(&request.amount) + .ok_or(Error::::MathError)?; + let debug_amount = ::StakingReservesProvider::unbond( + self.liquidity_token.into(), + &caller, + request.amount.into(), + ); + if !debug_amount.is_zero() { + log::warn!("Unbond in staking returned non-zero value {:?}", debug_amount); + } + let currency: CurrencyIdOf = self.liquidity_token.into(); + let new_total_staked = + >::get(currency).saturating_sub(request.amount.into()); + >::insert(currency, new_total_staked); + Event::CandidateBondedLess( + self.id.clone().into(), + request.amount.into(), + self.bond.into(), + ) + }, + }; + // reset s.t. no pending request + self.request = None; + // update candidate pool value because it must change if self bond changes + if self.is_active() { + Pallet::::update_active( + self.id.clone().into(), + self.total_counted.into(), + self.liquidity_token.into(), + ); + } + Ok(event) + } + /// Cancel pending request to change the collator self bond + pub fn cancel_pending_request(&mut self) -> Result, DispatchError> + where + T::AccountId: From, + CandidateBondRequest>: From>, + { + let request = self.request.ok_or(Error::::PendingCandidateRequestsDNE)?; + let event = Event::CancelledCandidateBondChange(self.id.clone().into(), request.into()); + self.request = None; + Ok(event) + } + /// Infallible sorted insertion + /// caller must verify !self.delegators.contains(delegation.owner) before call + pub fn add_top_delegation(&mut self, delegation: Bond) { + match self.top_delegations.binary_search_by(|x| delegation.amount.cmp(&x.amount)) { + Ok(i) => self.top_delegations.insert(i, delegation), + Err(i) => self.top_delegations.insert(i, delegation), + } + } + /// Infallible sorted insertion + /// caller must verify !self.delegators.contains(delegation.owner) before call + pub fn add_bottom_delegation(&mut self, delegation: Bond) { + match self.bottom_delegations.binary_search_by(|x| x.amount.cmp(&delegation.amount)) { + Ok(i) => self.bottom_delegations.insert(i, delegation), + Err(i) => self.bottom_delegations.insert(i, delegation), + } + } + /// Sort top delegations from greatest to least + pub fn sort_top_delegations(&mut self) { + self.top_delegations.sort_unstable_by(|a, b| b.amount.cmp(&a.amount)); + } + /// Sort bottom delegations from least to greatest + pub fn sort_bottom_delegations(&mut self) { + self.bottom_delegations.sort_unstable_by(|a, b| a.amount.cmp(&b.amount)); + } + /// Bond account and add delegation. If successful, the return value indicates whether the + /// delegation is top for the candidate. + pub fn add_delegation( + &mut self, + acc: A, + amount: Balance, + ) -> Result>, DispatchError> + where + BalanceOf: From, + { + ensure!(self.delegators.insert(acc.clone()), Error::::DelegatorExists); + self.total_backing = + self.total_backing.checked_add(&amount).ok_or(Error::::MathError)?; + if (self.top_delegations.len() as u32) < T::MaxDelegatorsPerCandidate::get() { + self.add_top_delegation(Bond { + owner: acc, + amount, + liquidity_token: self.liquidity_token, + }); + self.total_counted = + self.total_counted.checked_add(&amount).ok_or(Error::::MathError)?; + Ok(DelegatorAdded::AddedToTop { new_total: self.total_counted.into() }) + } else { + // >pop requires push to reset in case isn't pushed to bottom + let last_delegation_in_top = self + .top_delegations + .pop() + .expect("self.top_delegations.len() >= T::Max exists >= 1 element in top"); + if amount > last_delegation_in_top.amount { + // update total_counted with positive difference + self.total_counted = self + .total_counted + .checked_add( + &amount + .checked_sub(&last_delegation_in_top.amount.into()) + .ok_or(Error::::MathError)?, + ) + .ok_or(Error::::MathError)?; + // last delegation already popped from top_delegations + // insert new delegation into top_delegations + self.add_top_delegation(Bond { + owner: acc, + amount, + liquidity_token: self.liquidity_token, + }); + self.add_bottom_delegation(last_delegation_in_top); + Ok(DelegatorAdded::AddedToTop { new_total: self.total_counted.into() }) + } else { + // >required push to previously popped last delegation into top_delegations + self.top_delegations.push(last_delegation_in_top); + self.add_bottom_delegation(Bond { + owner: acc, + amount, + liquidity_token: self.liquidity_token, + }); + Ok(DelegatorAdded::AddedToBottom) + } + } + } + /// Return Ok((if_total_counted_changed, delegation_amount)) + pub fn rm_delegator( + &mut self, + delegator: A, + ) -> Result<(bool, Balance), DispatchError> { + ensure!(self.delegators.remove(&delegator), Error::::DelegatorDNEInDelegatorSet); + let mut delegation_amt: Option = None; + self.top_delegations = self + .top_delegations + .clone() + .into_iter() + .filter_map(|d| { + if d.owner != delegator { + Some(d) + } else { + delegation_amt = Some(d.amount); + None + } + }) + .collect(); + // item removed from the top => highest bottom is popped from bottom and pushed to top + if let Some(amount) = delegation_amt { + // last element has largest amount as per ordering + if let Some(last) = self.bottom_delegations.pop() { + self.total_counted = self + .total_counted + .checked_sub( + &amount.checked_sub(&last.amount).ok_or(Error::::MathError)?, + ) + .ok_or(Error::::MathError)?; + self.add_top_delegation(last); + } else { + // no item in bottom delegations so no item from bottom to pop and push up + self.total_counted = + self.total_counted.checked_sub(&amount).ok_or(Error::::MathError)?; + } + self.total_backing = + self.total_backing.checked_sub(&amount).ok_or(Error::::MathError)?; + return Ok((true, amount)) + } + // else (no item removed from the top) + self.bottom_delegations = self + .bottom_delegations + .clone() + .into_iter() + .filter_map(|d| { + if d.owner != delegator { + Some(d) + } else { + delegation_amt = Some(d.amount); + None + } + }) + .collect(); + // if err, no item with account exists in top || bottom + let amount = delegation_amt.ok_or(Error::::DelegatorDNEinTopNorBottom)?; + self.total_backing = + self.total_backing.checked_sub(&amount).ok_or(Error::::MathError)?; + Ok((false, amount)) + } + /// Return true if in_top after call + /// Caller must verify before call that account is a delegator + fn increase_delegation( + &mut self, + delegator: A, + more: Balance, + ) -> Result { + let mut in_top = false; + for x in &mut self.top_delegations { + if x.owner == delegator { + x.amount = x.amount.checked_add(&more).ok_or(Error::::MathError)?; + self.total_counted = + self.total_counted.checked_add(&more).ok_or(Error::::MathError)?; + self.total_backing = + self.total_backing.checked_add(&more).ok_or(Error::::MathError)?; + in_top = true; + break + } + } + // if delegator was increased in top delegations + if in_top { + self.sort_top_delegations(); + return Ok(true) + } + // else delegator to increase must exist in bottom + // >pop requires push later on to reset in case it isn't used + let lowest_top = self + .top_delegations + .pop() + .expect("any bottom delegations => must exist max top delegations"); + let mut move_2_top = false; + for x in &mut self.bottom_delegations { + if x.owner == delegator { + x.amount = x.amount.checked_add(&more).ok_or(Error::::MathError)?; + self.total_backing = + self.total_backing.checked_add(&more).ok_or(Error::::MathError)?; + move_2_top = x.amount > lowest_top.amount; + break + } + } + if move_2_top { + self.sort_bottom_delegations(); + let highest_bottom = self.bottom_delegations.pop().expect("updated => exists"); + self.total_counted = self + .total_counted + .checked_add( + &highest_bottom + .amount + .checked_sub(&lowest_top.amount) + .ok_or(Error::::MathError)?, + ) + .ok_or(Error::::MathError)?; + self.add_top_delegation(highest_bottom); + self.add_bottom_delegation(lowest_top); + Ok(true) + } else { + // >required push to reset top_delegations from earlier pop + self.top_delegations.push(lowest_top); + self.sort_bottom_delegations(); + Ok(false) + } + } + /// Return true if in_top after call + pub fn decrease_delegation( + &mut self, + delegator: A, + less: Balance, + ) -> Result { + let mut in_top = false; + let mut new_lowest_top: Option> = None; + for x in &mut self.top_delegations { + if x.owner == delegator { + x.amount = x.amount.checked_sub(&less).ok_or(Error::::MathError)?; + // if there is at least 1 delegator in bottom delegators, compare it to check + // if it should be swapped with lowest top delegation and put in top + // >pop requires push later on to reset in case it isn't used + if let Some(highest_bottom) = self.bottom_delegations.pop() { + if highest_bottom.amount > x.amount { + new_lowest_top = Some(highest_bottom); + } else { + // >required push to reset self.bottom_delegations + self.bottom_delegations.push(highest_bottom); + } + } + in_top = true; + break + } + } + if in_top { + self.sort_top_delegations(); + if let Some(highest_bottom) = new_lowest_top { + // pop last in top to swap it with top bottom + let lowest_top = self + .top_delegations + .pop() + .expect("must have >1 item to update, assign in_top = true"); + self.total_counted = self + .total_counted + .checked_sub( + &lowest_top.amount.checked_add(&less).ok_or(Error::::MathError)?, + ) + .ok_or(Error::::MathError)?; + self.total_counted = self + .total_counted + .checked_add(&highest_bottom.amount) + .ok_or(Error::::MathError)?; + self.total_backing = + self.total_backing.checked_sub(&less).ok_or(Error::::MathError)?; + self.add_top_delegation(highest_bottom); + self.add_bottom_delegation(lowest_top); + return Ok(false) + } else { + // no existing bottom delegators so update both counters the same magnitude + self.total_counted = + self.total_counted.checked_sub(&less).ok_or(Error::::MathError)?; + self.total_backing = + self.total_backing.checked_sub(&less).ok_or(Error::::MathError)?; + return Ok(true) + } + } + for x in &mut self.bottom_delegations { + if x.owner == delegator { + x.amount = x.amount.checked_sub(&less).ok_or(Error::::MathError)?; + self.total_backing = + self.total_backing.checked_sub(&less).ok_or(Error::::MathError)?; + break + } + } + self.sort_bottom_delegations(); + Ok(false) + } + pub fn go_offline(&mut self) { + self.state = CollatorStatus::Idle; + } + pub fn go_online(&mut self) { + self.state = CollatorStatus::Active; + } + pub fn leave(&mut self) -> Result<(RoundIndex, RoundIndex), DispatchError> { + ensure!(!self.is_leaving(), Error::::CandidateAlreadyLeaving); + let now = >::get().current; + let when = now.saturating_add(T::LeaveCandidatesDelay::get()); + self.state = CollatorStatus::Leaving(when); + Ok((now, when)) + } + } + + impl From> + for CollatorSnapshot + { + fn from( + other: CollatorCandidate, + ) -> CollatorSnapshot { + CollatorSnapshot { + bond: other.bond, + delegations: other.top_delegations, + total: other.total_counted, + liquidity_token: other.liquidity_token, + } + } + } + + #[derive(Clone, PartialEq, Encode, Decode, RuntimeDebug, TypeInfo)] + pub enum DelegatorStatus { + /// Active with no scheduled exit + Active, + /// Schedule exit to revoke all ongoing delegations + Leaving(RoundIndex), + } + + impl Default for DelegatorStatus { + fn default() -> DelegatorStatus { + DelegatorStatus::Active + } + } + + #[derive(Clone, Encode, Decode, RuntimeDebug, TypeInfo)] + /// Delegator state + pub struct Delegator { + /// Delegator account + pub id: AccountId, + /// All current delegations + pub delegations: OrderedSet>, + /// Requests to change delegations, relevant iff active + pub requests: PendingDelegationRequests, + /// Status for this delegator + pub status: DelegatorStatus, + } + + impl Default + for Delegator + { + fn default() -> Self { + Self { + id: AccountId::from_zeros(), + delegations: Default::default(), + requests: Default::default(), + status: Default::default(), + } + } + } + + impl Delegator + where + A: Ord + Clone, + Balance: Ord + Copy + Saturating + CheckedAdd + CheckedSub, + CurrencyId: Copy, + { + pub fn new(id: A, collator: A, amount: Balance, liquidity_token: CurrencyId) -> Self { + Delegator { + id, + delegations: OrderedSet::from(vec![Bond { + owner: collator, + amount, + liquidity_token, + }]), + requests: PendingDelegationRequests::new(), + status: DelegatorStatus::Active, + } + } + + pub fn requests(&self) -> BTreeMap> { + self.requests.requests.clone() + } + + pub fn is_active(&self) -> bool { + matches!(self.status, DelegatorStatus::Active) + } + + pub fn is_leaving(&self) -> bool { + matches!(self.status, DelegatorStatus::Leaving(_)) + } + + /// Can only leave if the current round is less than or equal to scheduled execution round + /// - returns None if not in leaving state + pub fn can_execute_leave(&self, delegation_weight_hint: u32) -> DispatchResult { + ensure!( + delegation_weight_hint >= (self.delegations.0.len() as u32), + Error::::TooLowDelegationCountToLeaveDelegators + ); + if let DelegatorStatus::Leaving(when) = self.status { + ensure!(>::get().current >= when, Error::::DelegatorCannotLeaveYet); + Ok(()) + } else { + Err(Error::::DelegatorNotLeaving.into()) + } + } + + /// Set status to leaving + pub(crate) fn set_leaving(&mut self, when: RoundIndex) { + self.status = DelegatorStatus::Leaving(when); + } + + /// Schedule status to exit + pub fn schedule_leave(&mut self) -> (RoundIndex, RoundIndex) { + let now = >::get().current; + let when = now.saturating_add(T::LeaveDelegatorsDelay::get()); + self.set_leaving(when); + (now, when) + } + + /// Set delegator status to active + pub fn cancel_leave(&mut self) { + self.status = DelegatorStatus::Active + } + + pub fn add_delegation(&mut self, bond: Bond) -> bool { + if self.delegations.insert(bond) { + true + } else { + false + } + } + + // Return Some(remaining balance), must be more than MinDelegatorStk + // Return None if delegation not found + pub fn rm_delegation(&mut self, collator: A) -> Option { + let mut amt: Option = None; + let delegations = self + .delegations + .0 + .iter() + .filter_map(|x| { + if x.owner == collator { + amt = Some(x.amount); + None + } else { + Some(x.clone()) + } + }) + .collect(); + if let Some(_) = amt { + self.delegations = OrderedSet::from(delegations); + Some(self.delegations.0.len()) + } else { + None + } + } + + /// Schedule increase delegation + pub fn schedule_increase_delegation( + &mut self, + collator: A, + more: Balance, + use_balance_from: Option, + ) -> Result + where + T::AccountId: From, + BalanceOf: From, + CurrencyIdOf: From, + { + let Bond { liquidity_token, .. } = self + .delegations + .0 + .iter() + .find(|b| b.owner == collator) + .ok_or(Error::::DelegationDNE)?; + let delegator_id: T::AccountId = self.id.clone().into(); + ensure!( + ::StakingReservesProvider::can_bond( + (*liquidity_token).into(), + &delegator_id, + more.into(), + use_balance_from + ), + Error::::InsufficientBalance + ); + let when = >::get().current.saturating_add(T::DelegationBondDelay::get()); + self.requests.bond_more::(collator, more, when)?; + Ok(when) + } + + /// Schedule decrease delegation + pub fn schedule_decrease_delegation( + &mut self, + collator: A, + less: Balance, + ) -> Result + where + BalanceOf: Into, + { + // get delegation amount + let Bond { amount, .. } = self + .delegations + .0 + .iter() + .find(|b| b.owner == collator) + .ok_or(Error::::DelegationDNE)?; + ensure!( + *amount >= T::MinDelegation::get().into().saturating_add(less), + Error::::DelegationBelowMin + ); + let when = >::get().current.saturating_add(T::DelegationBondDelay::get()); + self.requests.bond_less::(collator, less, when)?; + Ok(when) + } + + /// Schedule revocation for the given collator + pub fn schedule_revoke( + &mut self, + collator: A, + ) -> Result<(RoundIndex, RoundIndex), DispatchError> { + // get delegation amount + let Bond { amount, .. } = self + .delegations + .0 + .iter() + .find(|b| b.owner == collator) + .ok_or(Error::::DelegationDNE)?; + let now = >::get().current; + let when = now.saturating_add(T::RevokeDelegationDelay::get()); + // add revocation to pending requests + self.requests.revoke::(collator, *amount, when)?; + Ok((now, when)) + } + + /// Execute pending delegation change request + pub fn execute_pending_request( + &mut self, + candidate: A, + use_balance_from: Option, + ) -> DispatchResult + where + T::AccountId: From, + BalanceOf: From + Into, + CurrencyIdOf: From, + Delegator, CurrencyIdOf>: + From>, + { + let now = >::get().current; + let DelegationRequest { amount, action, when_executable, .. } = self + .requests + .requests + .remove(&candidate) + .ok_or(Error::::PendingDelegationRequestDNE)?; + ensure!(when_executable <= now, Error::::PendingDelegationRequestNotDueYet); + let (balance_amt, candidate_id, delegator_id): (Balance, T::AccountId, T::AccountId) = + (amount.into(), candidate.clone().into(), self.id.clone().into()); + match action { + DelegationChange::Revoke => { + // revoking last delegation => leaving set of delegators + let leaving = if self.delegations.0.len() == 1usize { true } else { false }; + // remove delegation from delegator state + self.rm_delegation(candidate.clone()); + // remove delegation from collator state delegations + Pallet::::delegator_leaves_collator( + delegator_id.clone(), + candidate_id.clone(), + )?; + Pallet::::deposit_event(Event::DelegationRevoked( + delegator_id.clone(), + candidate_id, + balance_amt.into(), + )); + if leaving { + >::remove(&delegator_id); + Pallet::::deposit_event(Event::DelegatorLeft( + delegator_id, + balance_amt.into(), + )); + } else { + let nom_st: Delegator, CurrencyIdOf> = + self.clone().into(); + >::insert(&delegator_id, nom_st); + } + Ok(()) + }, + DelegationChange::Increase => { + // increase delegation + for x in &mut self.delegations.0 { + if x.owner == candidate { + x.amount = + x.amount.checked_add(&amount).ok_or(Error::::MathError)?; + // update collator state delegation + let mut collator_state = >::get(&candidate_id) + .ok_or(Error::::CandidateDNE)?; + ::StakingReservesProvider::bond( + x.liquidity_token.into(), + &self.id.clone().into(), + balance_amt.into(), + use_balance_from, + )?; + let before = collator_state.total_counted; + let in_top = collator_state.increase_delegation::( + self.id.clone().into(), + balance_amt.into(), + )?; + let after = collator_state.total_counted; + if collator_state.is_active() && (before != after) { + Pallet::::update_active( + candidate_id.clone(), + after, + collator_state.liquidity_token, + ); + } + let new_total_staked = >::get(collator_state.liquidity_token) + .saturating_add(balance_amt.into()); + >::insert(collator_state.liquidity_token, new_total_staked); + >::insert(&candidate_id, collator_state); + let nom_st: Delegator, CurrencyIdOf> = + self.clone().into(); + >::insert(&delegator_id, nom_st); + Pallet::::deposit_event(Event::DelegationIncreased( + delegator_id, + candidate_id, + balance_amt.into(), + in_top, + )); + return Ok(()) + } + } + Err(Error::::DelegationDNE.into()) + }, + DelegationChange::Decrease => { + // decrease delegation + for x in &mut self.delegations.0 { + if x.owner == candidate { + if x.amount > amount.saturating_add(T::MinDelegation::get().into()) { + x.amount = + x.amount.checked_sub(&amount).ok_or(Error::::MathError)?; + let mut collator = >::get(&candidate_id) + .ok_or(Error::::CandidateDNE)?; + let debug_amount = + ::StakingReservesProvider::unbond( + x.liquidity_token.into(), + &delegator_id, + balance_amt.into(), + ); + if !debug_amount.is_zero() { + log::warn!( + "Unbond in staking returned non-zero value {:?}", + debug_amount + ); + } + let before = collator.total_counted; + // need to go into decrease_delegation + let in_top = collator.decrease_delegation::( + delegator_id.clone(), + balance_amt.into(), + )?; + let after = collator.total_counted; + if collator.is_active() && (before != after) { + Pallet::::update_active( + candidate_id.clone(), + after, + collator.liquidity_token, + ); + } + let new_total_staked = >::get(collator.liquidity_token) + .saturating_sub(balance_amt.into()); + >::insert(collator.liquidity_token, new_total_staked); + >::insert(&candidate_id, collator); + let nom_st: Delegator, CurrencyIdOf> = + self.clone().into(); + >::insert(&delegator_id, nom_st); + Pallet::::deposit_event(Event::DelegationDecreased( + delegator_id, + candidate_id, + balance_amt.into(), + in_top, + )); + return Ok(()) + } else { + // must rm entire delegation if x.amount <= less or cancel request + return Err(Error::::DelegationBelowMin.into()) + } + } + } + Err(Error::::DelegationDNE.into()) + }, + } + } + + /// Cancel pending delegation change request + pub fn cancel_pending_request( + &mut self, + candidate: A, + ) -> Result, DispatchError> { + let order = self + .requests + .requests + .remove(&candidate) + .ok_or(Error::::PendingDelegationRequestDNE)?; + Ok(order) + } + } + + #[derive(Clone, Eq, PartialEq, Encode, Decode, RuntimeDebug, TypeInfo)] + /// Changes requested by the delegator + /// - limit of 1 ongoing change per delegation + /// - no changes allowed if delegator is leaving + pub enum DelegationChange { + Revoke, + Increase, + Decrease, + } + + #[derive(Clone, Eq, PartialEq, Encode, Decode, RuntimeDebug, TypeInfo)] + pub struct DelegationRequest { + pub collator: AccountId, + pub amount: Balance, + pub when_executable: RoundIndex, + pub action: DelegationChange, + } + + #[derive(Clone, Encode, Decode, RuntimeDebug, TypeInfo)] + /// Pending requests to mutate delegations for each delegator + pub struct PendingDelegationRequests { + /// Map from collator -> Request (enforces at most 1 pending request per delegation) + pub requests: BTreeMap>, + } + + impl Default for PendingDelegationRequests { + fn default() -> PendingDelegationRequests { + PendingDelegationRequests { requests: BTreeMap::new() } + } + } + + impl PendingDelegationRequests { + /// New default (empty) pending requests + pub fn new() -> PendingDelegationRequests { + PendingDelegationRequests::default() + } + /// Add bond more order to pending requests + pub fn bond_more( + &mut self, + collator: A, + amount: Balance, + when_executable: RoundIndex, + ) -> DispatchResult { + ensure!( + self.requests.get(&collator).is_none(), + Error::::PendingDelegationRequestAlreadyExists + ); + self.requests.insert( + collator.clone(), + DelegationRequest { + collator, + amount, + when_executable, + action: DelegationChange::Increase, + }, + ); + Ok(()) + } + /// Add bond less order to pending requests, only succeeds if returns true + /// - limit is the maximum amount allowed that can be subtracted from the delegation + /// before it would be below the minimum delegation amount + pub fn bond_less( + &mut self, + collator: A, + amount: Balance, + when_executable: RoundIndex, + ) -> DispatchResult { + ensure!( + self.requests.get(&collator).is_none(), + Error::::PendingDelegationRequestAlreadyExists + ); + self.requests.insert( + collator.clone(), + DelegationRequest { + collator, + amount, + when_executable, + action: DelegationChange::Decrease, + }, + ); + Ok(()) + } + /// Add revoke order to pending requests + /// - limit is the maximum amount allowed that can be subtracted from the delegation + /// before it would be below the minimum delegation amount + pub fn revoke( + &mut self, + collator: A, + amount: Balance, + when_executable: RoundIndex, + ) -> DispatchResult { + ensure!( + self.requests.get(&collator).is_none(), + Error::::PendingDelegationRequestAlreadyExists + ); + self.requests.insert( + collator.clone(), + DelegationRequest { + collator, + amount, + when_executable, + action: DelegationChange::Revoke, + }, + ); + Ok(()) + } + } + + #[derive(Copy, Clone, PartialEq, Eq, Encode, Decode, RuntimeDebug, TypeInfo)] + /// The current round index and transition information + pub struct RoundInfo { + /// Current round index + pub current: RoundIndex, + /// The first block of the current round + pub first: BlockNumber, + /// The length of the current round in number of blocks + pub length: u32, + } + impl< + B: Copy + + sp_std::ops::Add + + sp_std::ops::Sub + + From + + PartialOrd + + One + + Zero + + Saturating + + CheckedMul, + > RoundInfo + { + pub fn new(current: RoundIndex, first: B, length: u32) -> RoundInfo { + RoundInfo { current, first, length } + } + /// Check if the round should be updated + pub fn should_update(&self, now: B) -> bool { + now.saturating_add(One::one()) >= self.first.saturating_add(self.length.into()) + } + /// New round + pub fn update(&mut self, now: B) { + self.current = self.current.saturating_add(1u32); + self.first = now; + } + } + impl< + B: Copy + + sp_std::ops::Add + + sp_std::ops::Sub + + From + + PartialOrd + + One + + Zero + + Saturating + + CheckedMul, + > Default for RoundInfo + { + fn default() -> RoundInfo { + RoundInfo::new(0u32, Zero::zero(), 20u32) + } + } + + pub(crate) type RoundIndex = u32; + type RewardPoint = u32; + + #[cfg(feature = "runtime-benchmarks")] + pub trait StakingBenchmarkConfig: pallet_session::Config + pallet_issuance::Config { + type Balance; + type CurrencyId; + type RewardsApi: ProofOfStakeRewardsApi; + type Xyk: XykFunctionsTrait; + } + + #[cfg(not(feature = "runtime-benchmarks"))] + pub trait StakingBenchmarkConfig {} + + pub type BalanceOf = <::Currency as MultiTokenCurrency< + ::AccountId, + >>::Balance; + + pub type CurrencyIdOf = <::Currency as MultiTokenCurrency< + ::AccountId, + >>::CurrencyId; + + /// Configuration trait of this pallet. + #[pallet::config] + pub trait Config: frame_system::Config + StakingBenchmarkConfig { + /// Overarching event type + type RuntimeEvent: From> + IsType<::RuntimeEvent>; + /// Multipurpose-liquidity + type StakingReservesProvider: StakingReservesProviderTrait< + Self::AccountId, + BalanceOf, + CurrencyIdOf, + >; + /// The currency type + type Currency: MultiTokenCurrency + + MultiTokenReservableCurrency + + MultiTokenCurrencyExtended; + /// The origin for monetary governance + type MonetaryGovernanceOrigin: EnsureOrigin; + /// Default number of blocks per round at genesis + #[pallet::constant] + type BlocksPerRound: Get; + /// Number of rounds that candidates remain bonded before exit request is executable + #[pallet::constant] + type LeaveCandidatesDelay: Get; + /// Number of rounds that candidate requests to adjust self-bond must wait to be executable + #[pallet::constant] + type CandidateBondDelay: Get; + /// Number of rounds that delegators remain bonded before exit request is executable + #[pallet::constant] + type LeaveDelegatorsDelay: Get; + /// Number of rounds that delegations remain bonded before revocation request is executable + #[pallet::constant] + type RevokeDelegationDelay: Get; + /// Number of rounds that delegation {more, less} requests must wait before executable + #[pallet::constant] + type DelegationBondDelay: Get; + /// Number of rounds after which block authors are rewarded + #[pallet::constant] + type RewardPaymentDelay: Get; + /// Minimum number of selected candidates every round + #[pallet::constant] + type MinSelectedCandidates: Get; + /// Maximum collator candidates allowed + #[pallet::constant] + type MaxCollatorCandidates: Get; + /// Maximum delegators allowed per candidate + #[pallet::constant] + type MaxTotalDelegatorsPerCandidate: Get; + /// Maximum delegators counted per candidate + #[pallet::constant] + type MaxDelegatorsPerCandidate: Get; + #[pallet::constant] + type DefaultPayoutLimit: Get; + /// Maximum delegations per delegator + #[pallet::constant] + type MaxDelegationsPerDelegator: Get; + /// Default commission due to collators, is `CollatorCommission` storage value in genesis + #[pallet::constant] + type DefaultCollatorCommission: Get; + /// Minimum stake required for any candidate to be in `SelectedCandidates` for the round + #[pallet::constant] + type MinCollatorStk: Get>; + /// Minimum stake required for any account to be a collator candidate + #[pallet::constant] + type MinCandidateStk: Get>; + /// Minimum stake for any registered on-chain account to delegate + #[pallet::constant] + type MinDelegation: Get>; + /// The native token used for payouts + #[pallet::constant] + type NativeTokenId: Get>; + /// The valuator for our staking liquidity tokens, i.e., XYK + /// This should never return (_, Zero::zero()) + type ValuateForNative: ValuateFor< + Self::NativeTokenId, + Balance = BalanceOf, + CurrencyId = CurrencyIdOf, + >; + /// The module used for computing and getting issuance + type Issuance: ComputeIssuance + GetIssuance>; + #[pallet::constant] + /// The account id that holds the liquidity mining issuance + type StakingIssuanceVault: Get; + /// The module that provides the set of fallback accounts + type FallbackProvider: GetMembers; + /// Weight information for extrinsics in this pallet. + type WeightInfo: WeightInfo; + /// Sequencer rewarding hooks + type SequencerStakingRewards: SequencerStakingRewardsTrait; + } + + #[pallet::error] + pub enum Error { + DelegatorDNE, + DelegatorDNEinTopNorBottom, + DelegatorDNEInDelegatorSet, + CandidateDNE, + DelegationDNE, + DelegatorExists, + CandidateExists, + CandidateBondBelowMin, + InsufficientBalance, + DelegationBelowMin, + AlreadyOffline, + AlreadyActive, + DelegatorAlreadyLeaving, + DelegatorNotLeaving, + DelegatorCannotLeaveYet, + CannotDelegateIfLeaving, + CandidateAlreadyLeaving, + CandidateNotLeaving, + CandidateCannotLeaveYet, + CannotGoOnlineIfLeaving, + ExceedMaxDelegationsPerDelegator, + AlreadyDelegatedCandidate, + InvalidSchedule, + CannotSetBelowMin, + NoWritingSameValue, + TooLowCandidateCountWeightHintJoinCandidates, + TooLowCandidateCountWeightHintCancelLeaveCandidates, + TooLowCandidateCountToLeaveCandidates, + TooLowDelegationCountToDelegate, + TooLowCandidateDelegationCountToDelegate, + TooLowDelegationCountToLeaveDelegators, + PendingCandidateRequestsDNE, + PendingCandidateRequestAlreadyExists, + PendingCandidateRequestNotDueYet, + PendingDelegationRequestDNE, + PendingDelegationRequestAlreadyExists, + PendingDelegationRequestNotDueYet, + StakingLiquidityTokenNotListed, + TooLowCurrentStakingLiquidityTokensCount, + StakingLiquidityTokenAlreadyListed, + ExceedMaxCollatorCandidates, + ExceedMaxTotalDelegatorsPerCandidate, + CandidateNotAggregating, + CandidateNotAggregatingUnderAggregator, + CandidateAlreadyApprovedByAggregator, + AggregatorExists, + CollatorRoundRewardsDNE, + DelegatorRewardsDNE, + AggregatorDNE, + TargettedAggregatorSameAsCurrent, + CandidateNotApprovedByAggregator, + AggregatorLiquidityTokenTaken, + IncorrectRewardDelegatorCount, + MathError, + } + + #[pallet::event] + #[pallet::generate_deposit(pub(crate) fn deposit_event)] + pub enum Event { + /// Starting Block, Round, Number of Collators Selected, Total Balance + NewRound(BlockNumberFor, RoundIndex, u32, BalanceOf), + /// Account, Amount Locked, New Total Amt Locked + JoinedCollatorCandidates(T::AccountId, BalanceOf, BalanceOf), + /// Round, Collator Account, Total Exposed Amount (includes all delegations) + CollatorChosen(RoundIndex, T::AccountId, BalanceOf), + /// Candidate, Amount To Increase, Round at which request can be executed by caller + CandidateBondMoreRequested(T::AccountId, BalanceOf, RoundIndex), + /// Candidate, Amount To Decrease, Round at which request can be executed by caller + CandidateBondLessRequested(T::AccountId, BalanceOf, RoundIndex), + /// Candidate, Amount, New Bond Total + CandidateBondedMore(T::AccountId, BalanceOf, BalanceOf), + /// Candidate, Amount, New Bond + CandidateBondedLess(T::AccountId, BalanceOf, BalanceOf), + /// Round Offline, Candidate + CandidateWentOffline(RoundIndex, T::AccountId), + /// Round Online, Candidate + CandidateBackOnline(RoundIndex, T::AccountId), + /// Round At Which Exit Is Allowed, Candidate, Scheduled Exit + CandidateScheduledExit(RoundIndex, T::AccountId, RoundIndex), + /// Candidate + CancelledCandidateExit(T::AccountId), + /// Candidate, Cancelled Request + CancelledCandidateBondChange(T::AccountId, CandidateBondRequest>), + /// Ex-Candidate, Amount Unlocked, New Total Amt Locked + CandidateLeft(T::AccountId, BalanceOf, BalanceOf), + /// Delegator, Candidate, Amount to be increased, Round at which can be executed + DelegationIncreaseScheduled(T::AccountId, T::AccountId, BalanceOf, RoundIndex), + /// Delegator, Candidate, Amount to be decreased, Round at which can be executed + DelegationDecreaseScheduled(T::AccountId, T::AccountId, BalanceOf, RoundIndex), + // Delegator, Candidate, Amount, If in top delegations for candidate after increase + DelegationIncreased(T::AccountId, T::AccountId, BalanceOf, bool), + // Delegator, Candidate, Amount, If in top delegations for candidate after decrease + DelegationDecreased(T::AccountId, T::AccountId, BalanceOf, bool), + /// Round, Delegator, Scheduled Exit + DelegatorExitScheduled(RoundIndex, T::AccountId, RoundIndex), + /// Round, Delegator, Candidate, Scheduled Exit + DelegationRevocationScheduled(RoundIndex, T::AccountId, T::AccountId, RoundIndex), + /// Delegator, Amount Unstaked + DelegatorLeft(T::AccountId, BalanceOf), + /// Delegator, Candidate, Amount Unstaked + DelegationRevoked(T::AccountId, T::AccountId, BalanceOf), + /// Delegator + DelegatorExitCancelled(T::AccountId), + /// Delegator, Cancelled Request + CancelledDelegationRequest(T::AccountId, DelegationRequest>), + /// Delegator, Amount Locked, Candidate, Delegator Position with New Total Counted if in Top + Delegation(T::AccountId, BalanceOf, T::AccountId, DelegatorAdded>), + /// Delegator, Candidate, Amount Unstaked, New Total Amt Staked for Candidate + DelegatorLeftCandidate(T::AccountId, T::AccountId, BalanceOf, BalanceOf), + /// Session index, Delegator, Collator, Due reward (as per counted delegation for collator) + DelegatorDueReward(RoundIndex, T::AccountId, T::AccountId, BalanceOf), + /// Paid the account (delegator or collator) the balance as liquid rewards + Rewarded(RoundIndex, T::AccountId, BalanceOf), + /// Notify about reward periods that has been paid (collator, payout rounds, any rewards left) + CollatorRewardsDistributed(T::AccountId, PayoutRounds), + /// Staking expectations set + StakeExpectationsSet(BalanceOf, BalanceOf, BalanceOf), + /// Set total selected candidates to this value [old, new] + TotalSelectedSet(u32, u32), + /// Set collator commission to this value [old, new] + CollatorCommissionSet(Perbill, Perbill), + /// A candidate updated aggregator + CandidateAggregatorUpdated(T::AccountId, Option), + /// An agggregator's metadata has been updated + AggregatorMetadataUpdated(T::AccountId), + } + + #[pallet::hooks] + impl Hooks> for Pallet { + fn on_idle(_now: BlockNumberFor, remaining_weight: Weight) -> Weight { + // some extra offset on top + let claim_cost = ::WeightInfo::payout_collator_rewards(); + if remaining_weight.ref_time() > claim_cost.ref_time() { + if let Some((collator, _round)) = RoundCollatorRewardInfo::::iter_keys().next() { + let _ = Self::do_payout_collator_rewards(collator, Some(1)); + } + } + + claim_cost + } + } + + #[pallet::storage] + #[pallet::getter(fn collator_commission)] + /// Commission percent taken off of rewards for all collators + type CollatorCommission = StorageValue<_, Perbill, ValueQuery>; + + #[pallet::storage] + #[pallet::getter(fn total_selected)] + /// The total candidates selected every round + pub(crate) type TotalSelected = StorageValue<_, u32, ValueQuery>; + + #[pallet::storage] + #[pallet::getter(fn round)] + /// Current round index and next round scheduled transition + pub(crate) type Round = StorageValue<_, RoundInfo>, ValueQuery>; + + #[pallet::storage] + #[pallet::getter(fn delegator_state)] + /// Get delegator state associated with an account if account is delegating else None + pub(crate) type DelegatorState = StorageMap< + _, + Twox64Concat, + T::AccountId, + Delegator, CurrencyIdOf>, + OptionQuery, + >; + + #[pallet::storage] + #[pallet::getter(fn candidate_state)] + /// Get collator candidate state associated with an account if account is a candidate else None + pub(crate) type CandidateState = StorageMap< + _, + Twox64Concat, + T::AccountId, + CollatorCandidate, CurrencyIdOf>, + OptionQuery, + >; + + #[pallet::storage] + #[pallet::getter(fn selected_candidates)] + /// The collator candidates selected for the current round + /// Block authors selection algorithm details [`Pallet::select_top_candidates`] + type SelectedCandidates = StorageValue<_, Vec, ValueQuery>; + + #[pallet::storage] + #[pallet::getter(fn total)] + /// Total capital locked by this staking pallet + type Total = StorageMap<_, Twox64Concat, CurrencyIdOf, BalanceOf, ValueQuery>; + + #[pallet::storage] + #[pallet::getter(fn candidate_pool)] + /// The pool of collator candidates, each with their total backing stake + type CandidatePool = + StorageValue<_, OrderedSet, CurrencyIdOf>>, ValueQuery>; + + #[pallet::storage] + #[pallet::getter(fn at_stake)] + /// Snapshot of collator delegation stake at the start of the round + pub type AtStake = StorageDoubleMap< + _, + Twox64Concat, + RoundIndex, + Twox64Concat, + T::AccountId, + CollatorSnapshot, CurrencyIdOf>, + ValueQuery, + >; + + #[pallet::storage] + #[pallet::getter(fn points)] + /// Total points awarded to collators for block production in the round + pub type Points = StorageMap<_, Twox64Concat, RoundIndex, RewardPoint, ValueQuery>; + + #[pallet::storage] + #[pallet::getter(fn awarded_pts)] + /// Points for each collator per round + pub type AwardedPts = StorageDoubleMap< + _, + Twox64Concat, + RoundIndex, + Twox64Concat, + T::AccountId, + RewardPoint, + ValueQuery, + >; + + #[pallet::storage] + #[pallet::getter(fn staking_liquidity_tokens)] + pub type StakingLiquidityTokens = StorageValue< + _, + BTreeMap, Option<(BalanceOf, BalanceOf)>>, + ValueQuery, + >; + + #[pallet::storage] + #[pallet::getter(fn get_candidate_aggregator)] + /// Maps collator to its aggregator + pub type CandidateAggregator = + StorageValue<_, BTreeMap, ValueQuery>; + + #[pallet::storage] + #[pallet::getter(fn get_aggregator_metadata)] + /// Stores information about approved candidates for aggregation + pub type AggregatorMetadata = StorageMap< + _, + Blake2_128Concat, + T::AccountId, + AggregatorMetadataType>, + OptionQuery, + >; + + #[pallet::storage] + #[pallet::getter(fn get_round_aggregator_info)] + /// Stored once per session, maps aggregator to list of assosiated candidates + pub type RoundAggregatorInfo = StorageMap< + _, + Twox64Concat, + RoundIndex, + BTreeMap>>, + OptionQuery, + >; + + #[pallet::storage] + #[pallet::getter(fn get_round_collator_reward_info)] + /// Stores information about rewards per each session + pub type RoundCollatorRewardInfo = StorageDoubleMap< + _, + Blake2_128Concat, + T::AccountId, + Twox64Concat, + RoundIndex, + RoundCollatorRewardInfoType>, + OptionQuery, + >; + + #[derive(Clone, Eq, PartialEq, Encode, Decode, RuntimeDebug, TypeInfo)] + pub struct RoundCollatorRewardInfoType { + pub collator_reward: Balance, + pub delegator_rewards: BTreeMap, + } + + impl Default for RoundCollatorRewardInfoType { + fn default() -> RoundCollatorRewardInfoType { + Self { collator_reward: Default::default(), delegator_rewards: Default::default() } + } + } + + #[derive(Clone, Eq, PartialEq, Encode, Decode, RuntimeDebug, TypeInfo)] + pub struct AggregatorMetadataType { + pub token_collator_map: BTreeMap, + pub approved_candidates: BTreeSet, + } + + impl Default for AggregatorMetadataType { + fn default() -> AggregatorMetadataType { + Self { token_collator_map: Default::default(), approved_candidates: Default::default() } + } + } + + #[pallet::genesis_config] + pub struct GenesisConfig { + pub candidates: Vec<(T::AccountId, BalanceOf, CurrencyIdOf)>, + pub delegations: Vec<(T::AccountId, T::AccountId, BalanceOf)>, + } + + impl Default for GenesisConfig { + fn default() -> Self { + Self { candidates: vec![], delegations: vec![] } + } + } + + #[pallet::genesis_build] + impl BuildGenesisConfig for GenesisConfig { + fn build(&self) { + let mut liquidity_token_list: Vec> = self + .candidates + .iter() + .cloned() + .map(|(_, _, l)| l) + .collect::>>(); + liquidity_token_list.sort(); + liquidity_token_list.dedup(); + let liquidity_token_count: u32 = liquidity_token_list.len().try_into().unwrap(); + for (i, liquidity_token) in liquidity_token_list.iter().enumerate() { + if *liquidity_token != T::NativeTokenId::get() { + if let Err(error) = >::add_staking_liquidity_token( + RawOrigin::Root.into(), + PairedOrLiquidityToken::Liquidity(*liquidity_token), + i as u32, + ) { + log::warn!( + "Adding staking liquidity token failed in genesis with error {:?}", + error + ); + } + } + } + let mut candidate_count = 0u32; + // Initialize the candidates + for &(ref candidate, balance, liquidity_token) in &self.candidates { + assert!( + ::Currency::available_balance( + liquidity_token.into(), + candidate + ) >= balance, + "Account does not have enough balance to bond as a candidate." + ); + candidate_count = candidate_count.saturating_add(1u32); + if let Err(error) = >::join_candidates( + T::RuntimeOrigin::from(Some(candidate.clone()).into()), + balance, + liquidity_token, + None, + candidate_count, + liquidity_token_count, + ) { + log::warn!("Join candidates failed in genesis with error {:?}", error); + } else { + candidate_count = candidate_count.saturating_add(1u32); + } + } + let mut col_delegator_count: BTreeMap = BTreeMap::new(); + let mut del_delegation_count: BTreeMap = BTreeMap::new(); + // Initialize the delegations + for &(ref delegator, ref target, balance) in &self.delegations { + let associated_collator = self.candidates.iter().find(|b| b.0 == *target); + let collator_liquidity_token = + associated_collator.expect("Delegation to non-existant collator").2; + assert!( + ::Currency::available_balance( + collator_liquidity_token.into(), + delegator + ) >= balance, + "Account does not have enough balance to place delegation." + ); + let cd_count = + if let Some(x) = col_delegator_count.get(target) { *x } else { 0u32 }; + let dd_count = + if let Some(x) = del_delegation_count.get(delegator) { *x } else { 0u32 }; + if let Err(error) = >::delegate( + T::RuntimeOrigin::from(Some(delegator.clone()).into()), + target.clone(), + balance, + None, + cd_count, + dd_count, + ) { + log::warn!("Delegate failed in genesis with error {:?}", error); + } else { + if let Some(x) = col_delegator_count.get_mut(target) { + *x = x.saturating_add(1u32); + } else { + col_delegator_count.insert(target.clone(), 1u32); + }; + if let Some(x) = del_delegation_count.get_mut(delegator) { + *x = x.saturating_add(1u32); + } else { + del_delegation_count.insert(delegator.clone(), 1u32); + }; + } + } + // Set collator commission to default config + >::put(T::DefaultCollatorCommission::get()); + // Set total selected candidates to minimum config + >::put(T::MinSelectedCandidates::get()); + // Choose top TotalSelected collator candidates + let (v_count, _, total_relevant_exposure) = >::select_top_candidates(1u32); + // Start Round 0 at Block 0 + let round: RoundInfo> = + RoundInfo::new(0u32, 0u32.into(), ::BlocksPerRound::get()); + >::put(round); + // So that round 0 can be rewarded + for atstake in >::iter_prefix(1u32) { + >::insert(0u32, atstake.0, atstake.1); + } + >::deposit_event(Event::NewRound( + BlockNumberFor::::zero(), + 0u32, + v_count, + total_relevant_exposure, + )); + } + } + + #[pallet::call] + impl Pallet { + #[pallet::call_index(0)] + #[pallet::weight(::WeightInfo::set_total_selected())] + /// Set the total number of collator candidates selected per round + /// - changes are not applied until the start of the next round + pub fn set_total_selected(origin: OriginFor, new: u32) -> DispatchResultWithPostInfo { + frame_system::ensure_root(origin)?; + ensure!(new >= T::MinSelectedCandidates::get(), Error::::CannotSetBelowMin); + let old = >::get(); + ensure!(old != new, Error::::NoWritingSameValue); + >::put(new); + Self::deposit_event(Event::TotalSelectedSet(old, new)); + Ok(().into()) + } + + #[pallet::call_index(1)] + #[pallet::weight(::WeightInfo::set_collator_commission())] + /// Set the commission for all collators + pub fn set_collator_commission( + origin: OriginFor, + new: Perbill, + ) -> DispatchResultWithPostInfo { + frame_system::ensure_root(origin)?; + let old = >::get(); + ensure!(old != new, Error::::NoWritingSameValue); + >::put(new); + Self::deposit_event(Event::CollatorCommissionSet(old, new)); + Ok(().into()) + } + + #[pallet::call_index(2)] + #[pallet::weight(::WeightInfo::join_candidates(*candidate_count, *liquidity_token_count))] + /// Join the set of collator candidates + pub fn join_candidates( + origin: OriginFor, + bond: BalanceOf, + liquidity_token: CurrencyIdOf, + use_balance_from: Option, + candidate_count: u32, + liquidity_token_count: u32, + ) -> DispatchResultWithPostInfo { + let acc = ensure_signed(origin)?; + ensure!(!Self::is_candidate(&acc), Error::::CandidateExists); + ensure!(!Self::is_delegator(&acc), Error::::DelegatorExists); + ensure!(!Self::is_aggregator(&acc), Error::::AggregatorExists); + let staking_liquidity_tokens = >::get(); + + ensure!( + liquidity_token_count as usize >= staking_liquidity_tokens.len(), + Error::::TooLowCurrentStakingLiquidityTokensCount + ); + ensure!( + staking_liquidity_tokens.contains_key(&liquidity_token) || + liquidity_token == T::NativeTokenId::get(), + Error::::StakingLiquidityTokenNotListed + ); + + ensure!( + Self::valuate_bond(liquidity_token, bond) >= T::MinCandidateStk::get(), + Error::::CandidateBondBelowMin + ); + let mut candidates = >::get(); + let old_count = candidates.0.len() as u32; + // This is a soft check + // Reinforced by similar check in go_online and cancel_leave_candidates + ensure!( + old_count < T::MaxCollatorCandidates::get(), + Error::::ExceedMaxCollatorCandidates + ); + ensure!( + candidate_count >= old_count, + Error::::TooLowCandidateCountWeightHintJoinCandidates + ); + ensure!( + candidates.insert(Bond { owner: acc.clone(), amount: bond, liquidity_token }), + Error::::CandidateExists + ); + // reserve must be called before storage changes + // and before any unsafe math operations with `bond: Balance` + ::StakingReservesProvider::bond( + liquidity_token, + &acc, + bond, + use_balance_from, + )?; + let candidate = CollatorCandidate::new(acc.clone(), bond, liquidity_token); + >::insert(&acc, candidate); + >::put(candidates); + let new_total = >::get(liquidity_token).saturating_add(bond); + >::insert(liquidity_token, new_total); + Self::deposit_event(Event::JoinedCollatorCandidates(acc, bond, new_total)); + Ok(().into()) + } + + #[pallet::call_index(3)] + #[pallet::weight(::WeightInfo::schedule_leave_candidates(*candidate_count))] + /// Request to leave the set of candidates. If successful, the account is immediately + /// removed from the candidate pool to prevent selection as a collator. + pub fn schedule_leave_candidates( + origin: OriginFor, + candidate_count: u32, + ) -> DispatchResultWithPostInfo { + let collator = ensure_signed(origin)?; + let mut state = >::get(&collator).ok_or(Error::::CandidateDNE)?; + let (now, when) = state.leave::()?; + let mut candidates = >::get(); + ensure!( + candidate_count >= candidates.0.len() as u32, + Error::::TooLowCandidateCountToLeaveCandidates + ); + if candidates.remove(&Bond::from_owner(collator.clone())) { + >::put(candidates); + } + >::insert(&collator, state); + Self::deposit_event(Event::CandidateScheduledExit(now, collator, when)); + Ok(().into()) + } + + #[pallet::call_index(4)] + #[pallet::weight(::WeightInfo::execute_leave_candidates(*candidate_delegation_count))] + /// Execute leave candidates request + pub fn execute_leave_candidates( + origin: OriginFor, + candidate: T::AccountId, + candidate_delegation_count: u32, + ) -> DispatchResultWithPostInfo { + ensure_signed(origin)?; + let state = >::get(&candidate).ok_or(Error::::CandidateDNE)?; + ensure!( + state.delegators.0.len() <= candidate_delegation_count as usize, + Error::::TooLowCandidateCountToLeaveCandidates + ); + state.can_leave::()?; + + let return_stake = |bond: Bond, CurrencyIdOf>| { + let debug_amount = ::StakingReservesProvider::unbond( + bond.liquidity_token.into(), + &bond.owner, + bond.amount, + ); + if !debug_amount.is_zero() { + log::warn!("Unbond in staking returned non-zero value {:?}", debug_amount); + } + // remove delegation from delegator state + let mut delegator = DelegatorState::::get(&bond.owner).expect( + "Collator state and delegator state are consistent. + Collator state has a record of this delegation. Therefore, + Delegator state also has a record. qed.", + ); + if let Some(remaining_delegations) = delegator.rm_delegation(candidate.clone()) { + if remaining_delegations.is_zero() { + >::remove(&bond.owner); + } else { + let _ = delegator.requests.requests.remove(&candidate); + >::insert(&bond.owner, delegator); + } + } + }; + // return all top delegations + for bond in state.top_delegations { + return_stake(bond); + } + // return all bottom delegations + for bond in state.bottom_delegations { + return_stake(bond); + } + // return stake to collator + let debug_amount = ::StakingReservesProvider::unbond( + state.liquidity_token.into(), + &state.id, + state.bond, + ); + if !debug_amount.is_zero() { + log::warn!("Unbond in staking returned non-zero value {:?}", debug_amount); + } + + let res = Self::do_update_candidate_aggregator(&candidate, None); + match res { + Err(e) if e == DispatchError::from(Error::::CandidateNotAggregating) => {}, + Err(_) => { + log::error!("do_update_candidate_aggregator failed with error {:?}", res); + }, + Ok(_) => {}, + } + + >::remove(&candidate); + let new_total_staked = + >::get(state.liquidity_token).saturating_sub(state.total_backing); + >::insert(state.liquidity_token, new_total_staked); + Self::deposit_event(Event::CandidateLeft( + candidate, + state.total_backing, + new_total_staked, + )); + Ok(().into()) + } + + #[pallet::call_index(5)] + #[pallet::weight(::WeightInfo::cancel_leave_candidates(*candidate_count))] + /// Cancel open request to leave candidates + /// - only callable by collator account + /// - result upon successful call is the candidate is active in the candidate pool + pub fn cancel_leave_candidates( + origin: OriginFor, + candidate_count: u32, + ) -> DispatchResultWithPostInfo { + let collator = ensure_signed(origin)?; + let mut state = >::get(&collator).ok_or(Error::::CandidateDNE)?; + ensure!(state.is_leaving(), Error::::CandidateNotLeaving); + state.go_online(); + let mut candidates = >::get(); + // Reinforcement for the soft check in join_candiates + ensure!( + candidates.0.len() < T::MaxCollatorCandidates::get() as usize, + Error::::ExceedMaxCollatorCandidates + ); + ensure!( + candidates.0.len() as u32 <= candidate_count, + Error::::TooLowCandidateCountWeightHintCancelLeaveCandidates + ); + ensure!( + candidates.insert(Bond { + owner: collator.clone(), + amount: state.total_counted, + liquidity_token: state.liquidity_token + }), + Error::::AlreadyActive + ); + >::put(candidates); + >::insert(&collator, state); + Self::deposit_event(Event::CancelledCandidateExit(collator)); + Ok(().into()) + } + + #[pallet::call_index(6)] + #[pallet::weight(::WeightInfo::go_offline())] + /// Temporarily leave the set of collator candidates without unbonding + pub fn go_offline(origin: OriginFor) -> DispatchResultWithPostInfo { + let collator = ensure_signed(origin)?; + let mut state = >::get(&collator).ok_or(Error::::CandidateDNE)?; + ensure!(state.is_active(), Error::::AlreadyOffline); + state.go_offline(); + let mut candidates = >::get(); + if candidates.remove(&Bond::from_owner(collator.clone())) { + >::put(candidates); + } + >::insert(&collator, state); + Self::deposit_event(Event::CandidateWentOffline(>::get().current, collator)); + Ok(().into()) + } + + #[pallet::call_index(7)] + #[pallet::weight(::WeightInfo::go_online())] + /// Rejoin the set of collator candidates if previously had called `go_offline` + pub fn go_online(origin: OriginFor) -> DispatchResultWithPostInfo { + let collator = ensure_signed(origin)?; + let mut state = >::get(&collator).ok_or(Error::::CandidateDNE)?; + ensure!(!state.is_active(), Error::::AlreadyActive); + ensure!(!state.is_leaving(), Error::::CannotGoOnlineIfLeaving); + state.go_online(); + let mut candidates = >::get(); + // Reinforcement for the soft check in join_candiates + ensure!( + candidates.0.len() < T::MaxCollatorCandidates::get() as usize, + Error::::ExceedMaxCollatorCandidates + ); + ensure!( + candidates.insert(Bond { + owner: collator.clone(), + amount: state.total_counted, + liquidity_token: state.liquidity_token + }), + Error::::AlreadyActive + ); + >::put(candidates); + >::insert(&collator, state); + Self::deposit_event(Event::CandidateBackOnline(>::get().current, collator)); + Ok(().into()) + } + + #[pallet::call_index(8)] + #[pallet::weight(::WeightInfo::schedule_candidate_bond_more())] + /// Request by collator candidate to increase self bond by `more` + pub fn schedule_candidate_bond_more( + origin: OriginFor, + more: BalanceOf, + use_balance_from: Option, + ) -> DispatchResultWithPostInfo { + let collator = ensure_signed(origin)?; + let mut state = >::get(&collator).ok_or(Error::::CandidateDNE)?; + let when = state.schedule_bond_more::(more, use_balance_from)?; + >::insert(&collator, state); + Self::deposit_event(Event::CandidateBondMoreRequested(collator, more, when)); + Ok(().into()) + } + + #[pallet::call_index(9)] + #[pallet::weight(::WeightInfo::schedule_candidate_bond_less())] + /// Request by collator candidate to decrease self bond by `less` + pub fn schedule_candidate_bond_less( + origin: OriginFor, + less: BalanceOf, + ) -> DispatchResultWithPostInfo { + let collator = ensure_signed(origin)?; + let mut state = >::get(&collator).ok_or(Error::::CandidateDNE)?; + let when = state.schedule_bond_less::(less)?; + >::insert(&collator, state); + Self::deposit_event(Event::CandidateBondLessRequested(collator, less, when)); + Ok(().into()) + } + + #[pallet::call_index(10)] + #[pallet::weight(::WeightInfo::execute_candidate_bond_more())] + /// Execute pending request to adjust the collator candidate self bond + pub fn execute_candidate_bond_request( + origin: OriginFor, + candidate: T::AccountId, + use_balance_from: Option, + ) -> DispatchResultWithPostInfo { + ensure_signed(origin)?; // we may want to reward this if caller != candidate + let mut state = >::get(&candidate).ok_or(Error::::CandidateDNE)?; + let event = state.execute_pending_request::(use_balance_from)?; + >::insert(&candidate, state); + Self::deposit_event(event); + Ok(().into()) + } + + #[pallet::call_index(11)] + #[pallet::weight(::WeightInfo::cancel_candidate_bond_more())] + /// Cancel pending request to adjust the collator candidate self bond + pub fn cancel_candidate_bond_request(origin: OriginFor) -> DispatchResultWithPostInfo { + let collator = ensure_signed(origin)?; + let mut state = >::get(&collator).ok_or(Error::::CandidateDNE)?; + let event = state.cancel_pending_request::()?; + >::insert(&collator, state); + Self::deposit_event(event); + Ok(().into()) + } + + #[pallet::call_index(12)] + #[pallet::weight( + ::WeightInfo::delegate( + *candidate_delegation_count, + *delegation_count, + ) + )] + /// If caller is not a delegator and not a collator, then join the set of delegators + /// If caller is a delegator, then makes delegation to change their delegation state + pub fn delegate( + origin: OriginFor, + collator: T::AccountId, + amount: BalanceOf, + use_balance_from: Option, + candidate_delegation_count: u32, + delegation_count: u32, + ) -> DispatchResultWithPostInfo { + let acc = ensure_signed(origin)?; + ensure!(!Self::is_aggregator(&acc), Error::::AggregatorExists); + let mut collator_state = + >::get(&collator).ok_or(Error::::CandidateDNE)?; + let delegator_state = if let Some(mut state) = >::get(&acc) { + ensure!(state.is_active(), Error::::CannotDelegateIfLeaving); + // delegation after first + ensure!( + Self::valuate_bond(collator_state.liquidity_token, amount) >= + T::MinDelegation::get(), + Error::::DelegationBelowMin + ); + ensure!( + delegation_count >= state.delegations.0.len() as u32, + Error::::TooLowDelegationCountToDelegate + ); + ensure!( + (state.delegations.0.len() as u32) < T::MaxDelegationsPerDelegator::get(), + Error::::ExceedMaxDelegationsPerDelegator + ); + ensure!( + state.add_delegation(Bond { + owner: collator.clone(), + amount, + liquidity_token: collator_state.liquidity_token, + }), + Error::::AlreadyDelegatedCandidate + ); + state + } else { + ensure!(amount >= T::MinDelegation::get(), Error::::DelegationBelowMin); + ensure!(!Self::is_candidate(&acc), Error::::CandidateExists); + Delegator::new( + acc.clone(), + collator.clone(), + amount, + collator_state.liquidity_token, + ) + }; + // This check is hard + // There is no other way to add to a collators delegation count + ensure!( + collator_state.delegators.0.len() < + T::MaxTotalDelegatorsPerCandidate::get() as usize, + Error::::ExceedMaxTotalDelegatorsPerCandidate + ); + ensure!( + candidate_delegation_count >= collator_state.delegators.0.len() as u32, + Error::::TooLowCandidateDelegationCountToDelegate + ); + let delegator_position = collator_state.add_delegation::(acc.clone(), amount)?; + ::StakingReservesProvider::bond( + collator_state.liquidity_token.into(), + &acc, + amount, + use_balance_from, + )?; + if let DelegatorAdded::AddedToTop { new_total } = delegator_position { + if collator_state.is_active() { + // collator in candidate pool + Self::update_active( + collator.clone(), + new_total, + collator_state.liquidity_token, + ); + } + } + let new_total_locked = + >::get(collator_state.liquidity_token).saturating_add(amount); + >::insert(collator_state.liquidity_token, new_total_locked); + >::insert(&collator, collator_state); + >::insert(&acc, delegator_state); + Self::deposit_event(Event::Delegation(acc, amount, collator, delegator_position)); + Ok(().into()) + } + + #[pallet::call_index(13)] + #[pallet::weight(::WeightInfo::schedule_leave_delegators())] + /// Request to leave the set of delegators. If successful, the caller is scheduled + /// to be allowed to exit. Success forbids future delegator actions until the request is + /// invoked or cancelled. + pub fn schedule_leave_delegators(origin: OriginFor) -> DispatchResultWithPostInfo { + let acc = ensure_signed(origin)?; + let mut state = >::get(&acc).ok_or(Error::::DelegatorDNE)?; + ensure!(!state.is_leaving(), Error::::DelegatorAlreadyLeaving); + let (now, when) = state.schedule_leave::(); + >::insert(&acc, state); + Self::deposit_event(Event::DelegatorExitScheduled(now, acc, when)); + Ok(().into()) + } + + #[pallet::call_index(14)] + #[pallet::weight(::WeightInfo::execute_leave_delegators(*delegation_count))] + /// Execute the right to exit the set of delegators and revoke all ongoing delegations. + pub fn execute_leave_delegators( + origin: OriginFor, + delegator: T::AccountId, + delegation_count: u32, + ) -> DispatchResultWithPostInfo { + ensure_signed(origin)?; + let state = >::get(&delegator).ok_or(Error::::DelegatorDNE)?; + state.can_execute_leave::(delegation_count)?; + let mut amount_unstaked: BalanceOf = Zero::zero(); + for bond in state.delegations.0 { + amount_unstaked = amount_unstaked.saturating_add(bond.amount); + if let Err(error) = + Self::delegator_leaves_collator(delegator.clone(), bond.owner.clone()) + { + log::warn!( + "STORAGE CORRUPTED \nDelegator leaving collator failed with error: {:?}", + error + ); + } + } + >::remove(&delegator); + Self::deposit_event(Event::DelegatorLeft(delegator, amount_unstaked)); + Ok(().into()) + } + + #[pallet::call_index(15)] + #[pallet::weight(::WeightInfo::cancel_leave_delegators())] + /// Cancel a pending request to exit the set of delegators. Success clears the pending exit + /// request (thereby resetting the delay upon another `leave_delegators` call). + pub fn cancel_leave_delegators(origin: OriginFor) -> DispatchResultWithPostInfo { + let delegator = ensure_signed(origin)?; + // ensure delegator state exists + let mut state = >::get(&delegator).ok_or(Error::::DelegatorDNE)?; + // ensure state is leaving + ensure!(state.is_leaving(), Error::::DelegatorDNE); + // cancel exit request + state.cancel_leave(); + >::insert(&delegator, state); + Self::deposit_event(Event::DelegatorExitCancelled(delegator)); + Ok(().into()) + } + + #[pallet::call_index(16)] + #[pallet::weight(::WeightInfo::schedule_revoke_delegation())] + /// Request to revoke an existing delegation. If successful, the delegation is scheduled + /// to be allowed to be revoked via the `execute_delegation_request` extrinsic. + pub fn schedule_revoke_delegation( + origin: OriginFor, + collator: T::AccountId, + ) -> DispatchResultWithPostInfo { + let delegator = ensure_signed(origin)?; + let mut state = >::get(&delegator).ok_or(Error::::DelegatorDNE)?; + let (now, when) = state.schedule_revoke::(collator.clone())?; + >::insert(&delegator, state); + Self::deposit_event(Event::DelegationRevocationScheduled( + now, delegator, collator, when, + )); + Ok(().into()) + } + + #[pallet::call_index(17)] + #[pallet::weight(::WeightInfo::schedule_delegator_bond_more())] + /// Request to bond more for delegators wrt a specific collator candidate. + pub fn schedule_delegator_bond_more( + origin: OriginFor, + candidate: T::AccountId, + more: BalanceOf, + use_balance_from: Option, + ) -> DispatchResultWithPostInfo { + let delegator = ensure_signed(origin)?; + let mut state = >::get(&delegator).ok_or(Error::::DelegatorDNE)?; + let when = state.schedule_increase_delegation::( + candidate.clone(), + more, + use_balance_from, + )?; + >::insert(&delegator, state); + Self::deposit_event(Event::DelegationIncreaseScheduled( + delegator, candidate, more, when, + )); + Ok(().into()) + } + + #[pallet::call_index(18)] + #[pallet::weight(::WeightInfo::schedule_delegator_bond_less())] + /// Request bond less for delegators wrt a specific collator candidate. + pub fn schedule_delegator_bond_less( + origin: OriginFor, + candidate: T::AccountId, + less: BalanceOf, + ) -> DispatchResultWithPostInfo { + let caller = ensure_signed(origin)?; + let mut state = >::get(&caller).ok_or(Error::::DelegatorDNE)?; + let when = state.schedule_decrease_delegation::(candidate.clone(), less)?; + >::insert(&caller, state); + Self::deposit_event(Event::DelegationDecreaseScheduled(caller, candidate, less, when)); + Ok(().into()) + } + + #[pallet::call_index(19)] + #[pallet::weight(::WeightInfo::execute_delegator_bond_more())] + /// Execute pending request to change an existing delegation + pub fn execute_delegation_request( + origin: OriginFor, + delegator: T::AccountId, + candidate: T::AccountId, + use_balance_from: Option, + ) -> DispatchResultWithPostInfo { + ensure_signed(origin)?; // we may want to reward caller if caller != delegator + let mut state = >::get(&delegator).ok_or(Error::::DelegatorDNE)?; + state.execute_pending_request::(candidate, use_balance_from)?; + Ok(().into()) + } + + #[pallet::call_index(20)] + #[pallet::weight(::WeightInfo::cancel_delegator_bond_more())] + /// Cancel request to change an existing delegation. + pub fn cancel_delegation_request( + origin: OriginFor, + candidate: T::AccountId, + ) -> DispatchResultWithPostInfo { + let delegator = ensure_signed(origin)?; + let mut state = >::get(&delegator).ok_or(Error::::DelegatorDNE)?; + let request = state.cancel_pending_request::(candidate)?; + >::insert(&delegator, state); + Self::deposit_event(Event::CancelledDelegationRequest(delegator, request)); + Ok(().into()) + } + + #[pallet::call_index(21)] + #[pallet::weight(::WeightInfo::add_staking_liquidity_token(*current_liquidity_tokens))] + /// Enables new staking token to be used for staking. Only tokens paired with MGX can be + /// used. Caller can pass the id of token for which MGX paired pool already exists or + /// liquidity token id itself. **Root only** + pub fn add_staking_liquidity_token( + origin: OriginFor, + paired_or_liquidity_token: PairedOrLiquidityToken>, + current_liquidity_tokens: u32, + ) -> DispatchResultWithPostInfo { + ensure_root(origin)?; + + let added_liquidity_token: CurrencyIdOf = match paired_or_liquidity_token { + PairedOrLiquidityToken::Paired(x) => + T::ValuateForNative::find_paired_pool_for(x)?.0, + PairedOrLiquidityToken::Liquidity(x) => { + T::ValuateForNative::check_can_valuate_for(x)?; + x + }, + }; + + StakingLiquidityTokens::::try_mutate( + |staking_liquidity_tokens| -> DispatchResult { + ensure!( + current_liquidity_tokens as usize >= staking_liquidity_tokens.len(), + Error::::TooLowCurrentStakingLiquidityTokensCount + ); + ensure!( + staking_liquidity_tokens.insert(added_liquidity_token, None).is_none(), + Error::::StakingLiquidityTokenAlreadyListed + ); + + Ok(()) + }, + )?; + Ok(().into()) + } + + #[pallet::call_index(22)] + #[pallet::weight(::WeightInfo::remove_staking_liquidity_token(*current_liquidity_tokens))] + /// Removes previously added liquidity token + pub fn remove_staking_liquidity_token( + origin: OriginFor, + paired_or_liquidity_token: PairedOrLiquidityToken>, + current_liquidity_tokens: u32, + ) -> DispatchResultWithPostInfo { + ensure_root(origin)?; + + let removed_liquidity_token: CurrencyIdOf = match paired_or_liquidity_token { + PairedOrLiquidityToken::Paired(x) => + T::ValuateForNative::find_paired_pool_for(x)?.0, + PairedOrLiquidityToken::Liquidity(x) => x, + }; + + StakingLiquidityTokens::::try_mutate( + |staking_liquidity_tokens| -> DispatchResult { + ensure!( + current_liquidity_tokens as usize >= staking_liquidity_tokens.len(), + Error::::TooLowCurrentStakingLiquidityTokensCount + ); + ensure!( + staking_liquidity_tokens.remove(&removed_liquidity_token).is_some(), + Error::::StakingLiquidityTokenNotListed + ); + + Ok(()) + }, + )?; + Ok(().into()) + } + + #[pallet::call_index(23)] + #[pallet::weight(T::DbWeight::get().reads_writes(20, 20))] + #[transactional] + /// Modifies aggregator metadata by extending or reducing list of approved candidates + /// Account may only become aggregator only if its not collator or delegator at the moment + pub fn aggregator_update_metadata( + origin: OriginFor, + collator_candidates: Vec, + action: MetadataUpdateAction, + ) -> DispatchResultWithPostInfo { + let aggregator = ensure_signed(origin)?; + + ensure!(!Self::is_candidate(&aggregator), Error::::CandidateExists); + ensure!(!Self::is_delegator(&aggregator), Error::::DelegatorExists); + + AggregatorMetadata::::try_mutate_exists( + aggregator.clone(), + |maybe_aggregator_metadata| -> DispatchResult { + let mut aggregator_metadata = + maybe_aggregator_metadata.take().unwrap_or_default(); + + match action { + MetadataUpdateAction::ExtendApprovedCollators => { + collator_candidates.iter().try_for_each( + |collator| -> DispatchResult { + Self::add_approved_candidate_for_collator_metadata( + collator, + &mut aggregator_metadata, + ) + }, + )?; + }, + MetadataUpdateAction::RemoveApprovedCollators => { + collator_candidates.iter().try_for_each( + |collator| -> DispatchResult { + Self::remove_approved_candidates_from_collator_metadata( + collator, + &aggregator, + &mut aggregator_metadata, + ) + }, + )?; + }, + } + + if !aggregator_metadata.approved_candidates.is_empty() { + *maybe_aggregator_metadata = Some(aggregator_metadata); + } + + Ok(()) + }, + )?; + + Self::deposit_event(Event::AggregatorMetadataUpdated(aggregator)); + Ok(().into()) + } + + #[pallet::call_index(24)] + #[pallet::weight(T::DbWeight::get().reads_writes(20, 20))] + /// Assigns/replaces the candidate that given collator wants to aggregate under + #[transactional] + pub fn update_candidate_aggregator( + origin: OriginFor, + maybe_aggregator: Option, + ) -> DispatchResultWithPostInfo { + let candidate = ensure_signed(origin)?; + + Self::do_update_candidate_aggregator(&candidate, maybe_aggregator.clone())?; + + Ok(().into()) + } + + /// This extrinsic should be used to distribute rewards for collator and assodiated + /// delegators. As round rewards are processed in random order its impossible predict + /// how many delegators (and assodiated transfer extrinsic calls) will be required so + /// worst case scenario (delegators_count = MaxCollatorCandidates) is assumed. + /// + /// params: + /// - collator - account id + /// - limit - number of rewards periods that should be processed within extrinsic. Note + /// that limit assumes worst case scenario of (delegators_count = MaxCollatorCandidates) + /// so as a result, `limit` or more session round rewards may be distributed + #[pallet::call_index(25)] + #[pallet::weight(number_of_sesisons.unwrap_or(T::DefaultPayoutLimit::get()) * ::WeightInfo::payout_collator_rewards())] + #[transactional] + pub fn payout_collator_rewards( + origin: OriginFor, + collator: T::AccountId, + number_of_sesisons: Option, + ) -> DispatchResultWithPostInfo { + let _caller = ensure_signed(origin)?; + Self::do_payout_collator_rewards(collator, number_of_sesisons) + } + + // TODO: use more precise benchmark + #[pallet::call_index(26)] + #[pallet::weight(::WeightInfo::payout_delegator_reward())] + #[transactional] + /// Payout delegator rewards only for particular round. Collators should rather use + /// [`Pallet::payout_collator_rewards`] but if collator is inresponsive one can claim + /// particular delegator rewards manually. + pub fn payout_delegator_reward( + origin: OriginFor, + round: RoundIndex, + collator: T::AccountId, + delegator: T::AccountId, + ) -> DispatchResultWithPostInfo { + let _caller = ensure_signed(origin)?; + + RoundCollatorRewardInfo::::try_mutate( + collator.clone(), + round, + |maybe_collator_payout_info| -> DispatchResult { + let collator_payout_info = maybe_collator_payout_info + .as_mut() + .ok_or(Error::::CollatorRoundRewardsDNE)?; + let delegator_reward = collator_payout_info + .delegator_rewards + .remove(&delegator) + .ok_or(Error::::DelegatorRewardsDNE)?; + Self::payout_reward( + round, + delegator, + delegator_reward, + RewardKind::Delegator(collator), + )?; + Ok(()) + }, + )?; + + Ok(().into()) + } + } + + impl Pallet { + fn add_approved_candidate_for_collator_metadata( + collator_candidate: &T::AccountId, + aggregator_metadata: &mut AggregatorMetadataType>, + ) -> DispatchResult { + ensure!(Self::is_candidate(&collator_candidate), Error::::CandidateDNE); + ensure!( + aggregator_metadata.approved_candidates.insert(collator_candidate.clone()), + Error::::CandidateAlreadyApprovedByAggregator + ); + Ok(()) + } + + fn remove_approved_candidates_from_collator_metadata( + collator_candidate: &T::AccountId, + aggregator: &T::AccountId, + aggregator_metadata: &mut AggregatorMetadataType>, + ) -> DispatchResult { + // Do not propagate the error if there's an error here + // Then it means that the aggregator wasn't aggregating for this candidate + // Or that the target is no longer a candidate, which can happen if the candidate has left + // removing all his aggregation details except for approvals from various aggregators + let _ = CandidateAggregator::::try_mutate( + |candidate_aggregator_map| -> DispatchResult { + let collator_candidate_state = >::get(&collator_candidate) + .ok_or(Error::::CandidateDNE)?; + ensure!( + Some(aggregator.clone()) == + candidate_aggregator_map.remove(collator_candidate), + Error::::CandidateNotAggregatingUnderAggregator + ); + + // If CandidateAggregator has the aggregator listed under this candidate then + // the aggregator metadata will have this candidate listed under its liquidity token + let res = aggregator_metadata + .token_collator_map + .remove(&collator_candidate_state.liquidity_token); + if res != Some(collator_candidate.clone()) { + log::error!( + "Inconsistent aggregator metadata: candidate - {:?}, aggregator - {:?}", + collator_candidate, + aggregator + ); + } + + Ok(()) + }, + ); + + ensure!( + aggregator_metadata.approved_candidates.remove(collator_candidate), + Error::::CandidateNotApprovedByAggregator + ); + + Ok(()) + } + + pub fn payout_reward( + round: RoundIndex, + to: T::AccountId, + amt: BalanceOf, + kind: RewardKind, + ) -> DispatchResult { + let _ = ::Currency::transfer( + T::NativeTokenId::get().into(), + &::StakingIssuanceVault::get(), + &to, + amt, + ExistenceRequirement::AllowDeath, + )?; + match kind { + RewardKind::Collator => + Self::deposit_event(Event::Rewarded(round, to.clone(), amt)), + RewardKind::Delegator(collator) => Self::deposit_event(Event::DelegatorDueReward( + round, + collator.clone(), + to.clone(), + amt, + )), + }; + Ok(()) + } + pub fn is_delegator(acc: &T::AccountId) -> bool { + >::get(acc).is_some() + } + pub fn is_candidate(acc: &T::AccountId) -> bool { + >::get(acc).is_some() + } + pub fn is_aggregator(acc: &T::AccountId) -> bool { + >::get(acc).is_some() + } + pub fn is_selected_candidate(acc: &T::AccountId) -> bool { + >::get().binary_search(acc).is_ok() + } + + fn remove_aggregator_for_collator(candidate: &T::AccountId) -> DispatchResult { + CandidateAggregator::::try_mutate(|candidate_aggregator_info| -> DispatchResult { + let detached_aggregator = candidate_aggregator_info + .remove(&candidate) + .ok_or(Error::::CandidateNotAggregating)?; + + AggregatorMetadata::::try_mutate( + detached_aggregator.clone(), + |maybe_aggregator_metadata| -> DispatchResult { + let aggregator_metadata = + maybe_aggregator_metadata.as_mut().ok_or(Error::::AggregatorDNE)?; + let candidate_state = + >::get(&candidate).ok_or(Error::::CandidateDNE)?; + let res = aggregator_metadata + .token_collator_map + .remove(&candidate_state.liquidity_token); + if res != Some(candidate.clone()) { + log::error!( + "Inconsistent aggregator metadata: candidate - {:?}, aggregator - {:?}", + candidate, + detached_aggregator + ); + } + + Ok(()) + }, + )?; + + Ok(()) + })?; + Ok(()) + } + + fn corelate_collator_with_aggregator( + candidate: &T::AccountId, + new_aggregator: T::AccountId, + ) -> DispatchResult { + AggregatorMetadata::::try_mutate( + new_aggregator, + |maybe_aggregator_metadata| -> DispatchResult { + let aggregator_metadata = + maybe_aggregator_metadata.as_mut().ok_or(Error::::AggregatorDNE)?; + ensure!( + aggregator_metadata.approved_candidates.contains(candidate), + Error::::CandidateNotApprovedByAggregator + ); + let candidate_state = + >::get(candidate).ok_or(Error::::CandidateDNE)?; + ensure!( + aggregator_metadata + .token_collator_map + .insert(candidate_state.liquidity_token, candidate.clone()) + .is_none(), + Error::::AggregatorLiquidityTokenTaken + ); + + Ok(()) + }, + )?; + Ok(()) + } + + fn replace_aggregator_for_collator( + candidate: &T::AccountId, + new_aggregator: T::AccountId, + prev_aggregator: T::AccountId, + ) -> DispatchResult { + ensure!( + prev_aggregator != new_aggregator, + Error::::TargettedAggregatorSameAsCurrent + ); + + AggregatorMetadata::::try_mutate( + prev_aggregator.clone(), + |maybe_prev_aggregator_metadata| -> DispatchResult { + let prev_aggregator_metadata = + maybe_prev_aggregator_metadata.as_mut().ok_or(Error::::AggregatorDNE)?; + let candidate_state = + >::get(candidate).ok_or(Error::::CandidateDNE)?; + let res = prev_aggregator_metadata + .token_collator_map + .remove(&candidate_state.liquidity_token); + if res != Some(candidate.clone()) { + log::error!( + "Inconsistent aggregator metadata: candidate - {:?}, aggregator - {:?}", + candidate, + prev_aggregator + ); + } + + Self::corelate_collator_with_aggregator(candidate, new_aggregator)?; + Ok(()) + }, + )?; + + Ok(()) + } + + fn assign_aggregator_for_collator( + candidate: &T::AccountId, + new_aggregator: T::AccountId, + ) -> DispatchResult { + CandidateAggregator::::try_mutate(|candidate_aggregator_info| -> DispatchResult { + match candidate_aggregator_info.insert(candidate.clone(), new_aggregator.clone()) { + Some(prev_aggregator) => { + Self::replace_aggregator_for_collator( + candidate, + new_aggregator, + prev_aggregator, + )?; + }, + None => { + Self::corelate_collator_with_aggregator(candidate, new_aggregator)?; + }, + } + Ok(()) + })?; + Ok(()) + } + + pub fn do_update_candidate_aggregator( + candidate: &T::AccountId, + maybe_aggregator: Option, + ) -> DispatchResult { + ensure!(Self::is_candidate(candidate), Error::::CandidateDNE); + + if let Some(ref new_aggregator) = maybe_aggregator { + Self::assign_aggregator_for_collator(candidate, new_aggregator.clone())?; + } else { + Self::remove_aggregator_for_collator(candidate)?; + } + + Self::deposit_event(Event::CandidateAggregatorUpdated( + candidate.clone(), + maybe_aggregator, + )); + Ok(()) + } + /// Caller must ensure candidate is active before calling + fn update_active( + candidate: T::AccountId, + total: BalanceOf, + candidate_liquidity_token: CurrencyIdOf, + ) { + let mut candidates = >::get(); + candidates.remove(&Bond::from_owner(candidate.clone())); + candidates.insert(Bond { + owner: candidate, + amount: total, + liquidity_token: candidate_liquidity_token, + }); + >::put(candidates); + } + fn delegator_leaves_collator( + delegator: T::AccountId, + collator: T::AccountId, + ) -> DispatchResult { + let mut state = >::get(&collator).ok_or(Error::::CandidateDNE)?; + let (total_changed, delegator_stake) = state.rm_delegator::(delegator.clone())?; + let debug_amount = ::StakingReservesProvider::unbond( + state.liquidity_token.into(), + &delegator, + delegator_stake, + ); + if !debug_amount.is_zero() { + log::warn!("Unbond in staking returned non-zero value {:?}", debug_amount); + } + if state.is_active() && total_changed { + Self::update_active(collator.clone(), state.total_counted, state.liquidity_token); + } + let new_total_locked = + >::get(state.liquidity_token).saturating_sub(delegator_stake); + >::insert(state.liquidity_token, new_total_locked); + let new_total = state.total_counted; + >::insert(&collator, state); + Self::deposit_event(Event::DelegatorLeftCandidate( + delegator, + collator, + delegator_stake, + new_total, + )); + Ok(()) + } + + fn process_collator_with_rewards( + round_to_payout: u32, + collator: T::AccountId, + reward: BalanceOf, + ) { + let state = >::take(round_to_payout, &collator); + let collator_commission_perbill = >::get(); + let mut collator_payout_info = + RoundCollatorRewardInfoType::>::default(); + if state.delegations.is_empty() { + // solo collator with no delegators + collator_payout_info.collator_reward = reward; + RoundCollatorRewardInfo::::insert( + collator, + round_to_payout, + collator_payout_info, + ); + } else { + let collator_commission = collator_commission_perbill.mul_floor(reward); + let reward_less_commission = reward.saturating_sub(collator_commission); + + let collator_perbill = Perbill::from_rational(state.bond, state.total); + let collator_reward_less_commission = + collator_perbill.mul_floor(reward_less_commission); + + collator_payout_info.collator_reward = + collator_reward_less_commission.saturating_add(collator_commission); + + match state + .delegations + .iter() + .cloned() + .try_fold(state.bond, |acc, x| acc.checked_add(&x.amount)) + { + Some(total) if total <= state.total => { + state.delegations.iter().for_each(|delegator_bond| { + collator_payout_info.delegator_rewards.insert( + delegator_bond.owner.clone(), + multiply_by_rational_with_rounding( + reward_less_commission.into(), + delegator_bond.amount.into(), + state.total.into(), + Rounding::Down, + ) + .map(SaturatedConversion::saturated_into) + .unwrap_or(BalanceOf::::zero()), + ); + }); + }, + _ => { + // unexpected overflow has occured and rewards will now distributed evenly amongst + let delegator_count = state.delegations.len() as u32; + let delegator_reward = reward + .saturating_sub(collator_payout_info.collator_reward) + .checked_div(&delegator_count.into()) + .unwrap_or(Zero::zero()); + state.delegations.iter().for_each(|delegator_bond| { + collator_payout_info + .delegator_rewards + .insert(delegator_bond.owner.clone(), delegator_reward); + }); + }, + } + + RoundCollatorRewardInfo::::insert( + collator, + round_to_payout, + collator_payout_info, + ); + } + } + + fn process_aggregator_with_rewards_and_dist( + round_to_payout: u32, + _aggregator: T::AccountId, + author_rewards: BalanceOf, + distribution: &BTreeMap>, + ) { + match distribution + .values() + .cloned() + .try_fold(BalanceOf::::zero(), |acc, x| acc.checked_add(&x)) + { + Some(aggregator_total_valuation) => { + distribution.iter().for_each(|(collator, contribution)| { + Self::process_collator_with_rewards( + round_to_payout, + collator.clone(), + multiply_by_rational_with_rounding( + author_rewards.into(), + contribution.clone().into(), + aggregator_total_valuation.into(), + Rounding::Down, + ) + .map(SaturatedConversion::saturated_into) + .unwrap_or(BalanceOf::::zero()), + ) + }); + }, + None => { + // unexpected overflow has occured and rewards will now distributed evenly amongst + let collator_count = distribution.keys().cloned().count() as u32; + let collator_reward = author_rewards + .checked_div(&collator_count.into()) + .unwrap_or(BalanceOf::::zero()); + distribution.keys().for_each(|collator| { + Self::process_collator_with_rewards( + round_to_payout, + collator.clone(), + collator_reward, + ) + }); + }, + } + } + + fn pay_stakers(now: RoundIndex) { + // payout is now - duration rounds ago => now - duration > 0 else return early + let duration = T::RewardPaymentDelay::get(); + if now < duration { + return + } + let round_to_payout = now.saturating_sub(duration); + let total = >::take(round_to_payout); + if total.is_zero() { + return + } + let total_issuance = + T::Issuance::get_staking_issuance(round_to_payout).unwrap_or(Zero::zero()); + + // unwrap_or_default here is to ensure backward compatibility during the upgrade + let round_aggregator_info = + RoundAggregatorInfo::::take(round_to_payout).unwrap_or_default(); + + for (author, pts) in >::drain_prefix(round_to_payout) { + let author_issuance_perbill = Perbill::from_rational(pts, total); + + let author_rewards = author_issuance_perbill.mul_floor(total_issuance); + + match round_aggregator_info.get(&author) { + Some(aggregator_distribution) => + Self::process_aggregator_with_rewards_and_dist( + round_to_payout, + author, + author_rewards, + aggregator_distribution, + ), + None => + Self::process_collator_with_rewards(round_to_payout, author, author_rewards), + } + } + } + + pub fn calculate_collators_valuations<'a, I>( + valuated_bond_it: I, + ) -> BTreeMap> + where + I: Iterator< + Item = (&'a Bond, CurrencyIdOf>, BalanceOf), + >, + I: Clone, + { + let aggregator_info = >::get(); + // collect aggregated bonds + let mut valuated_bonds = valuated_bond_it + .clone() + .filter_map(|(bond, valuation)| { + aggregator_info.get(&bond.owner).map(|aggregator| (bond, valuation, aggregator)) + }) + .fold( + BTreeMap::>::new(), + |mut acc, (_bond, valuation, aggregator)| { + acc.entry(aggregator.clone()) + .and_modify(|total| *total = total.saturating_add(valuation)) + .or_insert_with(|| valuation); + acc + }, + ); + + // extend with non agregated bonds + valuated_bonds.extend(valuated_bond_it.filter_map(|(bond, valuation)| { + if let None = aggregator_info.get(&bond.owner) { + Some((bond.owner.clone(), valuation)) + } else { + None + } + })); + + valuated_bonds + } + + pub fn calculate_aggregators_collator_info<'a, I>( + valuated_bond_it: I, + ) -> BTreeMap>> + where + I: Iterator< + Item = (&'a Bond, CurrencyIdOf>, BalanceOf), + >, + { + let aggregator_info = >::get(); + + valuated_bond_it + .filter_map(|(bond, valuation)| { + aggregator_info.get(&bond.owner).map(|aggregator| (bond, valuation, aggregator)) + }) + .fold( + BTreeMap::>>::new(), + |mut acc, (bond, valuation, aggregator)| { + acc.entry(aggregator.clone()) + .and_modify(|x| { + x.insert(bond.owner.clone(), valuation); + }) + .or_insert(BTreeMap::from([(bond.owner.clone(), valuation)])); + acc + }, + ) + } + + pub fn calculate_valuations_and_aggregation_info() -> ( + BTreeMap>, + BTreeMap>>, + ) { + let candidates = >::get().0; + + let liq_token_to_pool = >::get(); + let valuated_bond_it = candidates.iter().filter_map(|bond| { + if bond.liquidity_token == T::NativeTokenId::get() { + Some((bond, bond.amount.checked_div(&2_u32.into()).unwrap_or_default())) + } else { + match liq_token_to_pool.get(&bond.liquidity_token) { + Some(Some((reserve1, reserve2))) if !reserve1.is_zero() => + multiply_by_rational_with_rounding( + bond.amount.into(), + (*reserve1).into(), + (*reserve2).into(), + Rounding::Down, + ) + .map(SaturatedConversion::saturated_into) + .map(|val| (bond, val)) + .or(Some((bond, BalanceOf::::max_value()))), + _ => None, + } + } + }); + + ( + Self::calculate_collators_valuations(valuated_bond_it.clone()), + Self::calculate_aggregators_collator_info(valuated_bond_it.clone()), + ) + } + // + /// Compute the top `TotalSelected` candidates in the CandidatePool and return + /// a vec of their AccountIds (in the order of selection) + pub fn compute_top_candidates( + now: RoundIndex, + ) -> ( + Vec<(T::AccountId, BalanceOf)>, + Vec<(T::AccountId, BalanceOf)>, + BTreeMap>>, + ) { + let (valuated_author_candidates_btreemap, aggregators_collator_info) = + Self::calculate_valuations_and_aggregation_info(); + + let mut valuated_author_candidates_vec: Vec<(T::AccountId, BalanceOf)> = + valuated_author_candidates_btreemap.into_iter().collect::<_>(); + + let mut filtered_authors: Vec<_> = valuated_author_candidates_vec + .into_iter() + .filter(|x| x.1 >= T::MinCollatorStk::get()) + .collect::<_>(); + + // order candidates by stake (least to greatest so requires `rev()`) + filtered_authors.sort_by(|a, b| a.1.partial_cmp(&b.1).unwrap()); + let top_n = >::get() as usize; + // choose the top TotalSelected qualified candidates, ordered by stake + let mut selected_authors: Vec<(T::AccountId, BalanceOf)> = + filtered_authors.into_iter().rev().take(top_n).collect::<_>(); + selected_authors.sort_by(|a, b| a.0.partial_cmp(&b.0).unwrap()); + + let mut all_selected_collators = Vec::<(T::AccountId, BalanceOf)>::new(); + for selected_author in selected_authors.iter() { + if let Some(aggregator_collator_info) = + aggregators_collator_info.get(&selected_author.0) + { + all_selected_collators.extend_from_slice( + &{ + aggregator_collator_info + .into_iter() + .map(|(a, b)| (a.clone(), b.clone())) + .collect::)>>() + }[..], + ); + } else { + all_selected_collators.push(selected_author.clone()); + } + } + + (selected_authors, all_selected_collators, aggregators_collator_info) + } + + pub fn staking_liquidity_tokens_snapshot() { + let mut staking_liquidity_tokens = >::get(); + + for (token, valuation) in staking_liquidity_tokens.iter_mut() { + *valuation = T::ValuateForNative::get_reserve_and_lp_supply_for(*token); + } + + >::put(staking_liquidity_tokens); + } + + #[aquamarine::aquamarine] + /// Best as in most cumulatively supported in terms of stake + /// Returns [collator_count, delegation_count, total staked] + /// ```mermaid + /// flowchart + /// A[Start] --> B{for all candidates} + /// B -- Is aggregating under Aggregator? --> C[increase Aggreagator valuation] + /// B -- Is solo collator? --> D[increase collator valuation] + /// C --> E[collect final valuations of solo collators and aggregators] + /// D --> E + /// E -- list of solo collators and aggregators only--> F[pick top N valuated accounts] + /// F --> G{for every block author} + /// G -- author --> Z[persist into SelectedCandidates runtime storage] + /// G -- author --> Y{Is solo collator or Aggregator} + /// Y -- is solo collator --> I[emit CollatorChosen event] + /// Y -- is aggregator --> H{for every associated collator} + /// H --> I + /// ``` + pub fn select_top_candidates(now: RoundIndex) -> (u32, u32, BalanceOf) { + let (mut collator_count, mut delegation_count, mut total_relevant_exposure) = + (0u32, 0u32, BalanceOf::::zero()); + Self::staking_liquidity_tokens_snapshot(); + // choose the top TotalSelected qualified candidates, ordered by stake + let (selected_authors, all_selected_collators, aggregators_collator_info) = + Self::compute_top_candidates(now); + + RoundAggregatorInfo::::insert(now, aggregators_collator_info); + + // snapshot exposure for round for weighting reward distribution + for collator in all_selected_collators.iter() { + let state = >::get(&collator.0) + .expect("all members of CandidateQ must be candidates"); + collator_count = collator_count.saturating_add(1u32); + delegation_count = delegation_count.saturating_add(state.delegators.0.len() as u32); + let amount = collator.1; + total_relevant_exposure = total_relevant_exposure.saturating_add(amount); + let collator_snaphot: CollatorSnapshot< + T::AccountId, + BalanceOf, + CurrencyIdOf, + > = state.into(); + >::insert(now, collator.0.clone(), collator_snaphot); + Self::deposit_event(Event::CollatorChosen(now, collator.0.clone(), amount)); + } + + // insert canonical collator set + >::put( + selected_authors.iter().cloned().map(|x| x.0).collect::>(), + ); + (collator_count, delegation_count, total_relevant_exposure) + } + + fn valuate_bond(liquidity_token: CurrencyIdOf, bond: BalanceOf) -> BalanceOf { + if liquidity_token == T::NativeTokenId::get() { + bond.checked_div(&2_u32.into()).unwrap_or_default() + } else { + T::ValuateForNative::get_valuation_for_paired_for(liquidity_token, bond) + } + } + + fn do_payout_collator_rewards( + collator: T::AccountId, + number_of_sesisons: Option, + ) -> DispatchResultWithPostInfo { + let mut rounds = Vec::::new(); + + let limit = number_of_sesisons.unwrap_or(T::DefaultPayoutLimit::get()); + let mut payouts_left = limit * (T::MaxDelegationsPerDelegator::get() + 1); + + for (id, (round, info)) in + RoundCollatorRewardInfo::::iter_prefix(collator.clone()).enumerate() + { + if payouts_left < (info.delegator_rewards.len() as u32 + 1u32) { + break + } + + Self::payout_reward( + round, + collator.clone(), + info.collator_reward, + RewardKind::Collator, + )?; + payouts_left -= 1u32; + + let _ = info.delegator_rewards.iter().try_for_each(|(d, r)| { + Self::payout_reward( + round, + d.clone(), + r.clone(), + RewardKind::Delegator(collator.clone()), + ) + })?; + RoundCollatorRewardInfo::::remove(collator.clone(), round); + rounds.push(round); + + payouts_left = payouts_left + .checked_sub(info.delegator_rewards.len() as u32) + .unwrap_or_default(); + + if (id as u32).checked_add(1u32).unwrap_or(u32::MAX) > limit { + // We can optimize number of rounds that can be processed as extrinsic weight + // was benchmarked assuming that collator have delegators count == T::MaxDelegatorsPerCandidate + // so if there are less or no delegators, we can use remaining weight for + // processing following block, the only extra const is single iteration that + // consumes 1 storage read. We can compensate that by sacrificing single transfer tx + // + // esitmated weight or payout_collator_rewards extrinsic with limit parm set to 1 is + // 1 storage read + (MaxDelegatorsPerCollator + 1) * transfer weight + // + // so if collator does not have any delegators only 1 transfer was actually + // executed leaving MaxDelegatorsPerCollator spare. We can use that remaining + // weight to payout rewards for following rounds. Each extra round requires: + // - 1 storage read (iteration) + // - N <= MaxDelegatorsPerCandidate transfers (depending on collators count) + // + // we can compansate storage read with 1 transfer and try to process following + // round if there are enought transfers left + payouts_left = payouts_left.checked_sub(1).unwrap_or_default(); + } + } + + ensure!(!rounds.is_empty(), Error::::CollatorRoundRewardsDNE); + + if let Some(_) = RoundCollatorRewardInfo::::iter_prefix(collator.clone()).next() { + Self::deposit_event(Event::CollatorRewardsDistributed( + collator, + PayoutRounds::Partial(rounds), + )); + } else { + Self::deposit_event(Event::CollatorRewardsDistributed(collator, PayoutRounds::All)); + } + + // possibly use PostDispatchInfo.actual_weight to return refund caller if delegators + // count was lower than assumed upper bound + Ok(().into()) + } + } + + /// Add reward points to block authors: + /// * 20 points to the block producer for producing a block in the chain + impl pallet_authorship::EventHandler> for Pallet { + fn note_author(author: T::AccountId) { + let now = >::get().current; + let score_plus_20 = >::get(now, &author).saturating_add(20); + >::insert(now, author, score_plus_20); + >::mutate(now, |x| *x = x.saturating_add(20)); + } + } + + impl pallet_session::SessionManager for Pallet { + fn new_session(_: SessionIndex) -> Option> { + let selected_canidates = Self::selected_candidates(); + if !selected_canidates.is_empty() { + Some(selected_canidates) + } else { + let fallback_canidates = T::FallbackProvider::get_members(); + if !fallback_canidates.is_empty() { + Some(fallback_canidates) + } else { + None + } + } + } + fn start_session(session_index: SessionIndex) { + if !session_index.is_zero() { + let n = >::block_number().saturating_add(One::one()); + let mut round = >::get(); + // println!("ROUND FINISHED {}", round.current.saturated_into::()); + // mutate round + round.update(n); + // pay all stakers for T::RewardPaymentDelay rounds ago + Self::pay_stakers(round.current); + T::SequencerStakingRewards::pay_sequencers(round.current); + // select top collator candidates for next round + let (collator_count, _delegation_count, total_relevant_exposure) = + Self::select_top_candidates(round.current.saturating_add(One::one())); + // Calculate the issuance for next round + // No issuance must happen after this point + T::Issuance::compute_issuance(round.current); + // start next round + >::put(round); + // Emit new round event + Self::deposit_event(Event::NewRound( + round.first, + round.current, + collator_count, + total_relevant_exposure, + )); + } + } + fn end_session(_: SessionIndex) { + // ignore + } + } + + impl pallet_session::ShouldEndSession> for Pallet { + fn should_end_session(now: BlockNumberFor) -> bool { + let round = >::get(); + round.should_update(now) + } + } + + impl EstimateNextSessionRotation> for Pallet { + fn average_session_length() -> BlockNumberFor { + >::get().length.into() + } + + fn estimate_current_session_progress(now: BlockNumberFor) -> (Option, Weight) { + let round = >::get(); + let passed_blocks = now.saturating_sub(round.first).saturating_add(One::one()); + + ( + Some(Permill::from_rational(passed_blocks, round.length.into())), + // One read for the round info, blocknumber is read free + T::DbWeight::get().reads(1), + ) + } + + fn estimate_next_session_rotation( + _now: BlockNumberFor, + ) -> (Option>, Weight) { + let round = >::get(); + + ( + Some(round.first.saturating_add(round.length.saturating_sub(One::one()).into())), + // One read for the round info, blocknumber is read free + T::DbWeight::get().reads(1), + ) + } + } +} diff --git a/gasp-node/pallets/parachain-staking/src/mock.rs b/gasp-node/pallets/parachain-staking/src/mock.rs new file mode 100644 index 000000000..6a85cb57f --- /dev/null +++ b/gasp-node/pallets/parachain-staking/src/mock.rs @@ -0,0 +1,641 @@ +// Copyright 2019-2021 PureStake Inc. +// This file is part of Moonbeam. + +// Moonbeam 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. + +// Moonbeam 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 Moonbeam. If not, see . + +//! Test utilities +use crate as stake; +use crate::{ + pallet, AwardedPts, BondKind, ComputeIssuance, Config, DispatchError, GetIssuance, Points, + RoundCollatorRewardInfo, StakingReservesProviderTrait, +}; +use codec::{Decode, Encode}; +use frame_support::{ + assert_ok, construct_runtime, derive_impl, parameter_types, + traits::{ + Contains, MultiTokenCurrency, MultiTokenVestingSchedule, Nothing, OnFinalize, OnInitialize, + }, + PalletId, +}; +use mangata_support::pools::{PoolInfo, Valuate, ValuateFor}; +use orml_tokens::{MultiTokenCurrencyExtended, MultiTokenReservableCurrency}; +use orml_traits::parameter_type_with_key; +use scale_info::TypeInfo; +use sp_io; +use sp_runtime::{ + traits::{AccountIdConversion, Zero}, + BuildStorage, DispatchResult, Perbill, Percent, RuntimeDebug, +}; +use sp_std::{ + convert::{TryFrom, TryInto}, + marker::PhantomData, +}; + +pub(crate) type AccountId = u64; +pub(crate) type Amount = i128; +pub(crate) type Balance = u128; +pub(crate) type TokenId = u32; + +pub const MGA_TOKEN_ID: TokenId = 0; + +type Block = frame_system::mocking::MockBlock; + +// Configure a mock runtime to test the pallet. +construct_runtime!( + pub enum Test { + System: frame_system, + Tokens: orml_tokens, + Stake: stake, + } +); + +#[derive_impl(frame_system::config_preludes::TestDefaultConfig as frame_system::DefaultConfig)] +impl frame_system::Config for Test { + type Block = Block; +} + +parameter_types! { + pub const HistoryLimit: u32 = 10u32; + + pub const LiquidityMiningIssuanceVaultId: PalletId = PalletId(*b"py/lqmiv"); + pub LiquidityMiningIssuanceVault: AccountId = LiquidityMiningIssuanceVaultId::get().into_account_truncating(); + pub const StakingIssuanceVaultId: PalletId = PalletId(*b"py/stkiv"); + pub StakingIssuanceVault: AccountId = StakingIssuanceVaultId::get().into_account_truncating(); + + pub const TotalCrowdloanAllocation: Balance = 200_000_000; + pub const LinearIssuanceAmount: Balance = 4_000_000_000; + pub const LinearIssuanceBlocks: u32 = 13_140_000u32; // 5 years + pub const LiquidityMiningSplit: Perbill = Perbill::from_parts(555555556); + pub const StakingSplit: Perbill = Perbill::from_parts(444444444); + pub const ImmediateTGEReleasePercent: Percent = Percent::from_percent(20); + pub const TGEReleasePeriod: u32 = 5_256_000u32; // 2 years + pub const TGEReleaseBegin: u32 = 100_800u32; // Two weeks into chain start + + pub const TargetTge:u128 = 2_000_000_000u128; + +} + +pub struct MockIssuance; +impl ComputeIssuance for MockIssuance { + fn initialize() {} + fn compute_issuance(_n: u32) { + let staking_issuance = Self::get_staking_issuance(_n).unwrap(); + + let _ = StakeCurrency::mint( + MGA_TOKEN_ID.into(), + &StakingIssuanceVault::get(), + staking_issuance.into(), + ); + } +} + +impl GetIssuance for MockIssuance { + fn get_all_issuance(_n: u32) -> Option<(Balance, Balance, Balance)> { + unimplemented!() + } + fn get_liquidity_mining_issuance(_n: u32) -> Option { + unimplemented!() + } + fn get_staking_issuance(_n: u32) -> Option { + let to_be_issued: Balance = + LinearIssuanceAmount::get() - TargetTge::get() - TotalCrowdloanAllocation::get(); + let linear_issuance_sessions: u32 = LinearIssuanceBlocks::get() / BlocksPerRound::get(); + let linear_issuance_per_session = to_be_issued / linear_issuance_sessions as Balance; + let staking_issuance = StakingSplit::get() * linear_issuance_per_session; + Some(staking_issuance) + } + fn get_sequencer_issuance(_n: u32) -> Option { + unimplemented!() + } +} + +pub struct TestVestingModule, B>( + PhantomData, + PhantomData, + PhantomData, +); +impl, B> MultiTokenVestingSchedule for TestVestingModule { + type Currency = C; + type Moment = B; + + fn vesting_balance( + _who: &A, + _token_id: >::CurrencyId, + ) -> Option<>::Balance> { + None + } + + fn add_vesting_schedule( + _who: &A, + _locked: >::Balance, + _per_block: >::Balance, + _starting_block: B, + _token_id: >::CurrencyId, + ) -> DispatchResult { + Ok(()) + } + + // Ensure we can call `add_vesting_schedule` without error. This should always + // be called prior to `add_vesting_schedule`. + fn can_add_vesting_schedule( + _who: &A, + _locked: >::Balance, + _per_block: >::Balance, + _starting_block: B, + _token_id: >::CurrencyId, + ) -> DispatchResult { + Ok(()) + } + + /// Remove a vesting schedule for a given account. + fn remove_vesting_schedule( + _who: &A, + _token_id: >::CurrencyId, + _schedule_index: u32, + ) -> DispatchResult { + Ok(()) + } +} + +parameter_type_with_key! { + pub ExistentialDeposits: |currency_id: TokenId| -> Balance { + match currency_id { + &MGA_TOKEN_ID => 0, + _ => 0, + } + }; +} + +parameter_types! { + pub const TreasuryPalletId: PalletId = PalletId(*b"py/trsry"); + pub const BnbTreasurySubAccDerive: [u8; 4] = *b"bnbt"; + pub TreasuryAccount: AccountId = TreasuryPalletId::get().into_account_truncating(); + pub const MaxLocks: u32 = 50; + pub const MgaTokenId: TokenId = MGA_TOKEN_ID; +} + +pub struct DustRemovalWhitelist; +impl Contains for DustRemovalWhitelist { + fn contains(a: &AccountId) -> bool { + *a == TreasuryAccount::get() + } +} + +impl orml_tokens::Config for Test { + type RuntimeEvent = RuntimeEvent; + type Balance = Balance; + type Amount = Amount; + type CurrencyId = TokenId; + type WeightInfo = (); + type ExistentialDeposits = ExistentialDeposits; + type MaxLocks = MaxLocks; + type DustRemovalWhitelist = DustRemovalWhitelist; + type MaxReserves = (); + type ReserveIdentifier = [u8; 8]; + type CurrencyHooks = (); + type NontransferableTokens = Nothing; + type NontransferableTokensAllowList = Nothing; +} + +pub struct TokensStakingPassthrough(PhantomData); +impl StakingReservesProviderTrait + for TokensStakingPassthrough +where + T::Currency: MultiTokenReservableCurrency, +{ + fn can_bond( + token_id: TokenId, + account_id: &AccountId, + amount: Balance, + _use_balance_from: Option, + ) -> bool { + T::Currency::can_reserve(token_id, &account_id, amount) + } + + fn bond( + token_id: TokenId, + account_id: &AccountId, + amount: Balance, + _use_balance_from: Option, + ) -> DispatchResult { + T::Currency::reserve(token_id, account_id, amount) + } + + fn unbond(token_id: TokenId, account_id: &AccountId, amount: Balance) -> Balance { + T::Currency::unreserve(token_id, account_id, amount) + } +} + +impl crate::StakingBenchmarkConfig for Test {} + +parameter_types! { + pub const BlocksPerRound: u32 = 5; + pub const LeaveCandidatesDelay: u32 = 2; + pub const CandidateBondDelay: u32 = 2; + pub const LeaveDelegatorsDelay: u32 = 2; + pub const RevokeDelegationDelay: u32 = 2; + pub const DelegationBondDelay: u32 = 2; + pub const RewardPaymentDelay: u32 = 2; + pub const MinSelectedCandidates: u32 = 5; + pub const MaxCollatorCandidates: u32 = 10; + pub const MaxDelegatorsPerCandidate: u32 = 4; + pub const DefaultPayoutLimit: u32 = 15; + pub const MaxTotalDelegatorsPerCandidate: u32 = 10; + pub const MaxDelegationsPerDelegator: u32 = 4; + pub const DefaultCollatorCommission: Perbill = Perbill::from_percent(20); + pub const MinCollatorStk: u128 = 10; + pub const MinDelegation: u128 = 3; +} + +impl Config for Test { + type RuntimeEvent = RuntimeEvent; + type StakingReservesProvider = TokensStakingPassthrough; + type Currency = orml_tokens::MultiTokenCurrencyAdapter; + type MonetaryGovernanceOrigin = frame_system::EnsureRoot; + type BlocksPerRound = BlocksPerRound; + type LeaveCandidatesDelay = LeaveCandidatesDelay; + type CandidateBondDelay = CandidateBondDelay; + type LeaveDelegatorsDelay = LeaveDelegatorsDelay; + type RevokeDelegationDelay = RevokeDelegationDelay; + type DelegationBondDelay = DelegationBondDelay; + type RewardPaymentDelay = RewardPaymentDelay; + type MinSelectedCandidates = MinSelectedCandidates; + type MaxCollatorCandidates = MaxCollatorCandidates; + type MaxTotalDelegatorsPerCandidate = MaxTotalDelegatorsPerCandidate; + type MaxDelegatorsPerCandidate = MaxDelegatorsPerCandidate; + type DefaultPayoutLimit = DefaultPayoutLimit; + type MaxDelegationsPerDelegator = MaxDelegationsPerDelegator; + type DefaultCollatorCommission = DefaultCollatorCommission; + type MinCollatorStk = MinCollatorStk; + type MinCandidateStk = MinCollatorStk; + type MinDelegation = MinDelegation; + type NativeTokenId = MgaTokenId; + type ValuateForNative = TestTokenValuator; + type Issuance = MockIssuance; + type StakingIssuanceVault = StakingIssuanceVault; + type FallbackProvider = (); + type WeightInfo = (); + type SequencerStakingRewards = (); +} + +#[derive(Default, Clone, Encode, Decode, RuntimeDebug, TypeInfo)] +pub struct TestTokenValuator {} +impl ValuateFor for TestTokenValuator {} +impl Valuate for TestTokenValuator { + type Balance = Balance; + type CurrencyId = TokenId; + + fn find_paired_pool( + base_id: Self::CurrencyId, + asset_id: Self::CurrencyId, + ) -> Result, DispatchError> { + Ok((asset_id / 100, (base_id, asset_id), (0, 0))) + } + + fn check_can_valuate(_: Self::CurrencyId, _: Self::CurrencyId) -> Result<(), DispatchError> { + Ok(()) + } + + fn check_pool_exist(pool_id: Self::CurrencyId) -> Result<(), DispatchError> { + Ok(()) + } + + fn get_reserve_and_lp_supply( + _: Self::CurrencyId, + pool_id: Self::CurrencyId, + ) -> Option<(Self::Balance, Self::Balance)> { + match pool_id { + 1 => Some((1, 1)), + 2 => Some((2, 1)), + 3 => Some((5, 1)), + 4 => Some((1, 1)), + 5 => Some((1, 2)), + 6 => Some((1, 5)), + _ => None, + } + } + + fn get_valuation_for_paired( + _: Self::CurrencyId, + _: Self::CurrencyId, + amount: Self::Balance, + ) -> Self::Balance { + amount + } + + fn find_valuation( + _: Self::CurrencyId, + _: Self::CurrencyId, + _: Self::Balance, + ) -> Result { + unimplemented!("Not required in tests!") + } +} + +pub(crate) struct ExtBuilder { + // tokens used for staking, these aren't backed in the xyk pallet and are just simply nominal tokens + staking_tokens: Vec<(AccountId, Balance, TokenId)>, + // [collator, amount] + collators: Vec<(AccountId, Balance, TokenId)>, + // [delegator, collator, delegation_amount] + delegations: Vec<(AccountId, AccountId, Balance)>, +} + +impl Default for ExtBuilder { + fn default() -> ExtBuilder { + ExtBuilder { staking_tokens: vec![], delegations: vec![], collators: vec![] } + } +} + +impl ExtBuilder { + pub(crate) fn with_staking_tokens( + mut self, + staking_tokens: Vec<(AccountId, Balance, TokenId)>, + ) -> Self { + self.staking_tokens = staking_tokens; + self + } + + pub(crate) fn with_default_staking_token( + mut self, + staking_tokens: Vec<(AccountId, Balance)>, + ) -> Self { + let mut init_staking_token = vec![(999u64, 10u128, 0u32), (999u64, 100u128, 1u32)]; + init_staking_token.append( + &mut staking_tokens + .iter() + .cloned() + .map(|(x, y)| (x, y, 1u32)) + .collect::>(), + ); + self.staking_tokens = init_staking_token; + self + } + + pub(crate) fn with_candidates(mut self, collators: Vec<(AccountId, Balance, TokenId)>) -> Self { + self.collators = collators; + self + } + + pub(crate) fn with_default_token_candidates( + mut self, + collators: Vec<(AccountId, Balance)>, + ) -> Self { + self.collators = collators.iter().cloned().map(|(x, y)| (x, y, 1u32)).collect(); + self + } + + pub(crate) fn with_delegations( + mut self, + delegations: Vec<(AccountId, AccountId, Balance)>, + ) -> Self { + self.delegations = delegations; + self + } + + pub(crate) fn build(self) -> sp_io::TestExternalities { + let mut t = frame_system::GenesisConfig::::default() + .build_storage() + .expect("Frame system builds valid default genesis config"); + + orml_tokens::GenesisConfig:: { + tokens_endowment: Default::default(), + created_tokens_for_staking: self + .staking_tokens + .iter() + .cloned() + .map(|(who, amount, token)| (who, token, amount)) + .collect(), + } + .assimilate_storage(&mut t) + .expect("Tokens storage can be assimilated"); + + stake::GenesisConfig:: { candidates: self.collators, delegations: self.delegations } + .assimilate_storage(&mut t) + .expect("Parachain Staking's storage can be assimilated"); + + let mut ext = sp_io::TestExternalities::new(t); + ext.execute_with(|| { + System::set_block_number(1); + + if !StakeCurrency::exists(MGA_TOKEN_ID) { + assert_ok!(StakeCurrency::create(&99999, 100)); + } + + let current_issuance = StakeCurrency::total_issuance(MGA_TOKEN_ID); + assert!(current_issuance <= TargetTge::get()); + + assert_ok!(StakeCurrency::mint( + MGA_TOKEN_ID, + &99999, + TargetTge::get() - current_issuance + )); + }); + ext + } +} + +pub(crate) fn payout_collator_for_round(n: u64) { + let collators: Vec<::AccountId> = + RoundCollatorRewardInfo::::iter_keys() + .filter_map(|(account, round)| if round == (n as u32) { Some(account) } else { None }) + .collect(); + + for collator in collators.iter() { + Stake::payout_collator_rewards(RuntimeOrigin::signed(999), collator.clone(), None).unwrap(); + } +} + +pub(crate) fn roll_to(n: u64) { + while System::block_number() < n { + Stake::on_finalize(System::block_number()); + Tokens::on_finalize(System::block_number()); + System::on_finalize(System::block_number()); + System::set_block_number(System::block_number() + 1); + System::on_initialize(System::block_number()); + Tokens::on_initialize(System::block_number()); + Stake::on_initialize(System::block_number()); + if >::should_end_session(System::block_number()) + { + if System::block_number().is_zero() { + >::start_session(Default::default()); + } else { + >::start_session(1); + } + } + } +} + +pub(crate) fn last_event() -> RuntimeEvent { + System::events().pop().expect("Event expected").event +} + +pub(crate) fn events() -> Vec> { + System::events() + .into_iter() + .map(|r| r.event) + .filter_map(|e| if let RuntimeEvent::Stake(inner) = e { Some(inner) } else { None }) + .collect::>() +} + +/// Assert input equal to the last event emitted +#[macro_export] +macro_rules! assert_last_event { + ($event:expr) => { + match &$event { + e => assert_eq!(*e, crate::mock::last_event()), + } + }; +} + +/// Compares the system events with passed in events +/// Prints highlighted diff iff assert_eq fails +#[macro_export] +macro_rules! assert_eq_events { + ($events:expr) => { + match &$events { + e => similar_asserts::assert_eq!(*e, crate::mock::events()), + } + }; +} + +/// Panics if an event is not found in the system log of events +#[macro_export] +macro_rules! assert_event_emitted { + ($event:expr) => { + match &$event { + e => { + assert!( + crate::mock::events().iter().find(|x| *x == e).is_some(), + "Event {:?} was not found in events: \n {:?}", + e, + crate::mock::events() + ); + }, + } + }; +} + +// Same storage changes as EventHandler::note_author impl +pub(crate) fn set_author(round: u32, acc: u64, pts: u32) { + >::mutate(round, |p| *p += pts); + >::mutate(round, acc, |p| *p += pts); +} + +pub type StakeCurrency = ::Currency; + +#[test] +fn geneses() { + ExtBuilder::default() + .with_staking_tokens(vec![ + (999, 100, 0), + (1, 1000, 1), + (2, 300, 2), + (3, 100, 1), + (4, 100, 1), + (5, 100, 2), + (6, 100, 2), + (7, 100, 3), + (8, 9, 3), + (9, 4, 3), + ]) + .with_candidates(vec![(1, 500, 1), (2, 200, 2)]) + .with_delegations(vec![(3, 1, 100), (4, 1, 100), (5, 2, 100), (6, 2, 100)]) + .build() + .execute_with(|| { + // collators + assert_eq!(StakeCurrency::reserved_balance(1, &1), 500); + assert_eq!(StakeCurrency::free_balance(1, &1), 500); + assert!(Stake::is_candidate(&1)); + assert_eq!(StakeCurrency::reserved_balance(2, &2), 200); + assert_eq!(StakeCurrency::free_balance(2, &2), 100); + assert!(Stake::is_candidate(&2)); + // delegators + for x in 3..5 { + assert!(Stake::is_delegator(&x)); + assert_eq!(StakeCurrency::free_balance(1, &x), 0); + assert_eq!(StakeCurrency::reserved_balance(1, &x), 100); + } + for x in 5..7 { + assert!(Stake::is_delegator(&x)); + assert_eq!(StakeCurrency::free_balance(2, &x), 0); + assert_eq!(StakeCurrency::reserved_balance(2, &x), 100); + } + // uninvolved + for x in 7..10 { + assert!(!Stake::is_delegator(&x)); + } + assert_eq!(StakeCurrency::free_balance(3, &7), 100); + assert_eq!(StakeCurrency::reserved_balance(3, &7), 0); + assert_eq!(StakeCurrency::free_balance(3, &8), 9); + assert_eq!(StakeCurrency::reserved_balance(3, &8), 0); + assert_eq!(StakeCurrency::free_balance(3, &9), 4); + assert_eq!(StakeCurrency::reserved_balance(3, &9), 0); + }); + ExtBuilder::default() + .with_staking_tokens(vec![ + (999, 100, 0), + (1, 100, 1), + (2, 100, 2), + (3, 100, 1), + (4, 100, 3), + (5, 100, 3), + (6, 100, 1), + (7, 100, 1), + (8, 100, 1), + (8, 100, 2), + (9, 100, 2), + (10, 100, 1), + ]) + .with_candidates(vec![(1, 20, 1), (2, 20, 2), (3, 20, 1), (4, 20, 3), (5, 10, 3)]) + .with_delegations(vec![ + (6, 1, 10), + (7, 1, 10), + (8, 1, 10), + (8, 2, 10), + (9, 2, 10), + (10, 1, 10), + ]) + .build() + .execute_with(|| { + // collators + for x in [1, 3] { + assert!(Stake::is_candidate(&x)); + assert_eq!(StakeCurrency::free_balance(1, &x), 80); + assert_eq!(StakeCurrency::reserved_balance(1, &x), 20); + } + assert!(Stake::is_candidate(&2)); + assert_eq!(StakeCurrency::free_balance(2, &2), 80); + assert_eq!(StakeCurrency::reserved_balance(2, &2), 20); + for x in 4..5 { + assert!(Stake::is_candidate(&x)); + assert_eq!(StakeCurrency::free_balance(3, &x), 80); + assert_eq!(StakeCurrency::reserved_balance(3, &x), 20); + } + assert!(Stake::is_candidate(&5)); + assert_eq!(StakeCurrency::free_balance(3, &5), 90); + assert_eq!(StakeCurrency::reserved_balance(3, &5), 10); + // delegators + for x in [6, 7, 8, 10] { + assert!(Stake::is_delegator(&x)); + assert_eq!(StakeCurrency::free_balance(1, &x), 90); + assert_eq!(StakeCurrency::reserved_balance(1, &x), 10); + } + for x in [8, 9] { + assert!(Stake::is_delegator(&x)); + assert_eq!(StakeCurrency::free_balance(2, &x), 90); + assert_eq!(StakeCurrency::reserved_balance(2, &x), 10); + } + }); +} diff --git a/gasp-node/pallets/parachain-staking/src/set.rs b/gasp-node/pallets/parachain-staking/src/set.rs new file mode 100644 index 000000000..f6ebc176b --- /dev/null +++ b/gasp-node/pallets/parachain-staking/src/set.rs @@ -0,0 +1,89 @@ +// Copyright 2019-2021 PureStake Inc. +// This file is part of Moonbeam. + +// Moonbeam 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. + +// Moonbeam 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 Moonbeam. If not, see . + +/* TODO: use orml_utilities::OrderedSet without leaking substrate v2.0 dependencies*/ +use codec::{Decode, Encode}; +use scale_info::TypeInfo; +#[cfg(feature = "std")] +use serde::{Deserialize, Serialize}; +use sp_runtime::RuntimeDebug; +use sp_std::prelude::*; + +/// An ordered set backed by `Vec` +#[cfg_attr(feature = "std", derive(Serialize, Deserialize))] +#[derive(RuntimeDebug, PartialEq, Eq, Encode, Decode, Default, Clone, TypeInfo)] +pub struct OrderedSet(pub Vec); + +impl OrderedSet { + /// Create a new empty set + pub fn new() -> Self { + Self(Vec::new()) + } + + /// Create a set from a `Vec`. + /// `v` will be sorted and dedup first. + pub fn from(mut v: Vec) -> Self { + v.sort(); + v.dedup(); + Self::from_sorted_set(v) + } + + /// Create a set from a `Vec`. + /// Assume `v` is sorted and contain unique elements. + pub fn from_sorted_set(v: Vec) -> Self { + Self(v) + } + + /// Insert an element. + /// Return true if insertion happened. + pub fn insert(&mut self, value: T) -> bool { + match self.0.binary_search(&value) { + Ok(_) => false, + Err(loc) => { + self.0.insert(loc, value); + true + }, + } + } + + /// Remove an element. + /// Return true if removal happened. + pub fn remove(&mut self, value: &T) -> bool { + match self.0.binary_search(value) { + Ok(loc) => { + self.0.remove(loc); + true + }, + Err(_) => false, + } + } + + /// Return if the set contains `value` + pub fn contains(&self, value: &T) -> bool { + self.0.binary_search(value).is_ok() + } + + /// Clear the set + pub fn clear(&mut self) { + self.0.clear(); + } +} + +impl From> for OrderedSet { + fn from(v: Vec) -> Self { + Self::from(v) + } +} diff --git a/gasp-node/pallets/parachain-staking/src/tests.rs b/gasp-node/pallets/parachain-staking/src/tests.rs new file mode 100644 index 000000000..f4c2b7487 --- /dev/null +++ b/gasp-node/pallets/parachain-staking/src/tests.rs @@ -0,0 +1,5083 @@ +// Copyright 2019-2021 PureStake Inc. +// This file is part of Moonbeam. + +// Moonbeam 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. + +// Moonbeam 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 Moonbeam. If not, see . + +//! # Staking Pallet Unit Tests +//! The unit tests are organized by the call they test. The order matches the order +//! of the calls in the `lib.rs`. +//! 1. Root +//! 2. Monetary Governance +//! 3. Public (Collator, Nominator) +//! 4. Miscellaneous Property-Based Tests + +use crate::mock::{ + payout_collator_for_round, roll_to, set_author, AccountId, Balance, ExtBuilder, + RuntimeEvent as MetaEvent, RuntimeOrigin as Origin, Stake, StakeCurrency, Test, MGA_TOKEN_ID, +}; + +use crate::{ + assert_eq_events, assert_event_emitted, assert_last_event, Bond, CandidateBondChange, + CandidateBondRequest, CollatorStatus, DelegationChange, DelegationRequest, DelegatorAdded, + Error, Event, MetadataUpdateAction, PairedOrLiquidityToken, PayoutRounds, RoundAggregatorInfo, + RoundCollatorRewardInfo, TotalSelected, +}; +use frame_support::{assert_noop, assert_ok, traits::tokens::currency::MultiTokenCurrency}; +use orml_tokens::MultiTokenReservableCurrency; +use sp_runtime::{traits::Zero, DispatchError, ModuleError, Perbill}; + +// ~~ ROOT ~~ + +#[test] +fn invalid_root_origin_fails() { + ExtBuilder::default().build().execute_with(|| { + assert_noop!( + Stake::set_total_selected(Origin::signed(45), 6u32), + sp_runtime::DispatchError::BadOrigin + ); + assert_noop!( + Stake::set_collator_commission(Origin::signed(45), Perbill::from_percent(5)), + sp_runtime::DispatchError::BadOrigin + ); + }); +} + +// SET TOTAL SELECTED + +#[test] +fn set_total_selected_event_emits_correctly() { + ExtBuilder::default().build().execute_with(|| { + assert_ok!(Stake::set_total_selected(Origin::root(), 6u32)); + assert_last_event!(MetaEvent::Stake(Event::TotalSelectedSet(5u32, 6u32))); + }); +} + +#[test] +fn set_total_selected_storage_updates_correctly() { + ExtBuilder::default().build().execute_with(|| { + assert_eq!(Stake::total_selected(), 5u32); + assert_ok!(Stake::set_total_selected(Origin::root(), 6u32)); + assert_eq!(Stake::total_selected(), 6u32); + }); +} + +#[test] +fn cannot_set_total_selected_to_current_total_selected() { + ExtBuilder::default().build().execute_with(|| { + assert_noop!( + Stake::set_total_selected(Origin::root(), 5u32), + Error::::NoWritingSameValue + ); + }); +} + +#[test] +fn cannot_set_total_selected_below_module_min() { + ExtBuilder::default().build().execute_with(|| { + assert_noop!( + Stake::set_total_selected(Origin::root(), 4u32), + Error::::CannotSetBelowMin + ); + }); +} + +// SET COLLATOR COMMISSION + +#[test] +fn set_collator_commission_event_emits_correctly() { + ExtBuilder::default().build().execute_with(|| { + assert_ok!(Stake::set_collator_commission(Origin::root(), Perbill::from_percent(5))); + assert_last_event!(MetaEvent::Stake(Event::CollatorCommissionSet( + Perbill::from_percent(20), + Perbill::from_percent(5), + ))); + }); +} + +#[test] +fn set_collator_commission_storage_updates_correctly() { + ExtBuilder::default().build().execute_with(|| { + assert_eq!(Stake::collator_commission(), Perbill::from_percent(20)); + assert_ok!(Stake::set_collator_commission(Origin::root(), Perbill::from_percent(5))); + assert_eq!(Stake::collator_commission(), Perbill::from_percent(5)); + }); +} + +#[test] +fn cannot_set_collator_commission_to_current_collator_commission() { + ExtBuilder::default().build().execute_with(|| { + assert_noop!( + Stake::set_collator_commission(Origin::root(), Perbill::from_percent(20)), + Error::::NoWritingSameValue + ); + }); +} + +// ~~ PUBLIC ~~ + +// JOIN CANDIDATES + +#[test] +fn join_candidates_event_emits_correctly() { + ExtBuilder::default() + .with_default_staking_token(vec![(1, 10)]) + .with_default_token_candidates(vec![(999, 10)]) + .build() + .execute_with(|| { + assert_ok!(Stake::join_candidates( + Origin::signed(1), + 10u128, + 1u32, + None, + 1u32, + 10000u32 + )); + assert_last_event!(MetaEvent::Stake(Event::JoinedCollatorCandidates( + 1, 10u128, 20u128, + ))); + }); +} + +#[test] +fn join_candidates_reserves_balance() { + ExtBuilder::default() + .with_default_staking_token(vec![(1, 10)]) + .with_default_token_candidates(vec![(999, 10)]) + .build() + .execute_with(|| { + assert_eq!(StakeCurrency::reserved_balance(1, &1), 0); + assert_eq!(StakeCurrency::free_balance(1, &1), 10); + assert_ok!(Stake::join_candidates( + Origin::signed(1), + 10u128, + 1u32, + None, + 1u32, + 10000u32 + )); + assert_eq!(StakeCurrency::reserved_balance(1, &1), 10); + assert_eq!(StakeCurrency::free_balance(1, &1), 0); + }); +} + +#[test] +fn join_candidates_increases_total_staked() { + ExtBuilder::default() + .with_default_staking_token(vec![(1, 10)]) + .with_default_token_candidates(vec![(999, 10)]) + .build() + .execute_with(|| { + assert_eq!(Stake::total(1u32), 10); + assert_ok!(Stake::join_candidates( + Origin::signed(1), + 10u128, + 1u32, + None, + 1u32, + 10000u32 + )); + assert_eq!(Stake::total(1u32), 20); + }); +} + +#[test] +fn join_candidates_creates_candidate_state() { + ExtBuilder::default() + .with_default_staking_token(vec![(1, 10)]) + .with_default_token_candidates(vec![(999, 10)]) + .build() + .execute_with(|| { + assert!(Stake::candidate_state(1).is_none()); + assert_ok!(Stake::join_candidates( + Origin::signed(1), + 10u128, + 1u32, + None, + 1u32, + 10000u32 + )); + let candidate_state = Stake::candidate_state(1).expect("just joined => exists"); + assert_eq!(candidate_state.bond, 10u128); + }); +} + +#[test] +fn join_candidates_adds_to_candidate_pool() { + ExtBuilder::default() + .with_default_staking_token(vec![(1, 10)]) + .with_default_token_candidates(vec![(999, 10)]) + .build() + .execute_with(|| { + assert_eq!(Stake::candidate_pool().0.len(), 1usize); + assert_ok!(Stake::join_candidates( + Origin::signed(1), + 10u128, + 1u32, + None, + 1u32, + 10000u32 + )); + let candidate_pool = Stake::candidate_pool(); + assert_eq!( + candidate_pool.0[0], + Bond { owner: 1, amount: 10u128, liquidity_token: 1u32 } + ); + }); +} + +#[test] +fn cannot_join_candidates_if_candidate() { + ExtBuilder::default() + .with_default_staking_token(vec![(1, 1000)]) + .with_default_token_candidates(vec![(1, 500)]) + .build() + .execute_with(|| { + assert_noop!( + Stake::join_candidates(Origin::signed(1), 11u128, 1u32, None, 100u32, 10000u32), + Error::::CandidateExists + ); + }); +} + +#[test] +fn cannot_join_candidates_if_delegator() { + ExtBuilder::default() + .with_default_staking_token(vec![(1, 50), (2, 20)]) + .with_default_token_candidates(vec![(1, 50)]) + .with_delegations(vec![(2, 1, 10)]) + .build() + .execute_with(|| { + assert_noop!( + Stake::join_candidates(Origin::signed(2), 10u128, 1u32, None, 1u32, 10000u32), + Error::::DelegatorExists + ); + }); +} + +#[test] +fn cannot_join_candidates_without_min_bond() { + ExtBuilder::default() + .with_default_staking_token(vec![(1, 1000)]) + .with_default_token_candidates(vec![(999, 10)]) + .build() + .execute_with(|| { + assert_noop!( + Stake::join_candidates(Origin::signed(1), 9u128, 1u32, None, 100u32, 10000u32), + Error::::CandidateBondBelowMin + ); + }); +} + +#[test] +fn cannot_join_candidates_with_more_than_available_balance() { + ExtBuilder::default() + .with_default_staking_token(vec![(1, 500)]) + .with_default_token_candidates(vec![(999, 10)]) + .build() + .execute_with(|| { + assert_noop!( + Stake::join_candidates(Origin::signed(1), 501u128, 1u32, None, 100u32, 10000u32), + DispatchError::Module(ModuleError { + index: 1, + error: [0; 4], + message: Some("BalanceTooLow") + }) + ); + }); +} + +#[test] +fn insufficient_join_candidates_weight_hint_fails() { + ExtBuilder::default() + .with_default_staking_token(vec![(1, 20), (2, 20), (3, 20), (4, 20), (5, 20), (6, 20)]) + .with_default_token_candidates(vec![(1, 20), (2, 20), (3, 20), (4, 20), (5, 20)]) + .build() + .execute_with(|| { + for i in 0..5 { + assert_noop!( + Stake::join_candidates(Origin::signed(6), 20, 1u32, None, i, 10000u32), + Error::::TooLowCandidateCountWeightHintJoinCandidates + ); + } + }); +} + +#[test] +fn sufficient_join_candidates_weight_hint_succeeds() { + ExtBuilder::default() + .with_default_staking_token(vec![ + (1, 20), + (2, 20), + (3, 20), + (4, 20), + (5, 20), + (6, 20), + (7, 20), + (8, 20), + (9, 20), + ]) + .with_default_token_candidates(vec![(1, 20), (2, 20), (3, 20), (4, 20), (5, 20)]) + .build() + .execute_with(|| { + let mut count = 5u32; + for i in 6..10 { + assert_ok!(Stake::join_candidates( + Origin::signed(i), + 20, + 1u32, + None, + 10000u32, + count + )); + count += 1u32; + } + }); +} + +// SCHEDULE LEAVE CANDIDATES + +#[test] +fn leave_candidates_event_emits_correctly() { + ExtBuilder::default() + .with_default_staking_token(vec![(1, 10)]) + .with_default_token_candidates(vec![(1, 10)]) + .build() + .execute_with(|| { + assert_ok!(Stake::schedule_leave_candidates(Origin::signed(1), 1u32)); + assert_last_event!(MetaEvent::Stake(Event::CandidateScheduledExit(0, 1, 2))); + }); +} + +#[test] +fn leave_candidates_removes_candidate_from_candidate_pool() { + ExtBuilder::default() + .with_default_staking_token(vec![(1, 10)]) + .with_default_token_candidates(vec![(1, 10)]) + .build() + .execute_with(|| { + assert_eq!(Stake::candidate_pool().0.len(), 1); + assert_ok!(Stake::schedule_leave_candidates(Origin::signed(1), 1u32)); + assert!(Stake::candidate_pool().0.is_empty()); + }); +} + +#[test] +fn cannot_leave_candidates_if_not_candidate() { + ExtBuilder::default().build().execute_with(|| { + assert_noop!( + Stake::schedule_leave_candidates(Origin::signed(1), 1u32), + Error::::CandidateDNE + ); + }); +} + +#[test] +fn cannot_leave_candidates_if_already_leaving_candidates() { + ExtBuilder::default() + .with_default_staking_token(vec![(1, 10)]) + .with_default_token_candidates(vec![(1, 10)]) + .build() + .execute_with(|| { + assert_ok!(Stake::schedule_leave_candidates(Origin::signed(1), 1u32)); + assert_noop!( + Stake::schedule_leave_candidates(Origin::signed(1), 1u32), + Error::::CandidateAlreadyLeaving + ); + }); +} + +#[test] +fn insufficient_leave_candidates_weight_hint_fails() { + ExtBuilder::default() + .with_default_staking_token(vec![(1, 20), (2, 20), (3, 20), (4, 20), (5, 20)]) + .with_default_token_candidates(vec![(1, 20), (2, 20), (3, 20), (4, 20), (5, 20)]) + .build() + .execute_with(|| { + for i in 1..6 { + assert_noop!( + Stake::schedule_leave_candidates(Origin::signed(i), 4u32), + Error::::TooLowCandidateCountToLeaveCandidates + ); + } + }); +} + +#[test] +fn sufficient_leave_candidates_weight_hint_succeeds() { + ExtBuilder::default() + .with_default_staking_token(vec![(1, 20), (2, 20), (3, 20), (4, 20), (5, 20)]) + .with_default_token_candidates(vec![(1, 20), (2, 20), (3, 20), (4, 20), (5, 20)]) + .build() + .execute_with(|| { + let mut count = 5u32; + for i in 1..6 { + assert_ok!(Stake::schedule_leave_candidates(Origin::signed(i), count)); + count -= 1u32; + } + }); +} + +// EXECUTE LEAVE CANDIDATES + +#[test] +fn execute_leave_candidates_emits_event() { + ExtBuilder::default() + .with_default_staking_token(vec![(1, 10)]) + .with_default_token_candidates(vec![(1, 10)]) + .build() + .execute_with(|| { + assert_ok!(Stake::schedule_leave_candidates(Origin::signed(1), 1u32)); + roll_to(10); + assert_ok!(Stake::execute_leave_candidates(Origin::signed(1), 1, 10000u32)); + assert_last_event!(MetaEvent::Stake(Event::CandidateLeft(1, 10, 0))); + }); +} + +#[test] +fn execute_leave_candidates_callable_by_any_signed() { + ExtBuilder::default() + .with_default_staking_token(vec![(1, 10)]) + .with_default_token_candidates(vec![(1, 10)]) + .build() + .execute_with(|| { + assert_ok!(Stake::schedule_leave_candidates(Origin::signed(1), 1u32)); + roll_to(10); + assert_ok!(Stake::execute_leave_candidates(Origin::signed(2), 1, 10000u32)); + }); +} + +#[test] +fn execute_leave_candidates_unreserves_balance() { + ExtBuilder::default() + .with_default_staking_token(vec![(1, 10)]) + .with_default_token_candidates(vec![(1, 10)]) + .build() + .execute_with(|| { + assert_eq!(StakeCurrency::reserved_balance(1, &1), 10); + assert_eq!(StakeCurrency::free_balance(1, &1), 0); + assert_ok!(Stake::schedule_leave_candidates(Origin::signed(1), 1u32)); + roll_to(10); + assert_ok!(Stake::execute_leave_candidates(Origin::signed(1), 1, 10000u32)); + assert_eq!(StakeCurrency::reserved_balance(1, &1), 0); + assert_eq!(StakeCurrency::free_balance(1, &1), 10); + }); +} + +#[test] +fn execute_leave_candidates_decreases_total_staked() { + ExtBuilder::default() + .with_default_staking_token(vec![(1, 10)]) + .with_default_token_candidates(vec![(1, 10)]) + .build() + .execute_with(|| { + assert_eq!(Stake::total(1u32), 10); + assert_ok!(Stake::schedule_leave_candidates(Origin::signed(1), 1u32)); + roll_to(10); + assert_ok!(Stake::execute_leave_candidates(Origin::signed(1), 1, 10000u32)); + assert_eq!(Stake::total(1u32), 0); + }); +} + +#[test] +fn execute_leave_candidates_removes_candidate_state() { + ExtBuilder::default() + .with_default_staking_token(vec![(1, 10)]) + .with_default_token_candidates(vec![(1, 10)]) + .build() + .execute_with(|| { + assert_ok!(Stake::schedule_leave_candidates(Origin::signed(1), 1u32)); + // candidate state is not immediately removed + let candidate_state = Stake::candidate_state(1).expect("just left => still exists"); + assert_eq!(candidate_state.bond, 10u128); + roll_to(10); + assert_ok!(Stake::execute_leave_candidates(Origin::signed(1), 1, 10000u32)); + assert!(Stake::candidate_state(1).is_none()); + }); +} + +#[test] +fn cannot_execute_leave_candidates_before_delay() { + ExtBuilder::default() + .with_default_staking_token(vec![(1, 10)]) + .with_default_token_candidates(vec![(1, 10)]) + .build() + .execute_with(|| { + assert_ok!(Stake::schedule_leave_candidates(Origin::signed(1), 1u32)); + assert_noop!( + Stake::execute_leave_candidates(Origin::signed(3), 1, 10000u32), + Error::::CandidateCannotLeaveYet + ); + roll_to(8); + assert_noop!( + Stake::execute_leave_candidates(Origin::signed(3), 1, 10000u32), + Error::::CandidateCannotLeaveYet + ); + roll_to(10); + assert_ok!(Stake::execute_leave_candidates(Origin::signed(3), 1, 10000u32)); + }); +} + +// CANCEL LEAVE CANDIDATES + +#[test] +fn cancel_leave_candidates_emits_event() { + ExtBuilder::default() + .with_default_staking_token(vec![(1, 10)]) + .with_default_token_candidates(vec![(1, 10)]) + .build() + .execute_with(|| { + assert_ok!(Stake::schedule_leave_candidates(Origin::signed(1), 1u32)); + assert_ok!(Stake::cancel_leave_candidates(Origin::signed(1), 1)); + assert_last_event!(MetaEvent::Stake(Event::CancelledCandidateExit(1))); + }); +} + +#[test] +fn cancel_leave_candidates_updates_candidate_state() { + ExtBuilder::default() + .with_default_staking_token(vec![(1, 10)]) + .with_default_token_candidates(vec![(1, 10)]) + .build() + .execute_with(|| { + assert_ok!(Stake::schedule_leave_candidates(Origin::signed(1), 1u32)); + assert_ok!(Stake::cancel_leave_candidates(Origin::signed(1), 1)); + let candidate = Stake::candidate_state(&1).expect("just cancelled leave so exists"); + assert!(candidate.is_active()); + }); +} + +#[test] +fn cancel_leave_candidates_adds_to_candidate_pool() { + ExtBuilder::default() + .with_default_staking_token(vec![(1, 10)]) + .with_default_token_candidates(vec![(1, 10)]) + .build() + .execute_with(|| { + assert_ok!(Stake::schedule_leave_candidates(Origin::signed(1), 1u32)); + assert_ok!(Stake::cancel_leave_candidates(Origin::signed(1), 1)); + assert_eq!( + Stake::candidate_pool().0[0], + Bond { owner: 1, amount: 10, liquidity_token: 1u32 } + ); + }); +} + +// GO OFFLINE + +#[test] +fn go_offline_event_emits_correctly() { + ExtBuilder::default() + .with_default_staking_token(vec![(1, 20)]) + .with_default_token_candidates(vec![(1, 20)]) + .build() + .execute_with(|| { + assert_ok!(Stake::go_offline(Origin::signed(1))); + assert_last_event!(MetaEvent::Stake(Event::CandidateWentOffline(0, 1))); + }); +} + +#[test] +fn go_offline_removes_candidate_from_candidate_pool() { + ExtBuilder::default() + .with_default_staking_token(vec![(1, 20)]) + .with_default_token_candidates(vec![(1, 20)]) + .build() + .execute_with(|| { + assert_eq!(Stake::candidate_pool().0.len(), 1); + assert_ok!(Stake::go_offline(Origin::signed(1))); + assert!(Stake::candidate_pool().0.is_empty()); + }); +} + +#[test] +fn go_offline_updates_candidate_state_to_idle() { + ExtBuilder::default() + .with_default_staking_token(vec![(1, 20)]) + .with_default_token_candidates(vec![(1, 20)]) + .build() + .execute_with(|| { + let candidate_state = Stake::candidate_state(1).expect("is active candidate"); + assert_eq!(candidate_state.state, CollatorStatus::Active); + assert_ok!(Stake::go_offline(Origin::signed(1))); + let candidate_state = Stake::candidate_state(1).expect("is candidate, just offline"); + assert_eq!(candidate_state.state, CollatorStatus::Idle); + }); +} + +#[test] +fn cannot_go_offline_if_not_candidate() { + ExtBuilder::default().build().execute_with(|| { + assert_noop!(Stake::go_offline(Origin::signed(3)), Error::::CandidateDNE); + }); +} + +#[test] +fn cannot_go_offline_if_already_offline() { + ExtBuilder::default() + .with_default_staking_token(vec![(1, 20)]) + .with_default_token_candidates(vec![(1, 20)]) + .build() + .execute_with(|| { + assert_ok!(Stake::go_offline(Origin::signed(1))); + assert_noop!(Stake::go_offline(Origin::signed(1)), Error::::AlreadyOffline); + }); +} + +// GO ONLINE + +#[test] +fn go_online_event_emits_correctly() { + ExtBuilder::default() + .with_default_staking_token(vec![(1, 20)]) + .with_default_token_candidates(vec![(1, 20)]) + .build() + .execute_with(|| { + assert_ok!(Stake::go_offline(Origin::signed(1))); + assert_ok!(Stake::go_online(Origin::signed(1))); + assert_last_event!(MetaEvent::Stake(Event::CandidateBackOnline(0, 1))); + }); +} + +#[test] +fn go_online_adds_to_candidate_pool() { + ExtBuilder::default() + .with_default_staking_token(vec![(1, 20)]) + .with_default_token_candidates(vec![(1, 20)]) + .build() + .execute_with(|| { + assert_ok!(Stake::go_offline(Origin::signed(1))); + assert!(Stake::candidate_pool().0.is_empty()); + assert_ok!(Stake::go_online(Origin::signed(1))); + assert_eq!( + Stake::candidate_pool().0[0], + Bond { owner: 1, amount: 20, liquidity_token: 1u32 } + ); + }); +} + +#[test] +fn go_online_storage_updates_candidate_state() { + ExtBuilder::default() + .with_default_staking_token(vec![(1, 20)]) + .with_default_token_candidates(vec![(1, 20)]) + .build() + .execute_with(|| { + assert_ok!(Stake::go_offline(Origin::signed(1))); + let candidate_state = Stake::candidate_state(1).expect("offline still exists"); + assert_eq!(candidate_state.state, CollatorStatus::Idle); + assert_ok!(Stake::go_online(Origin::signed(1))); + let candidate_state = Stake::candidate_state(1).expect("online so exists"); + assert_eq!(candidate_state.state, CollatorStatus::Active); + }); +} + +#[test] +fn cannot_go_online_if_not_candidate() { + ExtBuilder::default().build().execute_with(|| { + assert_noop!(Stake::go_online(Origin::signed(3)), Error::::CandidateDNE); + }); +} + +#[test] +fn cannot_go_online_if_already_online() { + ExtBuilder::default() + .with_default_staking_token(vec![(1, 20)]) + .with_default_token_candidates(vec![(1, 20)]) + .build() + .execute_with(|| { + assert_noop!(Stake::go_online(Origin::signed(1)), Error::::AlreadyActive); + }); +} + +#[test] +fn cannot_go_online_if_leaving() { + ExtBuilder::default() + .with_default_staking_token(vec![(1, 20)]) + .with_default_token_candidates(vec![(1, 20)]) + .build() + .execute_with(|| { + assert_ok!(Stake::schedule_leave_candidates(Origin::signed(1), 1)); + assert_noop!( + Stake::go_online(Origin::signed(1)), + Error::::CannotGoOnlineIfLeaving + ); + }); +} + +// SCHEDULE CANDIDATE BOND MORE + +#[test] +fn schedule_candidate_bond_more_event_emits_correctly() { + ExtBuilder::default() + .with_default_staking_token(vec![(1, 50)]) + .with_default_token_candidates(vec![(1, 20)]) + .build() + .execute_with(|| { + assert_ok!(Stake::schedule_candidate_bond_more(Origin::signed(1), 30, None)); + assert_last_event!(MetaEvent::Stake(Event::CandidateBondMoreRequested(1, 30, 2,))); + }); +} + +#[test] +fn schedule_candidate_bond_more_updates_candidate_state() { + ExtBuilder::default() + .with_default_staking_token(vec![(1, 50)]) + .with_default_token_candidates(vec![(1, 20)]) + .build() + .execute_with(|| { + assert_ok!(Stake::schedule_candidate_bond_more(Origin::signed(1), 30, None)); + let state = Stake::candidate_state(&1).expect("request bonded more so exists"); + assert_eq!( + state.request, + Some(CandidateBondRequest { + amount: 30, + change: CandidateBondChange::Increase, + when_executable: 2, + }) + ); + }); +} + +#[test] +fn cannot_schedule_candidate_bond_more_if_request_exists() { + ExtBuilder::default() + .with_default_staking_token(vec![(1, 40)]) + .with_default_token_candidates(vec![(1, 30)]) + .build() + .execute_with(|| { + assert_ok!(Stake::schedule_candidate_bond_more(Origin::signed(1), 5, None)); + assert_noop!( + Stake::schedule_candidate_bond_more(Origin::signed(1), 5, None), + Error::::PendingCandidateRequestAlreadyExists + ); + }); +} + +#[test] +fn cannot_schedule_candidate_bond_more_if_not_candidate() { + ExtBuilder::default().build().execute_with(|| { + assert_noop!( + Stake::schedule_candidate_bond_more(Origin::signed(6), 50, None), + Error::::CandidateDNE + ); + }); +} + +#[test] +fn cannot_schedule_candidate_bond_more_if_insufficient_balance() { + ExtBuilder::default() + .with_default_staking_token(vec![(1, 30)]) + .with_default_token_candidates(vec![(1, 30)]) + .build() + .execute_with(|| { + assert_noop!( + Stake::schedule_candidate_bond_more(Origin::signed(1), 1, None), + Error::::InsufficientBalance + ); + }); +} + +#[test] +fn can_schedule_candidate_bond_more_if_leaving_candidates() { + ExtBuilder::default() + .with_default_staking_token(vec![(1, 50)]) + .with_default_token_candidates(vec![(1, 20)]) + .build() + .execute_with(|| { + assert_ok!(Stake::schedule_leave_candidates(Origin::signed(1), 1)); + assert_ok!(Stake::schedule_candidate_bond_more(Origin::signed(1), 30, None)); + }); +} + +#[test] +fn cannot_schedule_candidate_bond_more_if_exited_candidates() { + ExtBuilder::default() + .with_default_staking_token(vec![(1, 50)]) + .with_default_token_candidates(vec![(1, 20)]) + .build() + .execute_with(|| { + assert_ok!(Stake::schedule_leave_candidates(Origin::signed(1), 1)); + roll_to(10); + assert_ok!(Stake::execute_leave_candidates(Origin::signed(1), 1, 10000u32)); + assert_noop!( + Stake::schedule_candidate_bond_more(Origin::signed(1), 30, None), + Error::::CandidateDNE + ); + }); +} + +// CANDIDATE BOND LESS + +#[test] +fn schedule_candidate_bond_less_event_emits_correctly() { + ExtBuilder::default() + .with_default_staking_token(vec![(1, 30)]) + .with_default_token_candidates(vec![(1, 30)]) + .build() + .execute_with(|| { + assert_ok!(Stake::schedule_candidate_bond_less(Origin::signed(1), 10)); + assert_last_event!(MetaEvent::Stake(Event::CandidateBondLessRequested(1, 10, 2,))); + }); +} + +#[test] +fn cannot_schedule_candidate_bond_less_if_request_exists() { + ExtBuilder::default() + .with_default_staking_token(vec![(1, 30)]) + .with_default_token_candidates(vec![(1, 30)]) + .build() + .execute_with(|| { + assert_ok!(Stake::schedule_candidate_bond_less(Origin::signed(1), 5)); + assert_noop!( + Stake::schedule_candidate_bond_less(Origin::signed(1), 5), + Error::::PendingCandidateRequestAlreadyExists + ); + }); +} + +#[test] +fn cannot_schedule_candidate_bond_less_if_not_candidate() { + ExtBuilder::default().build().execute_with(|| { + assert_noop!( + Stake::schedule_candidate_bond_less(Origin::signed(6), 50), + Error::::CandidateDNE + ); + }); +} + +#[test] +fn cannot_schedule_candidate_bond_less_if_new_total_below_min_candidate_stk() { + ExtBuilder::default() + .with_default_staking_token(vec![(1, 30)]) + .with_default_token_candidates(vec![(1, 30)]) + .build() + .execute_with(|| { + assert_noop!( + Stake::schedule_candidate_bond_less(Origin::signed(1), 21), + Error::::CandidateBondBelowMin + ); + }); +} + +#[test] +fn can_schedule_candidate_bond_less_if_leaving_candidates() { + ExtBuilder::default() + .with_default_staking_token(vec![(1, 30)]) + .with_default_token_candidates(vec![(1, 30)]) + .build() + .execute_with(|| { + assert_ok!(Stake::schedule_leave_candidates(Origin::signed(1), 1)); + assert_ok!(Stake::schedule_candidate_bond_less(Origin::signed(1), 10)); + }); +} + +#[test] +fn cannot_schedule_candidate_bond_less_if_exited_candidates() { + ExtBuilder::default() + .with_default_staking_token(vec![(1, 30)]) + .with_default_token_candidates(vec![(1, 30)]) + .build() + .execute_with(|| { + assert_ok!(Stake::schedule_leave_candidates(Origin::signed(1), 1)); + roll_to(10); + assert_ok!(Stake::execute_leave_candidates(Origin::signed(1), 1, 10000u32)); + assert_noop!( + Stake::schedule_candidate_bond_less(Origin::signed(1), 10), + Error::::CandidateDNE + ); + }); +} + +// EXECUTE CANDIDATE BOND REQUEST +// 1. BOND MORE REQUEST + +#[test] +fn execute_candidate_bond_more_emits_correct_event() { + ExtBuilder::default() + .with_default_staking_token(vec![(1, 50)]) + .with_default_token_candidates(vec![(1, 20)]) + .build() + .execute_with(|| { + assert_ok!(Stake::schedule_candidate_bond_more(Origin::signed(1), 30, None)); + roll_to(10); + assert_ok!(Stake::execute_candidate_bond_request(Origin::signed(1), 1, None)); + assert_last_event!(MetaEvent::Stake(Event::CandidateBondedMore(1, 30, 50))); + }); +} + +#[test] +fn execute_candidate_bond_more_reserves_balance() { + ExtBuilder::default() + .with_default_staking_token(vec![(1, 50)]) + .with_default_token_candidates(vec![(1, 20)]) + .build() + .execute_with(|| { + assert_eq!(StakeCurrency::reserved_balance(1, &1), 20); + assert_eq!(StakeCurrency::free_balance(1, &1), 30); + assert_ok!(Stake::schedule_candidate_bond_more(Origin::signed(1), 30, None)); + roll_to(10); + assert_ok!(Stake::execute_candidate_bond_request(Origin::signed(1), 1, None)); + assert_eq!(StakeCurrency::reserved_balance(1, &1), 50); + assert_eq!(StakeCurrency::free_balance(1, &1), 0); + }); +} + +#[test] +fn execute_candidate_bond_more_increases_total() { + ExtBuilder::default() + .with_default_staking_token(vec![(1, 50)]) + .with_default_token_candidates(vec![(1, 20)]) + .build() + .execute_with(|| { + let mut total = Stake::total(1u32); + assert_ok!(Stake::schedule_candidate_bond_more(Origin::signed(1), 30, None)); + roll_to(10); + assert_ok!(Stake::execute_candidate_bond_request(Origin::signed(1), 1, None)); + total += 30; + assert_eq!(Stake::total(1u32), total); + }); +} + +#[test] +fn execute_candidate_bond_more_updates_candidate_state() { + ExtBuilder::default() + .with_default_staking_token(vec![(1, 50)]) + .with_default_token_candidates(vec![(1, 20)]) + .build() + .execute_with(|| { + let candidate_state = Stake::candidate_state(1).expect("updated => exists"); + assert_eq!(candidate_state.bond, 20); + assert_ok!(Stake::schedule_candidate_bond_more(Origin::signed(1), 30, None)); + roll_to(10); + assert_ok!(Stake::execute_candidate_bond_request(Origin::signed(1), 1, None)); + let candidate_state = Stake::candidate_state(1).expect("updated => exists"); + assert_eq!(candidate_state.bond, 50); + }); +} + +#[test] +fn execute_candidate_bond_more_updates_candidate_pool() { + ExtBuilder::default() + .with_default_staking_token(vec![(1, 50)]) + .with_default_token_candidates(vec![(1, 20)]) + .build() + .execute_with(|| { + assert_eq!( + Stake::candidate_pool().0[0], + Bond { owner: 1, amount: 20, liquidity_token: 1u32 } + ); + assert_ok!(Stake::schedule_candidate_bond_more(Origin::signed(1), 30, None)); + roll_to(10); + assert_ok!(Stake::execute_candidate_bond_request(Origin::signed(1), 1, None)); + assert_eq!( + Stake::candidate_pool().0[0], + Bond { owner: 1, amount: 50, liquidity_token: 1u32 } + ); + }); +} + +// 2. BOND LESS REQUEST + +#[test] +fn execute_candidate_bond_less_emits_correct_event() { + ExtBuilder::default() + .with_default_staking_token(vec![(1, 50)]) + .with_default_token_candidates(vec![(1, 50)]) + .build() + .execute_with(|| { + assert_ok!(Stake::schedule_candidate_bond_less(Origin::signed(1), 30)); + roll_to(10); + assert_ok!(Stake::execute_candidate_bond_request(Origin::signed(1), 1, None)); + assert_last_event!(MetaEvent::Stake(Event::CandidateBondedLess(1, 30, 20))); + }); +} + +#[test] +fn execute_candidate_bond_less_unreserves_balance() { + ExtBuilder::default() + .with_default_staking_token(vec![(1, 30)]) + .with_default_token_candidates(vec![(1, 30)]) + .build() + .execute_with(|| { + assert_eq!(StakeCurrency::reserved_balance(1, &1), 30); + assert_eq!(StakeCurrency::free_balance(1, &1), 0); + assert_ok!(Stake::schedule_candidate_bond_less(Origin::signed(1), 10)); + roll_to(10); + assert_ok!(Stake::execute_candidate_bond_request(Origin::signed(1), 1, None)); + assert_eq!(StakeCurrency::reserved_balance(1, &1), 20); + assert_eq!(StakeCurrency::free_balance(1, &1), 10); + }); +} + +#[test] +fn execute_candidate_bond_less_decreases_total() { + ExtBuilder::default() + .with_default_staking_token(vec![(1, 30)]) + .with_default_token_candidates(vec![(1, 30)]) + .build() + .execute_with(|| { + let mut total = Stake::total(1u32); + assert_ok!(Stake::schedule_candidate_bond_less(Origin::signed(1), 10)); + roll_to(10); + assert_ok!(Stake::execute_candidate_bond_request(Origin::signed(1), 1, None)); + total -= 10; + assert_eq!(Stake::total(1u32), total); + }); +} + +#[test] +fn execute_candidate_bond_less_updates_candidate_state() { + ExtBuilder::default() + .with_default_staking_token(vec![(1, 30)]) + .with_default_token_candidates(vec![(1, 30)]) + .build() + .execute_with(|| { + let candidate_state = Stake::candidate_state(1).expect("updated => exists"); + assert_eq!(candidate_state.bond, 30); + assert_ok!(Stake::schedule_candidate_bond_less(Origin::signed(1), 10)); + roll_to(10); + assert_ok!(Stake::execute_candidate_bond_request(Origin::signed(1), 1, None)); + let candidate_state = Stake::candidate_state(1).expect("updated => exists"); + assert_eq!(candidate_state.bond, 20); + }); +} + +#[test] +fn execute_candidate_bond_less_updates_candidate_pool() { + ExtBuilder::default() + .with_default_staking_token(vec![(1, 30)]) + .with_default_token_candidates(vec![(1, 30)]) + .build() + .execute_with(|| { + assert_eq!( + Stake::candidate_pool().0[0], + Bond { owner: 1, amount: 30, liquidity_token: 1u32 } + ); + assert_ok!(Stake::schedule_candidate_bond_less(Origin::signed(1), 10)); + roll_to(10); + assert_ok!(Stake::execute_candidate_bond_request(Origin::signed(1), 1, None)); + assert_eq!( + Stake::candidate_pool().0[0], + Bond { owner: 1, amount: 20, liquidity_token: 1u32 } + ); + }); +} + +// CANCEL CANDIDATE BOND REQUEST +// 1. CANCEL CANDIDATE BOND MORE REQUEST + +#[test] +fn cancel_candidate_bond_more_emits_event() { + ExtBuilder::default() + .with_default_staking_token(vec![(1, 40)]) + .with_default_token_candidates(vec![(1, 30)]) + .build() + .execute_with(|| { + assert_ok!(Stake::schedule_candidate_bond_more(Origin::signed(1), 10, None)); + assert_ok!(Stake::cancel_candidate_bond_request(Origin::signed(1))); + assert_last_event!(MetaEvent::Stake(Event::CancelledCandidateBondChange( + 1, + CandidateBondRequest { + amount: 10, + change: CandidateBondChange::Increase, + when_executable: 2, + }, + ))); + }); +} + +#[test] +fn cancel_candidate_bond_more_updates_candidate_state() { + ExtBuilder::default() + .with_default_staking_token(vec![(1, 40)]) + .with_default_token_candidates(vec![(1, 30)]) + .build() + .execute_with(|| { + assert_ok!(Stake::schedule_candidate_bond_more(Origin::signed(1), 10, None)); + assert_ok!(Stake::cancel_candidate_bond_request(Origin::signed(1))); + assert!(Stake::candidate_state(&1).unwrap().request.is_none()); + }); +} + +#[test] +fn only_candidate_can_cancel_candidate_bond_more_request() { + ExtBuilder::default() + .with_default_staking_token(vec![(1, 40)]) + .with_default_token_candidates(vec![(1, 30)]) + .build() + .execute_with(|| { + assert_ok!(Stake::schedule_candidate_bond_more(Origin::signed(1), 10, None)); + assert_noop!( + Stake::cancel_candidate_bond_request(Origin::signed(2)), + Error::::CandidateDNE + ); + }); +} + +// 2. CANCEL CANDIDATE BOND LESS REQUEST + +#[test] +fn cancel_candidate_bond_less_emits_event() { + ExtBuilder::default() + .with_default_staking_token(vec![(1, 30)]) + .with_default_token_candidates(vec![(1, 30)]) + .build() + .execute_with(|| { + assert_ok!(Stake::schedule_candidate_bond_less(Origin::signed(1), 10)); + assert_ok!(Stake::cancel_candidate_bond_request(Origin::signed(1))); + assert_last_event!(MetaEvent::Stake(Event::CancelledCandidateBondChange( + 1, + CandidateBondRequest { + amount: 10, + change: CandidateBondChange::Decrease, + when_executable: 2, + }, + ))); + }); +} + +#[test] +fn cancel_candidate_bond_less_updates_candidate_state() { + ExtBuilder::default() + .with_default_staking_token(vec![(1, 30)]) + .with_default_token_candidates(vec![(1, 30)]) + .build() + .execute_with(|| { + assert_ok!(Stake::schedule_candidate_bond_less(Origin::signed(1), 10)); + assert_ok!(Stake::cancel_candidate_bond_request(Origin::signed(1))); + assert!(Stake::candidate_state(&1).unwrap().request.is_none()); + }); +} + +#[test] +fn only_candidate_can_cancel_candidate_bond_less_request() { + ExtBuilder::default() + .with_default_staking_token(vec![(1, 30)]) + .with_default_token_candidates(vec![(1, 30)]) + .build() + .execute_with(|| { + assert_ok!(Stake::schedule_candidate_bond_less(Origin::signed(1), 10)); + assert_noop!( + Stake::cancel_candidate_bond_request(Origin::signed(2)), + Error::::CandidateDNE + ); + }); +} + +// NOMINATE + +#[test] +fn delegate_event_emits_correctly() { + ExtBuilder::default() + .with_default_staking_token(vec![(1, 30), (2, 10)]) + .with_default_token_candidates(vec![(1, 30)]) + .build() + .execute_with(|| { + assert_ok!(Stake::delegate(Origin::signed(2), 1, 10, None, 0, 0)); + assert_last_event!(MetaEvent::Stake(Event::Delegation( + 2, + 10, + 1, + DelegatorAdded::AddedToTop { new_total: 40 }, + ))); + }); +} + +#[test] +fn delegate_reserves_balance() { + ExtBuilder::default() + .with_default_staking_token(vec![(1, 30), (2, 10)]) + .with_default_token_candidates(vec![(1, 30)]) + .build() + .execute_with(|| { + assert_eq!(StakeCurrency::reserved_balance(1, &2), 0); + assert_eq!(StakeCurrency::free_balance(1, &2), 10); + assert_ok!(Stake::delegate(Origin::signed(2), 1, 10, None, 0, 0)); + assert_eq!(StakeCurrency::reserved_balance(1, &2), 10); + assert_eq!(StakeCurrency::free_balance(1, &2), 0); + }); +} + +#[test] +fn delegate_updates_delegator_state() { + ExtBuilder::default() + .with_default_staking_token(vec![(1, 30), (2, 10)]) + .with_default_token_candidates(vec![(1, 30)]) + .build() + .execute_with(|| { + assert!(Stake::delegator_state(2).is_none()); + assert_ok!(Stake::delegate(Origin::signed(2), 1, 10, None, 0, 0)); + let delegator_state = Stake::delegator_state(2).expect("just delegated => exists"); + assert_eq!( + delegator_state.delegations.0[0], + Bond { owner: 1, amount: 10, liquidity_token: 1u32 } + ); + }); +} + +#[test] +fn delegate_updates_collator_state() { + ExtBuilder::default() + .with_default_staking_token(vec![(1, 30), (2, 10)]) + .with_default_token_candidates(vec![(1, 30)]) + .build() + .execute_with(|| { + let candidate_state = Stake::candidate_state(1).expect("registered in genesis"); + assert_eq!(candidate_state.total_backing, 30); + assert_eq!(candidate_state.total_counted, 30); + assert!(candidate_state.top_delegations.is_empty()); + assert_ok!(Stake::delegate(Origin::signed(2), 1, 10, None, 0, 0)); + let candidate_state = Stake::candidate_state(1).expect("just delegated => exists"); + assert_eq!(candidate_state.total_backing, 40); + assert_eq!(candidate_state.total_counted, 40); + assert_eq!( + candidate_state.top_delegations[0], + Bond { owner: 2, amount: 10, liquidity_token: 1u32 } + ); + }); +} + +#[test] +fn can_delegate_immediately_after_other_join_candidates() { + ExtBuilder::default() + .with_default_staking_token(vec![(1, 20), (2, 20)]) + .with_default_token_candidates(vec![(999, 10)]) + .build() + .execute_with(|| { + assert_ok!(Stake::join_candidates(Origin::signed(1), 20, 1u32, None, 10000u32, 1)); + assert_ok!(Stake::delegate(Origin::signed(2), 1, 20, None, 0, 0)); + }); +} + +#[test] +fn can_delegate_if_revoking() { + ExtBuilder::default() + .with_default_staking_token(vec![(1, 20), (2, 30), (3, 20), (4, 20)]) + .with_default_token_candidates(vec![(1, 20), (3, 20), (4, 20)]) + .with_delegations(vec![(2, 1, 10), (2, 3, 10)]) + .build() + .execute_with(|| { + assert_ok!(Stake::schedule_revoke_delegation(Origin::signed(2), 1)); + assert_ok!(Stake::delegate(Origin::signed(2), 4, 10, None, 0, 2)); + }); +} + +#[test] +fn cannot_delegate_if_leaving() { + ExtBuilder::default() + .with_default_staking_token(vec![(1, 20), (2, 20), (3, 20)]) + .with_default_token_candidates(vec![(1, 20), (3, 20)]) + .with_delegations(vec![(2, 1, 10)]) + .build() + .execute_with(|| { + assert_ok!(Stake::schedule_leave_delegators(Origin::signed(2))); + assert_noop!( + Stake::delegate(Origin::signed(2), 3, 10, None, 0, 1), + Error::::CannotDelegateIfLeaving + ); + }); +} + +#[test] +fn cannot_delegate_if_candidate() { + ExtBuilder::default() + .with_default_staking_token(vec![(1, 20), (2, 20)]) + .with_default_token_candidates(vec![(1, 20), (2, 20)]) + .build() + .execute_with(|| { + assert_noop!( + Stake::delegate(Origin::signed(2), 1, 10, None, 0, 0), + Error::::CandidateExists + ); + }); +} + +#[test] +fn cannot_delegate_if_already_delegated() { + ExtBuilder::default() + .with_default_staking_token(vec![(1, 20), (2, 30)]) + .with_default_token_candidates(vec![(1, 20)]) + .with_delegations(vec![(2, 1, 20)]) + .build() + .execute_with(|| { + assert_noop!( + Stake::delegate(Origin::signed(2), 1, 10, None, 1, 1), + Error::::AlreadyDelegatedCandidate + ); + }); +} + +#[test] +fn cannot_delegate_more_than_max_delegations() { + ExtBuilder::default() + .with_default_staking_token(vec![(1, 20), (2, 50), (3, 20), (4, 20), (5, 20), (6, 20)]) + .with_default_token_candidates(vec![(1, 20), (3, 20), (4, 20), (5, 20), (6, 20)]) + .with_delegations(vec![(2, 1, 10), (2, 3, 10), (2, 4, 10), (2, 5, 10)]) + .build() + .execute_with(|| { + assert_noop!( + Stake::delegate(Origin::signed(2), 6, 10, None, 0, 4), + Error::::ExceedMaxDelegationsPerDelegator, + ); + }); +} + +#[test] +fn sufficient_delegate_weight_hint_succeeds() { + ExtBuilder::default() + .with_default_staking_token(vec![ + (1, 20), + (2, 20), + (3, 20), + (4, 20), + (5, 20), + (6, 20), + (7, 20), + (8, 20), + (9, 20), + (10, 20), + ]) + .with_default_token_candidates(vec![(1, 20), (2, 20)]) + .with_delegations(vec![(3, 1, 10), (4, 1, 10), (5, 1, 10), (6, 1, 10)]) + .build() + .execute_with(|| { + let mut count = 4u32; + for i in 7..11 { + assert_ok!(Stake::delegate(Origin::signed(i), 1, 10, None, count, 0u32)); + count += 1u32; + } + let mut count = 0u32; + for i in 3..11 { + assert_ok!(Stake::delegate(Origin::signed(i), 2, 10, None, count, 1u32)); + count += 1u32; + } + }); +} + +#[test] +fn insufficient_delegate_weight_hint_fails() { + ExtBuilder::default() + .with_default_staking_token(vec![ + (1, 20), + (2, 20), + (3, 20), + (4, 20), + (5, 20), + (6, 20), + (7, 20), + (8, 20), + (9, 20), + (10, 20), + ]) + .with_default_token_candidates(vec![(1, 20), (2, 20)]) + .with_delegations(vec![(3, 1, 10), (4, 1, 10), (5, 1, 10), (6, 1, 10)]) + .build() + .execute_with(|| { + let mut count = 3u32; + for i in 7..11 { + assert_noop!( + Stake::delegate(Origin::signed(i), 1, 10, None, count, 0u32), + Error::::TooLowCandidateDelegationCountToDelegate + ); + } + // to set up for next error test + count = 4u32; + for i in 7..11 { + assert_ok!(Stake::delegate(Origin::signed(i), 1, 10, None, count, 0u32)); + count += 1u32; + } + count = 0u32; + for i in 3..11 { + assert_noop!( + Stake::delegate(Origin::signed(i), 2, 10, None, count, 0u32), + Error::::TooLowDelegationCountToDelegate + ); + count += 1u32; + } + }); +} + +// SCHEDULE LEAVE DELEGATORS + +#[test] +fn schedule_leave_delegators_event_emits_correctly() { + ExtBuilder::default() + .with_default_staking_token(vec![(1, 30), (2, 10)]) + .with_default_token_candidates(vec![(1, 30)]) + .with_delegations(vec![(2, 1, 10)]) + .build() + .execute_with(|| { + assert_ok!(Stake::schedule_leave_delegators(Origin::signed(2))); + assert_last_event!(MetaEvent::Stake(Event::DelegatorExitScheduled(0, 2, 2))); + }); +} + +#[test] +fn cannot_schedule_leave_delegators_if_already_leaving() { + ExtBuilder::default() + .with_default_staking_token(vec![(1, 30), (2, 10)]) + .with_default_token_candidates(vec![(1, 30)]) + .with_delegations(vec![(2, 1, 10)]) + .build() + .execute_with(|| { + assert_ok!(Stake::schedule_leave_delegators(Origin::signed(2))); + assert_noop!( + Stake::schedule_leave_delegators(Origin::signed(2)), + Error::::DelegatorAlreadyLeaving + ); + }); +} + +#[test] +fn cannot_schedule_leave_delegators_if_not_delegator() { + ExtBuilder::default() + .with_default_staking_token(vec![(1, 30), (2, 10)]) + .with_default_token_candidates(vec![(1, 30)]) + .build() + .execute_with(|| { + assert_noop!( + Stake::schedule_leave_delegators(Origin::signed(2)), + Error::::DelegatorDNE + ); + }); +} + +// EXECUTE LEAVE DELEGATORS + +#[test] +fn execute_leave_delegators_event_emits_correctly() { + ExtBuilder::default() + .with_default_staking_token(vec![(1, 30), (2, 10)]) + .with_default_token_candidates(vec![(1, 30)]) + .with_delegations(vec![(2, 1, 10)]) + .build() + .execute_with(|| { + assert_ok!(Stake::schedule_leave_delegators(Origin::signed(2))); + roll_to(10); + assert_ok!(Stake::execute_leave_delegators(Origin::signed(2), 2, 1)); + assert_event_emitted!(Event::DelegatorLeft(2, 10)); + }); +} + +#[test] +fn execute_leave_delegators_unreserves_balance() { + ExtBuilder::default() + .with_default_staking_token(vec![(1, 30), (2, 10)]) + .with_default_token_candidates(vec![(1, 30)]) + .with_delegations(vec![(2, 1, 10)]) + .build() + .execute_with(|| { + assert_eq!(StakeCurrency::reserved_balance(1, &2), 10); + assert_eq!(StakeCurrency::free_balance(1, &2), 0); + assert_ok!(Stake::schedule_leave_delegators(Origin::signed(2))); + roll_to(10); + assert_ok!(Stake::execute_leave_delegators(Origin::signed(2), 2, 1)); + assert_eq!(StakeCurrency::reserved_balance(1, &2), 0); + assert_eq!(StakeCurrency::free_balance(1, &2), 10); + }); +} + +#[test] +fn execute_leave_delegators_decreases_total_staked() { + ExtBuilder::default() + .with_default_staking_token(vec![(1, 30), (2, 10)]) + .with_default_token_candidates(vec![(1, 30)]) + .with_delegations(vec![(2, 1, 10)]) + .build() + .execute_with(|| { + assert_eq!(Stake::total(1u32), 40); + assert_ok!(Stake::schedule_leave_delegators(Origin::signed(2))); + roll_to(10); + assert_ok!(Stake::execute_leave_delegators(Origin::signed(2), 2, 1)); + assert_eq!(Stake::total(1u32), 30); + }); +} + +#[test] +fn execute_leave_delegators_removes_delegator_state() { + ExtBuilder::default() + .with_default_staking_token(vec![(1, 30), (2, 10)]) + .with_default_token_candidates(vec![(1, 30)]) + .with_delegations(vec![(2, 1, 10)]) + .build() + .execute_with(|| { + assert!(Stake::delegator_state(2).is_some()); + assert_ok!(Stake::schedule_leave_delegators(Origin::signed(2))); + roll_to(10); + assert_ok!(Stake::execute_leave_delegators(Origin::signed(2), 2, 1)); + assert!(Stake::delegator_state(2).is_none()); + }); +} + +#[test] +fn execute_leave_delegators_removes_delegations_from_collator_state() { + ExtBuilder::default() + .with_default_staking_token(vec![(1, 100), (2, 20), (3, 20), (4, 20), (5, 20)]) + .with_default_token_candidates(vec![(2, 20), (3, 20), (4, 20), (5, 20)]) + .with_delegations(vec![(1, 2, 10), (1, 3, 10), (1, 4, 10), (1, 5, 10)]) + .build() + .execute_with(|| { + for i in 2..6 { + let candidate_state = + Stake::candidate_state(i).expect("initialized in ext builder"); + assert_eq!( + candidate_state.top_delegations[0], + Bond { owner: 1, amount: 10, liquidity_token: 1u32 } + ); + assert_eq!(candidate_state.delegators.0[0], 1); + assert_eq!(candidate_state.total_backing, 30); + } + assert_eq!(Stake::delegator_state(1).unwrap().delegations.0.len(), 4usize); + assert_ok!(Stake::schedule_leave_delegators(Origin::signed(1))); + roll_to(10); + assert_ok!(Stake::execute_leave_delegators(Origin::signed(1), 1, 10)); + for i in 2..6 { + let candidate_state = + Stake::candidate_state(i).expect("initialized in ext builder"); + assert!(candidate_state.top_delegations.is_empty()); + assert!(candidate_state.delegators.0.is_empty()); + assert_eq!(candidate_state.total_backing, 20); + } + }); +} + +#[test] +fn cannot_execute_leave_delegators_before_delay() { + ExtBuilder::default() + .with_default_staking_token(vec![(1, 30), (2, 10)]) + .with_default_token_candidates(vec![(1, 30)]) + .with_delegations(vec![(2, 1, 10)]) + .build() + .execute_with(|| { + assert_ok!(Stake::schedule_leave_delegators(Origin::signed(2))); + assert_noop!( + Stake::execute_leave_delegators(Origin::signed(2), 2, 1), + Error::::DelegatorCannotLeaveYet + ); + // can execute after delay + roll_to(10); + assert_ok!(Stake::execute_leave_delegators(Origin::signed(2), 2, 1)); + }); +} + +#[test] +fn insufficient_execute_leave_delegators_weight_hint_fails() { + ExtBuilder::default() + .with_default_staking_token(vec![(1, 20), (2, 20), (3, 20), (4, 20), (5, 20), (6, 20)]) + .with_default_token_candidates(vec![(1, 20)]) + .with_delegations(vec![(3, 1, 10), (4, 1, 10), (5, 1, 10), (6, 1, 10)]) + .build() + .execute_with(|| { + for i in 3..7 { + assert_ok!(Stake::schedule_leave_delegators(Origin::signed(i))); + } + roll_to(10); + for i in 3..7 { + assert_noop!( + Stake::execute_leave_delegators(Origin::signed(i), i, 0), + Error::::TooLowDelegationCountToLeaveDelegators + ); + } + }); +} + +#[test] +fn sufficient_execute_leave_delegators_weight_hint_succeeds() { + ExtBuilder::default() + .with_default_staking_token(vec![(1, 20), (2, 20), (3, 20), (4, 20), (5, 20), (6, 20)]) + .with_default_token_candidates(vec![(1, 20)]) + .with_delegations(vec![(3, 1, 10), (4, 1, 10), (5, 1, 10), (6, 1, 10)]) + .build() + .execute_with(|| { + for i in 3..7 { + assert_ok!(Stake::schedule_leave_delegators(Origin::signed(i))); + } + roll_to(10); + for i in 3..7 { + assert_ok!(Stake::execute_leave_delegators(Origin::signed(i), i, 1)); + } + }); +} + +// CANCEL LEAVE DELEGATORS + +#[test] +fn cancel_leave_delegators_emits_correct_event() { + ExtBuilder::default() + .with_default_staking_token(vec![(1, 30), (2, 10)]) + .with_default_token_candidates(vec![(1, 30)]) + .with_delegations(vec![(2, 1, 10)]) + .build() + .execute_with(|| { + assert_ok!(Stake::schedule_leave_delegators(Origin::signed(2))); + assert_ok!(Stake::cancel_leave_delegators(Origin::signed(2))); + assert_last_event!(MetaEvent::Stake(Event::DelegatorExitCancelled(2))); + }); +} + +#[test] +fn cancel_leave_delegators_updates_delegator_state() { + ExtBuilder::default() + .with_default_staking_token(vec![(1, 30), (2, 10)]) + .with_default_token_candidates(vec![(1, 30)]) + .with_delegations(vec![(2, 1, 10)]) + .build() + .execute_with(|| { + assert_ok!(Stake::schedule_leave_delegators(Origin::signed(2))); + assert_ok!(Stake::cancel_leave_delegators(Origin::signed(2))); + let delegator = Stake::delegator_state(&2).expect("just cancelled exit so exists"); + assert!(delegator.is_active()); + }); +} + +// SCHEDULE REVOKE DELEGATION + +#[test] +fn revoke_delegation_event_emits_correctly() { + ExtBuilder::default() + .with_default_staking_token(vec![(1, 30), (2, 20), (3, 30)]) + .with_default_token_candidates(vec![(1, 30), (3, 30)]) + .with_delegations(vec![(2, 1, 10), (2, 3, 10)]) + .build() + .execute_with(|| { + assert_ok!(Stake::schedule_revoke_delegation(Origin::signed(2), 1)); + assert_last_event!(MetaEvent::Stake(Event::DelegationRevocationScheduled(0, 2, 1, 2,))); + roll_to(10); + assert_ok!(Stake::execute_delegation_request(Origin::signed(2), 2, 1, None)); + assert_event_emitted!(Event::DelegatorLeftCandidate(2, 1, 10, 30)); + }); +} + +#[test] +fn can_revoke_delegation_if_revoking_another_delegation() { + ExtBuilder::default() + .with_default_staking_token(vec![(1, 30), (2, 20), (3, 20)]) + .with_default_token_candidates(vec![(1, 30), (3, 20)]) + .with_delegations(vec![(2, 1, 10), (2, 3, 10)]) + .build() + .execute_with(|| { + assert_ok!(Stake::schedule_revoke_delegation(Origin::signed(2), 1)); + // this is an exit implicitly because last delegation revoked + assert_ok!(Stake::schedule_revoke_delegation(Origin::signed(2), 3)); + }); +} + +#[test] +fn can_revoke_if_leaving() { + ExtBuilder::default() + .with_default_staking_token(vec![(1, 30), (2, 20), (3, 20)]) + .with_default_token_candidates(vec![(1, 30), (3, 20)]) + .with_delegations(vec![(2, 1, 10), (2, 3, 10)]) + .build() + .execute_with(|| { + assert_ok!(Stake::schedule_leave_delegators(Origin::signed(2))); + assert_ok!(Stake::schedule_revoke_delegation(Origin::signed(2), 3)); + }); +} + +#[test] +fn cannot_revoke_delegation_if_not_delegator() { + ExtBuilder::default().build().execute_with(|| { + assert_noop!( + Stake::schedule_revoke_delegation(Origin::signed(2), 1), + Error::::DelegatorDNE + ); + }); +} + +#[test] +fn cannot_revoke_delegation_that_dne() { + ExtBuilder::default() + .with_default_staking_token(vec![(1, 30), (2, 10)]) + .with_default_token_candidates(vec![(1, 30)]) + .with_delegations(vec![(2, 1, 10)]) + .build() + .execute_with(|| { + assert_noop!( + Stake::schedule_revoke_delegation(Origin::signed(2), 3), + Error::::DelegationDNE + ); + }); +} + +#[test] +// See `cannot_execute_revoke_delegation_below_min_delegator_stake` for where the "must be above +// MinDelegatorStk" rule is now enforced. +fn can_schedule_revoke_delegation_below_min_delegator_stake() { + ExtBuilder::default() + .with_default_staking_token(vec![(1, 20), (2, 8), (3, 20)]) + .with_default_token_candidates(vec![(1, 20), (3, 20)]) + .with_delegations(vec![(2, 1, 5), (2, 3, 3)]) + .build() + .execute_with(|| { + assert_ok!(Stake::schedule_revoke_delegation(Origin::signed(2), 1)); + }); +} + +// SCHEDULE DELEGATOR BOND MORE + +#[test] +fn delegator_bond_more_event_emits_correctly() { + ExtBuilder::default() + .with_default_staking_token(vec![(1, 30), (2, 15)]) + .with_default_token_candidates(vec![(1, 30)]) + .with_delegations(vec![(2, 1, 10)]) + .build() + .execute_with(|| { + assert_ok!(Stake::schedule_delegator_bond_more(Origin::signed(2), 1, 5, None)); + assert_last_event!(MetaEvent::Stake(Event::DelegationIncreaseScheduled(2, 1, 5, 2,))); + }); +} + +#[test] +fn delegator_bond_more_updates_delegator_state() { + ExtBuilder::default() + .with_default_staking_token(vec![(1, 30), (2, 15)]) + .with_default_token_candidates(vec![(1, 30)]) + .with_delegations(vec![(2, 1, 10)]) + .build() + .execute_with(|| { + assert_ok!(Stake::schedule_delegator_bond_more(Origin::signed(2), 1, 5, None)); + let state = Stake::delegator_state(&2).expect("just request bonded less so exists"); + assert_eq!( + state.requests().get(&1), + Some(&DelegationRequest { + collator: 1, + amount: 5, + when_executable: 2, + action: DelegationChange::Increase + }) + ); + }); +} + +#[test] +fn can_delegator_bond_more_if_leaving() { + ExtBuilder::default() + .with_default_staking_token(vec![(1, 30), (2, 15)]) + .with_default_token_candidates(vec![(1, 30)]) + .with_delegations(vec![(2, 1, 10)]) + .build() + .execute_with(|| { + assert_ok!(Stake::schedule_leave_delegators(Origin::signed(2))); + assert_ok!(Stake::schedule_delegator_bond_more(Origin::signed(2), 1, 5, None)); + }); +} + +#[test] +fn cannot_delegator_bond_more_if_revoking() { + ExtBuilder::default() + .with_default_staking_token(vec![(1, 30), (2, 25), (3, 20)]) + .with_default_token_candidates(vec![(1, 30), (3, 20)]) + .with_delegations(vec![(2, 1, 10), (2, 3, 10)]) + .build() + .execute_with(|| { + assert_ok!(Stake::schedule_revoke_delegation(Origin::signed(2), 1)); + assert_noop!( + Stake::schedule_delegator_bond_more(Origin::signed(2), 1, 5, None), + Error::::PendingDelegationRequestAlreadyExists + ); + }); +} + +#[test] +fn cannot_delegator_bond_more_if_not_delegator() { + ExtBuilder::default().build().execute_with(|| { + assert_noop!( + Stake::schedule_delegator_bond_more(Origin::signed(2), 1, 5, None), + Error::::DelegatorDNE + ); + }); +} + +#[test] +fn cannot_delegator_bond_more_if_candidate_dne() { + ExtBuilder::default() + .with_default_staking_token(vec![(1, 30), (2, 10)]) + .with_default_token_candidates(vec![(1, 30)]) + .with_delegations(vec![(2, 1, 10)]) + .build() + .execute_with(|| { + assert_noop!( + Stake::schedule_delegator_bond_more(Origin::signed(2), 3, 5, None), + Error::::DelegationDNE + ); + }); +} + +#[test] +fn cannot_delegator_bond_more_if_delegation_dne() { + ExtBuilder::default() + .with_default_staking_token(vec![(1, 30), (2, 10), (3, 30)]) + .with_default_token_candidates(vec![(1, 30), (3, 30)]) + .with_delegations(vec![(2, 1, 10)]) + .build() + .execute_with(|| { + assert_noop!( + Stake::schedule_delegator_bond_more(Origin::signed(2), 3, 5, None), + Error::::DelegationDNE + ); + }); +} + +#[test] +fn cannot_delegator_bond_more_if_insufficient_balance() { + ExtBuilder::default() + .with_default_staking_token(vec![(1, 30), (2, 10)]) + .with_default_token_candidates(vec![(1, 30)]) + .with_delegations(vec![(2, 1, 10)]) + .build() + .execute_with(|| { + assert_noop!( + Stake::schedule_delegator_bond_more(Origin::signed(2), 1, 5, None), + Error::::InsufficientBalance + ); + }); +} + +// DELEGATOR BOND LESS + +#[test] +fn delegator_bond_less_event_emits_correctly() { + ExtBuilder::default() + .with_default_staking_token(vec![(1, 30), (2, 10)]) + .with_default_token_candidates(vec![(1, 30)]) + .with_delegations(vec![(2, 1, 10)]) + .build() + .execute_with(|| { + assert_ok!(Stake::schedule_delegator_bond_less(Origin::signed(2), 1, 5)); + assert_last_event!(MetaEvent::Stake(Event::DelegationDecreaseScheduled(2, 1, 5, 2,))); + }); +} + +#[test] +fn delegator_bond_less_updates_delegator_state() { + ExtBuilder::default() + .with_default_staking_token(vec![(1, 30), (2, 10)]) + .with_default_token_candidates(vec![(1, 30)]) + .with_delegations(vec![(2, 1, 10)]) + .build() + .execute_with(|| { + assert_ok!(Stake::schedule_delegator_bond_less(Origin::signed(2), 1, 5)); + let state = Stake::delegator_state(&2).expect("just request bonded less so exists"); + assert_eq!( + state.requests().get(&1), + Some(&DelegationRequest { + collator: 1, + amount: 5, + when_executable: 2, + action: DelegationChange::Decrease + }) + ); + }); +} + +#[test] +fn can_delegator_bond_less_if_leaving() { + ExtBuilder::default() + .with_default_staking_token(vec![(1, 30), (2, 15)]) + .with_default_token_candidates(vec![(1, 30)]) + .with_delegations(vec![(2, 1, 10)]) + .build() + .execute_with(|| { + assert_ok!(Stake::schedule_leave_delegators(Origin::signed(2))); + assert_ok!(Stake::schedule_delegator_bond_less(Origin::signed(2), 1, 1)); + }); +} + +#[test] +fn cannot_delegator_bond_less_if_revoking() { + ExtBuilder::default() + .with_default_staking_token(vec![(1, 30), (2, 25), (3, 20)]) + .with_default_token_candidates(vec![(1, 30), (3, 20)]) + .with_delegations(vec![(2, 1, 10), (2, 3, 10)]) + .build() + .execute_with(|| { + assert_ok!(Stake::schedule_revoke_delegation(Origin::signed(2), 1)); + assert_noop!( + Stake::schedule_delegator_bond_less(Origin::signed(2), 1, 1), + Error::::PendingDelegationRequestAlreadyExists + ); + }); +} + +#[test] +fn cannot_delegator_bond_less_if_not_delegator() { + ExtBuilder::default().build().execute_with(|| { + assert_noop!( + Stake::schedule_delegator_bond_less(Origin::signed(2), 1, 5), + Error::::DelegatorDNE + ); + }); +} + +#[test] +fn cannot_delegator_bond_less_if_candidate_dne() { + ExtBuilder::default() + .with_default_staking_token(vec![(1, 30), (2, 10)]) + .with_default_token_candidates(vec![(1, 30)]) + .with_delegations(vec![(2, 1, 10)]) + .build() + .execute_with(|| { + assert_noop!( + Stake::schedule_delegator_bond_less(Origin::signed(2), 3, 5), + Error::::DelegationDNE + ); + }); +} + +#[test] +fn cannot_delegator_bond_less_if_delegation_dne() { + ExtBuilder::default() + .with_default_staking_token(vec![(1, 30), (2, 10), (3, 30)]) + .with_default_token_candidates(vec![(1, 30), (3, 30)]) + .with_delegations(vec![(2, 1, 10)]) + .build() + .execute_with(|| { + assert_noop!( + Stake::schedule_delegator_bond_less(Origin::signed(2), 3, 5), + Error::::DelegationDNE + ); + }); +} + +#[test] +fn cannot_delegator_bond_less_more_than_total_delegation() { + ExtBuilder::default() + .with_default_staking_token(vec![(1, 30), (2, 10)]) + .with_default_token_candidates(vec![(1, 30)]) + .with_delegations(vec![(2, 1, 10)]) + .build() + .execute_with(|| { + assert_noop!( + Stake::schedule_delegator_bond_less(Origin::signed(2), 1, 11), + Error::::DelegationBelowMin + ); + }); +} + +#[test] +fn cannot_delegator_bond_less_below_min_delegation() { + ExtBuilder::default() + .with_default_staking_token(vec![(1, 30), (2, 20), (3, 30)]) + .with_default_token_candidates(vec![(1, 30), (3, 30)]) + .with_delegations(vec![(2, 1, 10), (2, 3, 10)]) + .build() + .execute_with(|| { + assert_noop!( + Stake::schedule_delegator_bond_less(Origin::signed(2), 1, 8), + Error::::DelegationBelowMin + ); + }); +} + +// EXECUTE PENDING DELEGATION REQUEST + +// 1. REVOKE DELEGATION + +#[test] +fn execute_revoke_delegation_emits_exit_event_if_exit_happens() { + // last delegation is revocation + ExtBuilder::default() + .with_default_staking_token(vec![(1, 30), (2, 10)]) + .with_default_token_candidates(vec![(1, 30)]) + .with_delegations(vec![(2, 1, 10)]) + .build() + .execute_with(|| { + assert_ok!(Stake::schedule_revoke_delegation(Origin::signed(2), 1)); + roll_to(10); + assert_ok!(Stake::execute_delegation_request(Origin::signed(2), 2, 1, None)); + assert_event_emitted!(Event::DelegatorLeftCandidate(2, 1, 10, 30)); + assert_event_emitted!(Event::DelegatorLeft(2, 10)); + }); +} + +#[test] +fn revoke_delegation_executes_exit_if_last_delegation() { + // last delegation is revocation + ExtBuilder::default() + .with_default_staking_token(vec![(1, 30), (2, 10)]) + .with_default_token_candidates(vec![(1, 30)]) + .with_delegations(vec![(2, 1, 10)]) + .build() + .execute_with(|| { + assert_ok!(Stake::schedule_revoke_delegation(Origin::signed(2), 1)); + roll_to(10); + assert_ok!(Stake::execute_delegation_request(Origin::signed(2), 2, 1, None)); + assert_event_emitted!(Event::DelegatorLeftCandidate(2, 1, 10, 30)); + assert_event_emitted!(Event::DelegatorLeft(2, 10)); + }); +} + +#[test] +fn execute_revoke_delegation_emits_correct_event() { + ExtBuilder::default() + .with_default_staking_token(vec![(1, 30), (2, 20), (3, 30)]) + .with_default_token_candidates(vec![(1, 30), (3, 30)]) + .with_delegations(vec![(2, 1, 10), (2, 3, 10)]) + .build() + .execute_with(|| { + assert_ok!(Stake::schedule_revoke_delegation(Origin::signed(2), 1)); + roll_to(10); + assert_ok!(Stake::execute_delegation_request(Origin::signed(2), 2, 1, None)); + assert_event_emitted!(Event::DelegatorLeftCandidate(2, 1, 10, 30)); + }); +} + +#[test] +fn execute_revoke_delegation_unreserves_balance() { + ExtBuilder::default() + .with_default_staking_token(vec![(1, 30), (2, 10)]) + .with_default_token_candidates(vec![(1, 30)]) + .with_delegations(vec![(2, 1, 10)]) + .build() + .execute_with(|| { + assert_eq!(StakeCurrency::reserved_balance(1, &2), 10); + assert_eq!(StakeCurrency::free_balance(1, &2), 0); + assert_ok!(Stake::schedule_revoke_delegation(Origin::signed(2), 1)); + roll_to(10); + assert_ok!(Stake::execute_delegation_request(Origin::signed(2), 2, 1, None)); + assert_eq!(StakeCurrency::reserved_balance(1, &2), 0); + assert_eq!(StakeCurrency::free_balance(1, &2), 10); + }); +} + +#[test] +fn execute_revoke_delegation_adds_revocation_to_delegator_state() { + ExtBuilder::default() + .with_default_staking_token(vec![(1, 30), (2, 20), (3, 20)]) + .with_default_token_candidates(vec![(1, 30), (3, 20)]) + .with_delegations(vec![(2, 1, 10), (2, 3, 10)]) + .build() + .execute_with(|| { + assert!(Stake::delegator_state(2).expect("exists").requests.requests.get(&1).is_none()); + assert_ok!(Stake::schedule_revoke_delegation(Origin::signed(2), 1)); + assert!(Stake::delegator_state(2).expect("exists").requests.requests.get(&1).is_some()); + }); +} + +#[test] +fn execute_revoke_delegation_removes_revocation_from_delegator_state_upon_execution() { + ExtBuilder::default() + .with_default_staking_token(vec![(1, 30), (2, 20), (3, 20)]) + .with_default_token_candidates(vec![(1, 30), (3, 20)]) + .with_delegations(vec![(2, 1, 10), (2, 3, 10)]) + .build() + .execute_with(|| { + assert_ok!(Stake::schedule_revoke_delegation(Origin::signed(2), 1)); + roll_to(10); + assert_ok!(Stake::execute_delegation_request(Origin::signed(2), 2, 1, None)); + assert!(Stake::delegator_state(2).expect("exists").requests.requests.get(&1).is_none()); + }); +} + +#[test] +fn execute_revoke_delegation_decreases_total_staked() { + ExtBuilder::default() + .with_default_staking_token(vec![(1, 30), (2, 10)]) + .with_default_token_candidates(vec![(1, 30)]) + .with_delegations(vec![(2, 1, 10)]) + .build() + .execute_with(|| { + assert_eq!(Stake::total(1u32), 40); + assert_ok!(Stake::schedule_revoke_delegation(Origin::signed(2), 1)); + roll_to(10); + assert_ok!(Stake::execute_delegation_request(Origin::signed(2), 2, 1, None)); + assert_eq!(Stake::total(1u32), 30); + }); +} + +#[test] +fn execute_revoke_delegation_for_last_delegation_removes_delegator_state() { + ExtBuilder::default() + .with_default_staking_token(vec![(1, 30), (2, 10)]) + .with_default_token_candidates(vec![(1, 30)]) + .with_delegations(vec![(2, 1, 10)]) + .build() + .execute_with(|| { + assert!(Stake::delegator_state(2).is_some()); + assert_ok!(Stake::schedule_revoke_delegation(Origin::signed(2), 1)); + roll_to(10); + // this will be confusing for people + // if status is leaving, then execute_delegation_request works if last delegation + assert_ok!(Stake::execute_delegation_request(Origin::signed(2), 2, 1, None)); + assert!(Stake::delegator_state(2).is_none()); + }); +} + +#[test] +fn execute_revoke_delegation_removes_delegation_from_candidate_state() { + ExtBuilder::default() + .with_default_staking_token(vec![(1, 30), (2, 10)]) + .with_default_token_candidates(vec![(1, 30)]) + .with_delegations(vec![(2, 1, 10)]) + .build() + .execute_with(|| { + assert_eq!(Stake::candidate_state(1).expect("exists").delegators.0.len(), 1usize); + assert_ok!(Stake::schedule_revoke_delegation(Origin::signed(2), 1)); + roll_to(10); + assert_ok!(Stake::execute_delegation_request(Origin::signed(2), 2, 1, None)); + assert!(Stake::candidate_state(1).expect("exists").delegators.0.is_empty()); + }); +} + +#[test] +fn can_execute_revoke_delegation_for_leaving_candidate() { + ExtBuilder::default() + .with_default_staking_token(vec![(1, 30), (2, 10)]) + .with_default_token_candidates(vec![(1, 30)]) + .with_delegations(vec![(2, 1, 10)]) + .build() + .execute_with(|| { + assert_ok!(Stake::schedule_leave_candidates(Origin::signed(1), 1)); + assert_ok!(Stake::schedule_revoke_delegation(Origin::signed(2), 1)); + roll_to(10); + // can execute delegation request for leaving candidate + assert_ok!(Stake::execute_delegation_request(Origin::signed(2), 2, 1, None)); + }); +} + +#[test] +fn can_execute_leave_candidates_if_revoking_candidate() { + ExtBuilder::default() + .with_default_staking_token(vec![(1, 30), (2, 10)]) + .with_default_token_candidates(vec![(1, 30)]) + .with_delegations(vec![(2, 1, 10)]) + .build() + .execute_with(|| { + assert_ok!(Stake::schedule_leave_candidates(Origin::signed(1), 1)); + assert_ok!(Stake::schedule_revoke_delegation(Origin::signed(2), 1)); + roll_to(10); + // revocation executes during execute leave candidates (callable by anyone) + assert_ok!(Stake::execute_leave_candidates(Origin::signed(1), 1, 10000u32)); + assert!(!Stake::is_delegator(&2)); + assert_eq!(StakeCurrency::reserved_balance(1, &2), 0); + assert_eq!(StakeCurrency::free_balance(1, &2), 10); + }); +} + +#[test] +fn delegator_bond_more_after_revoke_delegation_does_not_effect_exit() { + ExtBuilder::default() + .with_default_staking_token(vec![(1, 30), (2, 30), (3, 30)]) + .with_default_token_candidates(vec![(1, 30), (3, 30)]) + .with_delegations(vec![(2, 1, 10), (2, 3, 10)]) + .build() + .execute_with(|| { + assert_ok!(Stake::schedule_revoke_delegation(Origin::signed(2), 1)); + assert_noop!( + Stake::schedule_delegator_bond_more(Origin::signed(2), 1, 10, None), + Error::::PendingDelegationRequestAlreadyExists + ); + assert_ok!(Stake::schedule_delegator_bond_more(Origin::signed(2), 3, 10, None)); + roll_to(10); + assert_ok!(Stake::execute_delegation_request(Origin::signed(2), 2, 1, None)); + assert_ok!(Stake::execute_delegation_request(Origin::signed(2), 2, 3, None)); + assert!(Stake::is_delegator(&2)); + assert_eq!(StakeCurrency::reserved_balance(1, &2), 20); + assert_eq!(StakeCurrency::free_balance(1, &2), 10); + }); +} + +#[test] +fn delegator_bond_less_after_revoke_delegation_does_not_effect_exit() { + ExtBuilder::default() + .with_default_staking_token(vec![(1, 30), (2, 30), (3, 30)]) + .with_default_token_candidates(vec![(1, 30), (3, 30)]) + .with_delegations(vec![(2, 1, 10), (2, 3, 10)]) + .build() + .execute_with(|| { + assert_ok!(Stake::schedule_revoke_delegation(Origin::signed(2), 1)); + assert_last_event!(MetaEvent::Stake(Event::DelegationRevocationScheduled(0, 2, 1, 2,))); + assert_noop!( + Stake::schedule_delegator_bond_less(Origin::signed(2), 1, 2), + Error::::PendingDelegationRequestAlreadyExists + ); + assert_ok!(Stake::schedule_delegator_bond_less(Origin::signed(2), 3, 2)); + roll_to(10); + assert_ok!(Stake::execute_delegation_request(Origin::signed(2), 2, 1, None)); + assert_ok!(Stake::execute_delegation_request(Origin::signed(2), 2, 3, None)); + assert_last_event!(MetaEvent::Stake(Event::DelegationDecreased(2, 3, 2, true))); + assert!(Stake::is_delegator(&2)); + assert_eq!(StakeCurrency::reserved_balance(1, &2), 8); + assert_eq!(StakeCurrency::free_balance(1, &2), 22); + }); +} + +// 2. EXECUTE BOND MORE + +#[test] +fn execute_delegator_bond_more_reserves_balance() { + ExtBuilder::default() + .with_default_staking_token(vec![(1, 30), (2, 15)]) + .with_default_token_candidates(vec![(1, 30)]) + .with_delegations(vec![(2, 1, 10)]) + .build() + .execute_with(|| { + assert_eq!(StakeCurrency::reserved_balance(1, &2), 10); + assert_eq!(StakeCurrency::free_balance(1, &2), 5); + assert_ok!(Stake::schedule_delegator_bond_more(Origin::signed(2), 1, 5, None)); + roll_to(10); + assert_ok!(Stake::execute_delegation_request(Origin::signed(2), 2, 1, None)); + assert_eq!(StakeCurrency::reserved_balance(1, &2), 15); + assert_eq!(StakeCurrency::free_balance(1, &2), 0); + }); +} + +#[test] +fn execute_delegator_bond_more_increases_total_staked() { + ExtBuilder::default() + .with_default_staking_token(vec![(1, 30), (2, 15)]) + .with_default_token_candidates(vec![(1, 30)]) + .with_delegations(vec![(2, 1, 10)]) + .build() + .execute_with(|| { + assert_eq!(Stake::total(1u32), 40); + assert_ok!(Stake::schedule_delegator_bond_more(Origin::signed(2), 1, 5, None)); + roll_to(10); + assert_ok!(Stake::execute_delegation_request(Origin::signed(2), 2, 1, None)); + assert_eq!(Stake::total(1u32), 45); + }); +} + +#[test] +fn execute_delegator_bond_more_updates_delegator_state() { + ExtBuilder::default() + .with_default_staking_token(vec![(1, 30), (2, 15)]) + .with_default_token_candidates(vec![(1, 30)]) + .with_delegations(vec![(2, 1, 10)]) + .build() + .execute_with(|| { + assert_ok!(Stake::schedule_delegator_bond_more(Origin::signed(2), 1, 5, None)); + roll_to(10); + assert_ok!(Stake::execute_delegation_request(Origin::signed(2), 2, 1, None)); + }); +} + +#[test] +fn execute_delegator_bond_more_updates_candidate_state_top_delegations() { + ExtBuilder::default() + .with_default_staking_token(vec![(1, 30), (2, 15)]) + .with_default_token_candidates(vec![(1, 30)]) + .with_delegations(vec![(2, 1, 10)]) + .build() + .execute_with(|| { + assert_eq!( + Stake::candidate_state(1).expect("exists").top_delegations[0], + Bond { owner: 2, amount: 10, liquidity_token: 1u32 } + ); + assert_ok!(Stake::schedule_delegator_bond_more(Origin::signed(2), 1, 5, None)); + roll_to(10); + assert_ok!(Stake::execute_delegation_request(Origin::signed(2), 2, 1, None)); + assert_eq!( + Stake::candidate_state(1).expect("exists").top_delegations[0], + Bond { owner: 2, amount: 15, liquidity_token: 1u32 } + ); + }); +} + +#[test] +fn execute_delegator_bond_more_updates_candidate_state_bottom_delegations() { + ExtBuilder::default() + .with_default_staking_token(vec![(1, 30), (2, 20), (3, 20), (4, 20), (5, 20), (6, 20)]) + .with_default_token_candidates(vec![(1, 30)]) + .with_delegations(vec![(2, 1, 10), (3, 1, 20), (4, 1, 20), (5, 1, 20), (6, 1, 20)]) + .build() + .execute_with(|| { + assert_eq!( + Stake::candidate_state(1).expect("exists").bottom_delegations[0], + Bond { owner: 2, amount: 10, liquidity_token: 1u32 } + ); + assert_ok!(Stake::schedule_delegator_bond_more(Origin::signed(2), 1, 5, None)); + roll_to(10); + assert_ok!(Stake::execute_delegation_request(Origin::signed(2), 2, 1, None)); + assert_last_event!(MetaEvent::Stake(Event::DelegationIncreased(2, 1, 5, false))); + assert_eq!( + Stake::candidate_state(1).expect("exists").bottom_delegations[0], + Bond { owner: 2, amount: 15, liquidity_token: 1u32 } + ); + }); +} + +#[test] +fn execute_delegator_bond_more_increases_total() { + ExtBuilder::default() + .with_default_staking_token(vec![(1, 30), (2, 15)]) + .with_default_token_candidates(vec![(1, 30)]) + .with_delegations(vec![(2, 1, 10)]) + .build() + .execute_with(|| { + assert_eq!(Stake::total(1u32), 40); + assert_ok!(Stake::schedule_delegator_bond_more(Origin::signed(2), 1, 5, None)); + roll_to(10); + assert_ok!(Stake::execute_delegation_request(Origin::signed(2), 2, 1, None)); + assert_eq!(Stake::total(1u32), 45); + }); +} + +#[test] +fn can_execute_delegator_bond_more_for_leaving_candidate() { + ExtBuilder::default() + .with_default_staking_token(vec![(1, 30), (2, 15)]) + .with_default_token_candidates(vec![(1, 30)]) + .with_delegations(vec![(2, 1, 10)]) + .build() + .execute_with(|| { + assert_ok!(Stake::schedule_leave_candidates(Origin::signed(1), 1)); + assert_ok!(Stake::schedule_delegator_bond_more(Origin::signed(2), 1, 5, None)); + roll_to(10); + // can execute bond more delegation request for leaving candidate + assert_ok!(Stake::execute_delegation_request(Origin::signed(2), 2, 1, None)); + }); +} + +// 3, EXECUTE BOND LESS + +#[test] +fn execute_delegator_bond_less_unreserves_balance() { + ExtBuilder::default() + .with_default_staking_token(vec![(1, 30), (2, 10)]) + .with_default_token_candidates(vec![(1, 30)]) + .with_delegations(vec![(2, 1, 10)]) + .build() + .execute_with(|| { + assert_eq!(StakeCurrency::reserved_balance(1, &2), 10); + assert_eq!(StakeCurrency::free_balance(1, &2), 0); + assert_ok!(Stake::schedule_delegator_bond_less(Origin::signed(2), 1, 5)); + roll_to(10); + assert_ok!(Stake::execute_delegation_request(Origin::signed(2), 2, 1, None)); + assert_eq!(StakeCurrency::reserved_balance(1, &2), 5); + assert_eq!(StakeCurrency::free_balance(1, &2), 5); + }); +} + +#[test] +fn execute_delegator_bond_less_decreases_total_staked() { + ExtBuilder::default() + .with_default_staking_token(vec![(1, 30), (2, 10)]) + .with_default_token_candidates(vec![(1, 30)]) + .with_delegations(vec![(2, 1, 10)]) + .build() + .execute_with(|| { + assert_eq!(Stake::total(1u32), 40); + assert_ok!(Stake::schedule_delegator_bond_less(Origin::signed(2), 1, 5)); + roll_to(10); + assert_ok!(Stake::execute_delegation_request(Origin::signed(2), 2, 1, None)); + assert_eq!(Stake::total(1u32), 35); + }); +} + +#[test] +fn execute_delegator_bond_less_updates_delegator_state() { + ExtBuilder::default() + .with_default_staking_token(vec![(1, 30), (2, 15)]) + .with_default_token_candidates(vec![(1, 30)]) + .with_delegations(vec![(2, 1, 10)]) + .build() + .execute_with(|| { + assert_ok!(Stake::schedule_delegator_bond_less(Origin::signed(2), 1, 5)); + roll_to(10); + assert_ok!(Stake::execute_delegation_request(Origin::signed(2), 2, 1, None)); + }); +} + +#[test] +fn execute_delegator_bond_less_updates_candidate_state() { + ExtBuilder::default() + .with_default_staking_token(vec![(1, 30), (2, 15)]) + .with_default_token_candidates(vec![(1, 30)]) + .with_delegations(vec![(2, 1, 10)]) + .build() + .execute_with(|| { + assert_eq!( + Stake::candidate_state(1).expect("exists").top_delegations[0], + Bond { owner: 2, amount: 10, liquidity_token: 1u32 } + ); + assert_ok!(Stake::schedule_delegator_bond_less(Origin::signed(2), 1, 5)); + roll_to(10); + assert_ok!(Stake::execute_delegation_request(Origin::signed(2), 2, 1, None)); + assert_eq!( + Stake::candidate_state(1).expect("exists").top_delegations[0], + Bond { owner: 2, amount: 5, liquidity_token: 1u32 } + ); + }); +} + +#[test] +fn execute_delegator_bond_less_decreases_total() { + ExtBuilder::default() + .with_default_staking_token(vec![(1, 30), (2, 15)]) + .with_default_token_candidates(vec![(1, 30)]) + .with_delegations(vec![(2, 1, 10)]) + .build() + .execute_with(|| { + assert_eq!(Stake::total(1u32), 40); + assert_ok!(Stake::schedule_delegator_bond_less(Origin::signed(2), 1, 5)); + roll_to(10); + assert_ok!(Stake::execute_delegation_request(Origin::signed(2), 2, 1, None)); + assert_eq!(Stake::total(1u32), 35); + }); +} + +#[test] +fn execute_delegator_bond_less_updates_just_bottom_delegations() { + ExtBuilder::default() + .with_default_staking_token(vec![(1, 20), (2, 10), (3, 11), (4, 12), (5, 14), (6, 15)]) + .with_default_token_candidates(vec![(1, 20)]) + .with_delegations(vec![(2, 1, 10), (3, 1, 11), (4, 1, 12), (5, 1, 14), (6, 1, 15)]) + .build() + .execute_with(|| { + let pre_call_collator_state = + Stake::candidate_state(&1).expect("delegated by all so exists"); + assert_ok!(Stake::schedule_delegator_bond_less(Origin::signed(2), 1, 2)); + roll_to(10); + assert_ok!(Stake::execute_delegation_request(Origin::signed(2), 2, 1, None)); + let post_call_collator_state = + Stake::candidate_state(&1).expect("delegated by all so exists"); + let mut not_equal = false; + for Bond { owner, amount, .. } in pre_call_collator_state.bottom_delegations { + for Bond { owner: post_owner, amount: post_amount, .. } in + &post_call_collator_state.bottom_delegations + { + if &owner == post_owner { + if &amount != post_amount { + not_equal = true; + break + } + } + } + } + assert!(not_equal); + let mut equal = true; + for Bond { owner, amount, .. } in pre_call_collator_state.top_delegations { + for Bond { owner: post_owner, amount: post_amount, .. } in + &post_call_collator_state.top_delegations + { + if &owner == post_owner { + if &amount != post_amount { + equal = false; + break + } + } + } + } + assert!(equal); + assert_eq!( + pre_call_collator_state.total_backing - 2, + post_call_collator_state.total_backing + ); + assert_eq!( + pre_call_collator_state.total_counted, + post_call_collator_state.total_counted + ); + }); +} + +#[test] +fn execute_delegator_bond_less_does_not_delete_bottom_delegations() { + ExtBuilder::default() + .with_default_staking_token(vec![(1, 20), (2, 10), (3, 11), (4, 12), (5, 14), (6, 15)]) + .with_default_token_candidates(vec![(1, 20)]) + .with_delegations(vec![(2, 1, 10), (3, 1, 11), (4, 1, 12), (5, 1, 14), (6, 1, 15)]) + .build() + .execute_with(|| { + let pre_call_collator_state = + Stake::candidate_state(&1).expect("delegated by all so exists"); + assert_ok!(Stake::schedule_delegator_bond_less(Origin::signed(6), 1, 4)); + roll_to(10); + assert_ok!(Stake::execute_delegation_request(Origin::signed(6), 6, 1, None)); + let post_call_collator_state = + Stake::candidate_state(&1).expect("delegated by all so exists"); + let mut equal = true; + for Bond { owner, amount, .. } in pre_call_collator_state.bottom_delegations { + for Bond { owner: post_owner, amount: post_amount, .. } in + &post_call_collator_state.bottom_delegations + { + if &owner == post_owner { + if &amount != post_amount { + equal = false; + break + } + } + } + } + assert!(equal); + let mut not_equal = false; + for Bond { owner, amount, .. } in pre_call_collator_state.top_delegations { + for Bond { owner: post_owner, amount: post_amount, .. } in + &post_call_collator_state.top_delegations + { + if &owner == post_owner { + if &amount != post_amount { + not_equal = true; + break + } + } + } + } + assert!(not_equal); + assert_eq!( + pre_call_collator_state.total_backing - 4, + post_call_collator_state.total_backing + ); + assert_eq!( + pre_call_collator_state.total_counted - 4, + post_call_collator_state.total_counted + ); + }); +} + +#[test] +fn can_execute_delegator_bond_less_for_leaving_candidate() { + ExtBuilder::default() + .with_default_staking_token(vec![(1, 30), (2, 15)]) + .with_default_token_candidates(vec![(1, 30)]) + .with_delegations(vec![(2, 1, 15)]) + .build() + .execute_with(|| { + assert_ok!(Stake::schedule_leave_candidates(Origin::signed(1), 1)); + assert_ok!(Stake::schedule_delegator_bond_less(Origin::signed(2), 1, 5)); + roll_to(10); + // can execute bond more delegation request for leaving candidate + assert_ok!(Stake::execute_delegation_request(Origin::signed(2), 2, 1, None)); + }); +} + +// CANCEL PENDING DELEGATION REQUEST +// 1. CANCEL REVOKE DELEGATION + +#[test] +fn cancel_revoke_delegation_emits_correct_event() { + ExtBuilder::default() + .with_default_staking_token(vec![(1, 30), (2, 10)]) + .with_default_token_candidates(vec![(1, 30)]) + .with_delegations(vec![(2, 1, 10)]) + .build() + .execute_with(|| { + assert_ok!(Stake::schedule_revoke_delegation(Origin::signed(2), 1)); + assert_ok!(Stake::cancel_delegation_request(Origin::signed(2), 1)); + assert_last_event!(MetaEvent::Stake(Event::CancelledDelegationRequest( + 2, + DelegationRequest { + collator: 1, + amount: 10, + when_executable: 2, + action: DelegationChange::Revoke, + }, + ))); + }); +} + +#[test] +fn cancel_revoke_delegation_updates_delegator_state() { + ExtBuilder::default() + .with_default_staking_token(vec![(1, 30), (2, 10)]) + .with_default_token_candidates(vec![(1, 30)]) + .with_delegations(vec![(2, 1, 10)]) + .build() + .execute_with(|| { + assert_ok!(Stake::schedule_revoke_delegation(Origin::signed(2), 1)); + let state = Stake::delegator_state(&2).unwrap(); + assert_eq!( + state.requests().get(&1), + Some(&DelegationRequest { + collator: 1, + amount: 10, + when_executable: 2, + action: DelegationChange::Revoke, + }) + ); + assert_ok!(Stake::cancel_delegation_request(Origin::signed(2), 1)); + let state = Stake::delegator_state(&2).unwrap(); + assert!(state.requests().get(&1).is_none()); + }); +} + +// 2. CANCEL DELEGATOR BOND MORE + +#[test] +fn cancel_delegator_bond_more_emits_correct_event() { + ExtBuilder::default() + .with_default_staking_token(vec![(1, 30), (2, 15)]) + .with_default_token_candidates(vec![(1, 30)]) + .with_delegations(vec![(2, 1, 10)]) + .build() + .execute_with(|| { + assert_ok!(Stake::schedule_delegator_bond_more(Origin::signed(2), 1, 5, None)); + assert_ok!(Stake::cancel_delegation_request(Origin::signed(2), 1)); + assert_last_event!(MetaEvent::Stake(Event::CancelledDelegationRequest( + 2, + DelegationRequest { + collator: 1, + amount: 5, + when_executable: 2, + action: DelegationChange::Increase, + }, + ))); + }); +} + +#[test] +fn cancel_delegator_bond_more_updates_delegator_state() { + ExtBuilder::default() + .with_default_staking_token(vec![(1, 30), (2, 15)]) + .with_default_token_candidates(vec![(1, 30)]) + .with_delegations(vec![(2, 1, 10)]) + .build() + .execute_with(|| { + assert_ok!(Stake::schedule_delegator_bond_more(Origin::signed(2), 1, 5, None)); + let state = Stake::delegator_state(&2).unwrap(); + assert_eq!( + state.requests().get(&1), + Some(&DelegationRequest { + collator: 1, + amount: 5, + when_executable: 2, + action: DelegationChange::Increase, + }) + ); + assert_ok!(Stake::cancel_delegation_request(Origin::signed(2), 1)); + let state = Stake::delegator_state(&2).unwrap(); + assert!(state.requests().get(&1).is_none()); + }); +} + +// 3. CANCEL DELEGATOR BOND LESS + +#[test] +fn cancel_delegator_bond_less_correct_event() { + ExtBuilder::default() + .with_default_staking_token(vec![(1, 30), (2, 15)]) + .with_default_token_candidates(vec![(1, 30)]) + .with_delegations(vec![(2, 1, 15)]) + .build() + .execute_with(|| { + assert_ok!(Stake::schedule_delegator_bond_less(Origin::signed(2), 1, 5)); + assert_ok!(Stake::cancel_delegation_request(Origin::signed(2), 1)); + assert_last_event!(MetaEvent::Stake(Event::CancelledDelegationRequest( + 2, + DelegationRequest { + collator: 1, + amount: 5, + when_executable: 2, + action: DelegationChange::Decrease, + }, + ))); + }); +} + +#[test] +fn cancel_delegator_bond_less_updates_delegator_state() { + ExtBuilder::default() + .with_default_staking_token(vec![(1, 30), (2, 15)]) + .with_default_token_candidates(vec![(1, 30)]) + .with_delegations(vec![(2, 1, 15)]) + .build() + .execute_with(|| { + assert_ok!(Stake::schedule_delegator_bond_less(Origin::signed(2), 1, 5)); + let state = Stake::delegator_state(&2).unwrap(); + assert_eq!( + state.requests().get(&1), + Some(&DelegationRequest { + collator: 1, + amount: 5, + when_executable: 2, + action: DelegationChange::Decrease, + }) + ); + assert_ok!(Stake::cancel_delegation_request(Origin::signed(2), 1)); + let state = Stake::delegator_state(&2).unwrap(); + assert!(state.requests().get(&1).is_none()); + }); +} + +// ~~ PROPERTY-BASED TESTS ~~ + +#[test] +fn delegator_schedule_revocation() { + ExtBuilder::default() + .with_default_staking_token(vec![(1, 20), (2, 40), (3, 20), (4, 20), (5, 20)]) + .with_default_token_candidates(vec![(1, 20), (3, 20), (4, 20), (5, 20)]) + .with_delegations(vec![(2, 1, 10), (2, 3, 10), (2, 4, 10)]) + .build() + .execute_with(|| { + assert_ok!(Stake::schedule_revoke_delegation(Origin::signed(2), 1)); + roll_to(10); + assert_ok!(Stake::execute_delegation_request(Origin::signed(2), 2, 1, None)); + assert_ok!(Stake::delegate(Origin::signed(2), 5, 10, None, 0, 2)); + assert_ok!(Stake::schedule_revoke_delegation(Origin::signed(2), 3)); + assert_ok!(Stake::schedule_revoke_delegation(Origin::signed(2), 4)); + roll_to(20); + assert_ok!(Stake::execute_delegation_request(Origin::signed(2), 2, 3, None)); + assert_ok!(Stake::execute_delegation_request(Origin::signed(2), 2, 4, None)); + }); +} + +// #[ignore] +#[test] +fn reworked_deprecated_test() { + ExtBuilder::default() + .with_staking_tokens(vec![ + (999, 600, 0), + (1, 100, 1), + (2, 100, 1), + (3, 100, 1), + (4, 100, 1), + (5, 100, 1), + (6, 100, 1), + (7, 100, 1), + (8, 100, 1), + (9, 100, 1), + (10, 100, 1), + (11, 1, 1), + ]) + .with_default_token_candidates(vec![(1, 20), (2, 20), (3, 20), (4, 20), (5, 10)]) + .with_delegations(vec![(6, 1, 10), (7, 1, 10), (8, 2, 10), (9, 2, 10), (10, 1, 10)]) + .build() + .execute_with(|| { + assert_eq!(StakeCurrency::free_balance(1, &11), 1); + roll_to(8); + // chooses top TotalSelectedCandidates (5), in order + let mut expected = vec![ + Event::CollatorChosen(2, 1, 50), + Event::CollatorChosen(2, 2, 40), + Event::CollatorChosen(2, 3, 20), + Event::CollatorChosen(2, 4, 20), + Event::CollatorChosen(2, 5, 10), + Event::NewRound(5, 1, 5, 140), + ]; + assert_eq_events!(expected.clone()); + assert_eq!(StakeCurrency::free_balance(1, &11), 1); + roll_to(10); + let mut new0 = vec![ + Event::CollatorChosen(3, 1, 50), + Event::CollatorChosen(3, 2, 40), + Event::CollatorChosen(3, 3, 20), + Event::CollatorChosen(3, 4, 20), + Event::CollatorChosen(3, 5, 10), + Event::NewRound(10, 2, 5, 140), + ]; + expected.append(&mut new0); + assert_eq_events!(expected.clone()); + // ~ set block author as 1 for all blocks this round + set_author(2, 1, 100); + roll_to(20); + payout_collator_for_round(2); + // distribute total issuance to collator 1 and its delegators 6, 7, 19 + let mut new = vec![ + Event::CollatorChosen(4, 1, 50), + Event::CollatorChosen(4, 2, 40), + Event::CollatorChosen(4, 3, 20), + Event::CollatorChosen(4, 4, 20), + Event::CollatorChosen(4, 5, 10), + Event::NewRound(15, 3, 5, 140), + Event::CollatorChosen(5, 1, 50), + Event::CollatorChosen(5, 2, 40), + Event::CollatorChosen(5, 3, 20), + Event::CollatorChosen(5, 4, 20), + Event::CollatorChosen(5, 5, 10), + Event::NewRound(20, 4, 5, 140), + Event::Rewarded(2, 1, 157), + Event::DelegatorDueReward(2, 1, 6, 48), + Event::DelegatorDueReward(2, 1, 7, 48), + Event::DelegatorDueReward(2, 1, 10, 48), + Event::CollatorRewardsDistributed(1, PayoutRounds::All), + ]; + expected.append(&mut new); + assert_eq_events!(expected.clone()); + + assert_eq!(StakeCurrency::free_balance(0, &11), 0); + // ~ set block author as 1 for all blocks this round + set_author(3, 1, 100); + set_author(4, 1, 100); + set_author(5, 1, 100); + // 1. ensure delegators are paid for 2 rounds after they leave + assert_noop!( + Stake::schedule_leave_delegators(Origin::signed(66)), + Error::::DelegatorDNE + ); + assert_ok!(Stake::schedule_leave_delegators(Origin::signed(6))); + // fast forward to block in which delegator 6 exit executes + roll_to(30); + payout_collator_for_round(3); + payout_collator_for_round(4); + assert_ok!(Stake::execute_leave_delegators(Origin::signed(6), 6, 10)); + roll_to(35); + payout_collator_for_round(5); + let mut new2 = vec![ + Event::DelegatorExitScheduled(4, 6, 6), + Event::CollatorChosen(6, 1, 50), + Event::CollatorChosen(6, 2, 40), + Event::CollatorChosen(6, 3, 20), + Event::CollatorChosen(6, 4, 20), + Event::CollatorChosen(6, 5, 10), + Event::NewRound(25, 5, 5, 140), + Event::CollatorChosen(7, 1, 50), + Event::CollatorChosen(7, 2, 40), + Event::CollatorChosen(7, 3, 20), + Event::CollatorChosen(7, 4, 20), + Event::CollatorChosen(7, 5, 10), + Event::NewRound(30, 6, 5, 140), + Event::Rewarded(3, 1, 157), + Event::DelegatorDueReward(3, 1, 6, 48), + Event::DelegatorDueReward(3, 1, 7, 48), + Event::DelegatorDueReward(3, 1, 10, 48), + Event::Rewarded(4, 1, 157), + Event::DelegatorDueReward(4, 1, 6, 48), + Event::DelegatorDueReward(4, 1, 7, 48), + Event::DelegatorDueReward(4, 1, 10, 48), + Event::CollatorRewardsDistributed(1, PayoutRounds::All), + Event::DelegatorLeftCandidate(6, 1, 10, 40), + Event::DelegatorLeft(6, 10), + Event::CollatorChosen(8, 1, 40), + Event::CollatorChosen(8, 2, 40), + Event::CollatorChosen(8, 3, 20), + Event::CollatorChosen(8, 4, 20), + Event::CollatorChosen(8, 5, 10), + Event::NewRound(35, 7, 5, 130), + Event::Rewarded(5, 1, 157), + Event::DelegatorDueReward(5, 1, 6, 48), + Event::DelegatorDueReward(5, 1, 7, 48), + Event::DelegatorDueReward(5, 1, 10, 48), + Event::CollatorRewardsDistributed(1, PayoutRounds::All), + ]; + expected.append(&mut new2); + assert_eq_events!(expected.clone()); + assert_eq!(StakeCurrency::free_balance(0, &11), 0); + // 6 won't be paid for this round because they left already + set_author(6, 1, 100); + roll_to(40); + payout_collator_for_round(6); + // keep paying 6 + let mut new3 = vec![ + Event::CollatorChosen(9, 1, 40), + Event::CollatorChosen(9, 2, 40), + Event::CollatorChosen(9, 3, 20), + Event::CollatorChosen(9, 4, 20), + Event::CollatorChosen(9, 5, 10), + Event::NewRound(40, 8, 5, 130), + Event::Rewarded(6, 1, 157), + Event::DelegatorDueReward(6, 1, 6, 48), + Event::DelegatorDueReward(6, 1, 7, 48), + Event::DelegatorDueReward(6, 1, 10, 48), + Event::CollatorRewardsDistributed(1, PayoutRounds::All), + ]; + expected.append(&mut new3); + assert_eq_events!(expected.clone()); + assert_eq!(StakeCurrency::free_balance(0, &11), 0); + set_author(7, 1, 100); + roll_to(45); + payout_collator_for_round(7); + // no more paying 6 + let mut new4 = vec![ + Event::CollatorChosen(10, 1, 40), + Event::CollatorChosen(10, 2, 40), + Event::CollatorChosen(10, 3, 20), + Event::CollatorChosen(10, 4, 20), + Event::CollatorChosen(10, 5, 10), + Event::NewRound(45, 9, 5, 130), + Event::Rewarded(7, 1, 157), + Event::DelegatorDueReward(7, 1, 6, 48), + Event::DelegatorDueReward(7, 1, 7, 48), + Event::DelegatorDueReward(7, 1, 10, 48), + Event::CollatorRewardsDistributed(1, PayoutRounds::All), + ]; + expected.append(&mut new4); + assert_eq_events!(expected.clone()); + assert_eq!(StakeCurrency::free_balance(0, &11), 0); + set_author(8, 1, 100); + assert_ok!(Stake::delegate(Origin::signed(8), 1, 10, None, 10, 10)); + roll_to(50); + payout_collator_for_round(8); + // new delegation is not rewarded yet + let mut new5 = vec![ + Event::Delegation(8, 10, 1, DelegatorAdded::AddedToTop { new_total: 50 }), + Event::CollatorChosen(11, 1, 50), + Event::CollatorChosen(11, 2, 40), + Event::CollatorChosen(11, 3, 20), + Event::CollatorChosen(11, 4, 20), + Event::CollatorChosen(11, 5, 10), + Event::NewRound(50, 10, 5, 140), + Event::Rewarded(8, 1, 182), + Event::DelegatorDueReward(8, 1, 7, 61), + Event::DelegatorDueReward(8, 1, 10, 61), + Event::CollatorRewardsDistributed(1, PayoutRounds::All), + ]; + expected.append(&mut new5); + assert_eq_events!(expected.clone()); + assert_eq!(StakeCurrency::free_balance(0, &11), 0); + set_author(9, 1, 100); + set_author(10, 1, 100); + roll_to(55); + payout_collator_for_round(9); + // new delegation is still not rewarded yet + let mut new6 = vec![ + Event::CollatorChosen(12, 1, 50), + Event::CollatorChosen(12, 2, 40), + Event::CollatorChosen(12, 3, 20), + Event::CollatorChosen(12, 4, 20), + Event::CollatorChosen(12, 5, 10), + Event::NewRound(55, 11, 5, 140), + Event::Rewarded(9, 1, 182), + Event::DelegatorDueReward(9, 1, 7, 61), + Event::DelegatorDueReward(9, 1, 10, 61), + Event::CollatorRewardsDistributed(1, PayoutRounds::All), + ]; + expected.append(&mut new6); + assert_eq_events!(expected.clone()); + assert_eq!(StakeCurrency::free_balance(0, &11), 0); + roll_to(60); + payout_collator_for_round(10); + // new delegation is rewarded, 2 rounds after joining (`RewardPaymentDelay` is 2) + let mut new7 = vec![ + Event::CollatorChosen(13, 1, 50), + Event::CollatorChosen(13, 2, 40), + Event::CollatorChosen(13, 3, 20), + Event::CollatorChosen(13, 4, 20), + Event::CollatorChosen(13, 5, 10), + Event::NewRound(60, 12, 5, 140), + Event::Rewarded(10, 1, 182), + Event::DelegatorDueReward(10, 1, 7, 61), + Event::DelegatorDueReward(10, 1, 10, 61), + Event::CollatorRewardsDistributed(1, PayoutRounds::All), + ]; + expected.append(&mut new7); + assert_eq_events!(expected); + assert_eq!(StakeCurrency::free_balance(0, &11), 0); + }); +} + +#[test] +fn paid_collator_commission_matches_config() { + ExtBuilder::default() + .with_staking_tokens(vec![ + (999, 600, 0), + (1, 100, 1), + (2, 100, 1), + (3, 100, 1), + (4, 100, 1), + (5, 100, 1), + (6, 100, 1), + ]) + .with_default_token_candidates(vec![(1, 20)]) + .with_delegations(vec![(2, 1, 10), (3, 1, 10)]) + .build() + .execute_with(|| { + roll_to(7); + // chooses top TotalSelectedCandidates (5), in order + let mut expected = vec![Event::CollatorChosen(2, 1, 40), Event::NewRound(5, 1, 1, 40)]; + assert_eq_events!(expected.clone()); + assert_ok!(Stake::join_candidates( + Origin::signed(4), + 20u128, + 1u32, + None, + 100u32, + 10000u32 + )); + assert_last_event!(MetaEvent::Stake(Event::JoinedCollatorCandidates( + 4, 20u128, 60u128, + ))); + roll_to(8); + assert_ok!(Stake::delegate(Origin::signed(5), 4, 10, None, 10, 10)); + assert_ok!(Stake::delegate(Origin::signed(6), 4, 10, None, 10, 10)); + roll_to(10); + let mut new = vec![ + Event::JoinedCollatorCandidates(4, 20, 60), + Event::Delegation(5, 10, 4, DelegatorAdded::AddedToTop { new_total: 30 }), + Event::Delegation(6, 10, 4, DelegatorAdded::AddedToTop { new_total: 40 }), + Event::CollatorChosen(3, 1, 40), + Event::CollatorChosen(3, 4, 40), + Event::NewRound(10, 2, 2, 80), + ]; + expected.append(&mut new); + assert_eq_events!(expected.clone()); + roll_to(15); + let mut new1 = vec![ + Event::CollatorChosen(4, 1, 40), + Event::CollatorChosen(4, 4, 40), + Event::NewRound(15, 3, 2, 80), + ]; + expected.append(&mut new1); + assert_eq_events!(expected.clone()); + // only reward author with id 4 + set_author(3, 4, 100); + roll_to(25); + payout_collator_for_round(3); + // 20% of 10 is commission + due_portion (4) = 2 + 4 = 6 + // all delegator payouts are 10-2 = 8 * stake_pct + let mut new2 = vec![ + Event::CollatorChosen(5, 1, 40), + Event::CollatorChosen(5, 4, 40), + Event::NewRound(20, 4, 2, 80), + Event::CollatorChosen(6, 1, 40), + Event::CollatorChosen(6, 4, 40), + Event::NewRound(25, 5, 2, 80), + Event::Rewarded(3, 4, 182), + Event::DelegatorDueReward(3, 4, 5, 61), + Event::DelegatorDueReward(3, 4, 6, 61), + Event::CollatorRewardsDistributed(4, PayoutRounds::All), + ]; + expected.append(&mut new2); + assert_eq_events!(expected); + }); +} + +#[test] +fn collator_exit_executes_after_delay() { + ExtBuilder::default() + .with_default_staking_token(vec![ + (1, 1000), + (2, 300), + (3, 100), + (4, 100), + (5, 100), + (6, 100), + (7, 100), + (8, 9), + (9, 4), + ]) + .with_default_token_candidates(vec![(1, 500), (2, 200)]) + .with_delegations(vec![(3, 1, 100), (4, 1, 100), (5, 2, 100), (6, 2, 100)]) + .build() + .execute_with(|| { + roll_to(11); + assert_ok!(Stake::schedule_leave_candidates(Origin::signed(2), 2)); + let info = Stake::candidate_state(&2).unwrap(); + assert_eq!(info.state, CollatorStatus::Leaving(4)); + roll_to(21); + assert_ok!(Stake::execute_leave_candidates(Origin::signed(2), 2, 10000u32)); + // we must exclude leaving collators from rewards while + // holding them retroactively accountable for previous faults + // (within the last T::SlashingWindow blocks) + let expected = vec![ + Event::CollatorChosen(2, 1, 700), + Event::CollatorChosen(2, 2, 400), + Event::NewRound(5, 1, 2, 1100), + Event::CollatorChosen(3, 1, 700), + Event::CollatorChosen(3, 2, 400), + Event::NewRound(10, 2, 2, 1100), + Event::CandidateScheduledExit(2, 2, 4), + Event::CollatorChosen(4, 1, 700), + Event::NewRound(15, 3, 1, 700), + Event::CollatorChosen(5, 1, 700), + Event::NewRound(20, 4, 1, 700), + Event::CandidateLeft(2, 400, 700), + ]; + assert_eq_events!(expected); + }); +} + +#[test] +fn collator_selection_chooses_top_candidates() { + ExtBuilder::default() + .with_default_staking_token(vec![ + (1, 1000), + (2, 1000), + (3, 1000), + (4, 1000), + (5, 1000), + (6, 1000), + (7, 33), + (8, 33), + (9, 33), + ]) + .with_default_token_candidates(vec![(1, 100), (2, 90), (3, 80), (4, 70), (5, 60), (6, 50)]) + .build() + .execute_with(|| { + roll_to(8); + // should choose top TotalSelectedCandidates (5), in order + let expected = vec![ + Event::CollatorChosen(2, 1, 100), + Event::CollatorChosen(2, 2, 90), + Event::CollatorChosen(2, 3, 80), + Event::CollatorChosen(2, 4, 70), + Event::CollatorChosen(2, 5, 60), + Event::NewRound(5, 1, 5, 400), + ]; + assert_eq_events!(expected.clone()); + assert_ok!(Stake::schedule_leave_candidates(Origin::signed(6), 6)); + assert_last_event!(MetaEvent::Stake(Event::CandidateScheduledExit(1, 6, 3))); + roll_to(21); + assert_ok!(Stake::execute_leave_candidates(Origin::signed(6), 6, 10000u32)); + assert_ok!(Stake::join_candidates( + Origin::signed(6), + 69u128, + 1u32, + None, + 100u32, + 10000u32 + )); + assert_last_event!(MetaEvent::Stake(Event::JoinedCollatorCandidates( + 6, 69u128, 469u128, + ))); + roll_to(27); + // should choose top TotalSelectedCandidates (5), in order + let expected = vec![ + Event::CollatorChosen(2, 1, 100), + Event::CollatorChosen(2, 2, 90), + Event::CollatorChosen(2, 3, 80), + Event::CollatorChosen(2, 4, 70), + Event::CollatorChosen(2, 5, 60), + Event::NewRound(5, 1, 5, 400), + Event::CandidateScheduledExit(1, 6, 3), + Event::CollatorChosen(3, 1, 100), + Event::CollatorChosen(3, 2, 90), + Event::CollatorChosen(3, 3, 80), + Event::CollatorChosen(3, 4, 70), + Event::CollatorChosen(3, 5, 60), + Event::NewRound(10, 2, 5, 400), + Event::CollatorChosen(4, 1, 100), + Event::CollatorChosen(4, 2, 90), + Event::CollatorChosen(4, 3, 80), + Event::CollatorChosen(4, 4, 70), + Event::CollatorChosen(4, 5, 60), + Event::NewRound(15, 3, 5, 400), + Event::CollatorChosen(5, 1, 100), + Event::CollatorChosen(5, 2, 90), + Event::CollatorChosen(5, 3, 80), + Event::CollatorChosen(5, 4, 70), + Event::CollatorChosen(5, 5, 60), + Event::NewRound(20, 4, 5, 400), + Event::CandidateLeft(6, 50, 400), + Event::JoinedCollatorCandidates(6, 69, 469), + Event::CollatorChosen(6, 1, 100), + Event::CollatorChosen(6, 2, 90), + Event::CollatorChosen(6, 3, 80), + Event::CollatorChosen(6, 4, 70), + Event::CollatorChosen(6, 6, 69), + Event::NewRound(25, 5, 5, 409), + ]; + assert_eq_events!(expected); + }); +} + +#[test] +fn payout_distribution_to_solo_collators() { + ExtBuilder::default() + .with_staking_tokens(vec![ + (999, 1000, 0), + (1, 1000, 1), + (2, 1000, 1), + (3, 1000, 1), + (4, 1000, 1), + (5, 1000, 1), + (6, 1000, 1), + (7, 33, 1), + (8, 33, 1), + (9, 33, 1), + ]) + .with_default_token_candidates(vec![(1, 100), (2, 90), (3, 80), (4, 70), (5, 60), (6, 50)]) + .build() + .execute_with(|| { + roll_to(8); + // should choose top TotalCandidatesSelected (5), in order + let mut expected = vec![ + Event::CollatorChosen(2, 1, 100), + Event::CollatorChosen(2, 2, 90), + Event::CollatorChosen(2, 3, 80), + Event::CollatorChosen(2, 4, 70), + Event::CollatorChosen(2, 5, 60), + Event::NewRound(5, 1, 5, 400), + ]; + assert_eq_events!(expected.clone()); + // ~ set block author as 1 for all blocks this round + set_author(2, 1, 100); + roll_to(16); + // pay total issuance to 1 + let mut new = vec![ + Event::CollatorChosen(3, 1, 100), + Event::CollatorChosen(3, 2, 90), + Event::CollatorChosen(3, 3, 80), + Event::CollatorChosen(3, 4, 70), + Event::CollatorChosen(3, 5, 60), + Event::NewRound(10, 2, 5, 400), + Event::CollatorChosen(4, 1, 100), + Event::CollatorChosen(4, 2, 90), + Event::CollatorChosen(4, 3, 80), + Event::CollatorChosen(4, 4, 70), + Event::CollatorChosen(4, 5, 60), + Event::NewRound(15, 3, 5, 400), + ]; + expected.append(&mut new); + assert_eq_events!(expected.clone()); + // ~ set block author as 1 for 3 blocks this round + set_author(4, 1, 60); + // ~ set block author as 2 for 2 blocks this round + set_author(4, 2, 40); + roll_to(26); + payout_collator_for_round(2); + // pay 60% total issuance to 1 and 40% total issuance to 2 + let mut new1 = vec![ + Event::CollatorChosen(5, 1, 100), + Event::CollatorChosen(5, 2, 90), + Event::CollatorChosen(5, 3, 80), + Event::CollatorChosen(5, 4, 70), + Event::CollatorChosen(5, 5, 60), + Event::NewRound(20, 4, 5, 400), + Event::CollatorChosen(6, 1, 100), + Event::CollatorChosen(6, 2, 90), + Event::CollatorChosen(6, 3, 80), + Event::CollatorChosen(6, 4, 70), + Event::CollatorChosen(6, 5, 60), + Event::NewRound(25, 5, 5, 400), + Event::Rewarded(2, 1, 304), + Event::CollatorRewardsDistributed(1, PayoutRounds::All), + ]; + expected.append(&mut new1); + assert_eq_events!(expected.clone()); + // ~ each collator produces 1 block this round + set_author(6, 1, 20); + set_author(6, 2, 20); + set_author(6, 3, 20); + set_author(6, 4, 20); + set_author(6, 5, 20); + roll_to(36); + payout_collator_for_round(4); + payout_collator_for_round(5); + // pay 20% issuance for all collators + let mut new2 = vec![ + Event::CollatorChosen(7, 1, 100), + Event::CollatorChosen(7, 2, 90), + Event::CollatorChosen(7, 3, 80), + Event::CollatorChosen(7, 4, 70), + Event::CollatorChosen(7, 5, 60), + Event::NewRound(30, 6, 5, 400), + Event::CollatorChosen(8, 1, 100), + Event::CollatorChosen(8, 2, 90), + Event::CollatorChosen(8, 3, 80), + Event::CollatorChosen(8, 4, 70), + Event::CollatorChosen(8, 5, 60), + Event::NewRound(35, 7, 5, 400), + Event::Rewarded(4, 1, 182), + Event::CollatorRewardsDistributed(1, PayoutRounds::All), + Event::Rewarded(4, 2, 121), + Event::CollatorRewardsDistributed(2, PayoutRounds::All), + ]; + expected.append(&mut new2); + assert_eq_events!(expected); + + roll_to(46); + payout_collator_for_round(6); + payout_collator_for_round(7); + // pay 20% issuance for all collators + let mut new3 = vec![ + Event::CollatorChosen(9, 1, 100), + Event::CollatorChosen(9, 2, 90), + Event::CollatorChosen(9, 3, 80), + Event::CollatorChosen(9, 4, 70), + Event::CollatorChosen(9, 5, 60), + Event::NewRound(40, 8, 5, 400), + Event::CollatorChosen(10, 1, 100), + Event::CollatorChosen(10, 2, 90), + Event::CollatorChosen(10, 3, 80), + Event::CollatorChosen(10, 4, 70), + Event::CollatorChosen(10, 5, 60), + Event::NewRound(45, 9, 5, 400), + Event::Rewarded(6, 5, 60), + Event::CollatorRewardsDistributed(5, PayoutRounds::All), + Event::Rewarded(6, 3, 60), + Event::CollatorRewardsDistributed(3, PayoutRounds::All), + Event::Rewarded(6, 1, 60), + Event::CollatorRewardsDistributed(1, PayoutRounds::All), + Event::Rewarded(6, 4, 60), + Event::CollatorRewardsDistributed(4, PayoutRounds::All), + Event::Rewarded(6, 2, 60), + Event::CollatorRewardsDistributed(2, PayoutRounds::All), + ]; + expected.append(&mut new3); + assert_eq_events!(expected); + // check that distributing rewards clears awarded pts + assert!(Stake::awarded_pts(1, 1).is_zero()); + assert!(Stake::awarded_pts(4, 1).is_zero()); + assert!(Stake::awarded_pts(4, 2).is_zero()); + assert!(Stake::awarded_pts(6, 1).is_zero()); + assert!(Stake::awarded_pts(6, 2).is_zero()); + assert!(Stake::awarded_pts(6, 3).is_zero()); + assert!(Stake::awarded_pts(6, 4).is_zero()); + assert!(Stake::awarded_pts(6, 5).is_zero()); + }); +} + +#[test] +fn multiple_delegations() { + ExtBuilder::default() + .with_default_staking_token(vec![ + (1, 100), + (2, 100), + (3, 100), + (4, 100), + (5, 100), + (6, 100), + (7, 100), + (8, 100), + (9, 100), + (10, 100), + ]) + .with_default_token_candidates(vec![(1, 20), (2, 20), (3, 20), (4, 20), (5, 10)]) + .with_delegations(vec![(6, 1, 10), (7, 1, 10), (8, 2, 10), (9, 2, 10), (10, 1, 10)]) + .build() + .execute_with(|| { + roll_to(8); + // chooses top TotalSelectedCandidates (5), in order + let mut expected = vec![ + Event::CollatorChosen(2, 1, 50), + Event::CollatorChosen(2, 2, 40), + Event::CollatorChosen(2, 3, 20), + Event::CollatorChosen(2, 4, 20), + Event::CollatorChosen(2, 5, 10), + Event::NewRound(5, 1, 5, 140), + ]; + assert_eq_events!(expected.clone()); + assert_ok!(Stake::delegate(Origin::signed(6), 2, 10, None, 10, 10)); + assert_ok!(Stake::delegate(Origin::signed(6), 3, 10, None, 10, 10)); + assert_ok!(Stake::delegate(Origin::signed(6), 4, 10, None, 10, 10)); + roll_to(16); + let mut new = vec![ + Event::Delegation(6, 10, 2, DelegatorAdded::AddedToTop { new_total: 50 }), + Event::Delegation(6, 10, 3, DelegatorAdded::AddedToTop { new_total: 30 }), + Event::Delegation(6, 10, 4, DelegatorAdded::AddedToTop { new_total: 30 }), + Event::CollatorChosen(3, 1, 50), + Event::CollatorChosen(3, 2, 50), + Event::CollatorChosen(3, 3, 30), + Event::CollatorChosen(3, 4, 30), + Event::CollatorChosen(3, 5, 10), + Event::NewRound(10, 2, 5, 170), + Event::CollatorChosen(4, 1, 50), + Event::CollatorChosen(4, 2, 50), + Event::CollatorChosen(4, 3, 30), + Event::CollatorChosen(4, 4, 30), + Event::CollatorChosen(4, 5, 10), + Event::NewRound(15, 3, 5, 170), + ]; + expected.append(&mut new); + assert_eq_events!(expected.clone()); + roll_to(21); + assert_ok!(Stake::delegate(Origin::signed(7), 2, 80, None, 10, 10)); + assert_ok!(Stake::delegate(Origin::signed(10), 2, 10, None, 10, 10),); + roll_to(26); + let mut new2 = vec![ + Event::CollatorChosen(5, 1, 50), + Event::CollatorChosen(5, 2, 50), + Event::CollatorChosen(5, 3, 30), + Event::CollatorChosen(5, 4, 30), + Event::CollatorChosen(5, 5, 10), + Event::NewRound(20, 4, 5, 170), + Event::Delegation(7, 80, 2, DelegatorAdded::AddedToTop { new_total: 130 }), + Event::Delegation(10, 10, 2, DelegatorAdded::AddedToBottom), + Event::CollatorChosen(6, 1, 50), + Event::CollatorChosen(6, 2, 130), + Event::CollatorChosen(6, 3, 30), + Event::CollatorChosen(6, 4, 30), + Event::CollatorChosen(6, 5, 10), + Event::NewRound(25, 5, 5, 250), + ]; + expected.append(&mut new2); + assert_eq_events!(expected.clone()); + assert_ok!(Stake::schedule_leave_candidates(Origin::signed(2), 5)); + assert_last_event!(MetaEvent::Stake(Event::CandidateScheduledExit(5, 2, 7))); + roll_to(31); + let mut new3 = vec![ + Event::CandidateScheduledExit(5, 2, 7), + Event::CollatorChosen(7, 1, 50), + Event::CollatorChosen(7, 3, 30), + Event::CollatorChosen(7, 4, 30), + Event::CollatorChosen(7, 5, 10), + Event::NewRound(30, 6, 4, 120), + ]; + expected.append(&mut new3); + assert_eq_events!(expected); + // verify that delegations are removed after collator leaves, not before + assert_eq!(Stake::delegator_state(7).unwrap().delegations.0.len(), 2usize); + assert_eq!(Stake::delegator_state(6).unwrap().delegations.0.len(), 4usize); + assert_eq!(StakeCurrency::reserved_balance(1, &6), 40); + assert_eq!(StakeCurrency::reserved_balance(1, &7), 90); + assert_eq!(StakeCurrency::free_balance(1, &6), 60); + assert_eq!(StakeCurrency::free_balance(1, &7), 10); + roll_to(40); + assert_ok!(Stake::execute_leave_candidates(Origin::signed(2), 2, 10000u32)); + assert_eq!(Stake::delegator_state(7).unwrap().delegations.0.len(), 1usize); + assert_eq!(Stake::delegator_state(6).unwrap().delegations.0.len(), 3usize); + assert_eq!(StakeCurrency::reserved_balance(1, &6), 30); + assert_eq!(StakeCurrency::reserved_balance(1, &7), 10); + assert_eq!(StakeCurrency::free_balance(1, &6), 70); + assert_eq!(StakeCurrency::free_balance(1, &7), 90); + }); +} + +#[test] +fn payouts_follow_delegation_changes() { + ExtBuilder::default() + .with_staking_tokens(vec![ + (999, 1000, 0), + (1, 100, 1), + (2, 100, 1), + (3, 100, 1), + (4, 100, 1), + (5, 100, 1), + (6, 100, 1), + (7, 100, 1), + (8, 100, 1), + (9, 100, 1), + (10, 100, 1), + ]) + .with_default_token_candidates(vec![(1, 20), (2, 20), (3, 20), (4, 20), (5, 10)]) + .with_delegations(vec![(6, 1, 10), (7, 1, 10), (8, 2, 10), (9, 2, 10), (10, 1, 10)]) + .build() + .execute_with(|| { + roll_to(10); + // chooses top TotalSelectedCandidates (5), in order + let mut expected = vec![ + Event::CollatorChosen(2, 1, 50), + Event::CollatorChosen(2, 2, 40), + Event::CollatorChosen(2, 3, 20), + Event::CollatorChosen(2, 4, 20), + Event::CollatorChosen(2, 5, 10), + Event::NewRound(5, 1, 5, 140), + Event::CollatorChosen(3, 1, 50), + Event::CollatorChosen(3, 2, 40), + Event::CollatorChosen(3, 3, 20), + Event::CollatorChosen(3, 4, 20), + Event::CollatorChosen(3, 5, 10), + Event::NewRound(10, 2, 5, 140), + ]; + assert_eq_events!(expected.clone()); + // ~ set block author as 1 for all blocks this round + set_author(2, 1, 100); + roll_to(16); + // distribute total issuance to collator 1 and its delegators 6, 7, 19 + let mut new = vec![ + Event::CollatorChosen(4, 1, 50), + Event::CollatorChosen(4, 2, 40), + Event::CollatorChosen(4, 3, 20), + Event::CollatorChosen(4, 4, 20), + Event::CollatorChosen(4, 5, 10), + Event::NewRound(15, 3, 5, 140), + ]; + expected.append(&mut new); + assert_eq_events!(expected.clone()); + + // ~ set block author as 1 for all blocks this round + set_author(3, 1, 100); + set_author(4, 1, 100); + set_author(5, 1, 100); + set_author(6, 1, 100); + // 1. ensure delegators are paid for 2 rounds after they leave + assert_noop!( + Stake::schedule_leave_delegators(Origin::signed(66)), + Error::::DelegatorDNE + ); + assert_ok!(Stake::schedule_leave_delegators(Origin::signed(6))); + // fast forward to block in which delegator 6 exit executes + roll_to(25); + + payout_collator_for_round(2); + payout_collator_for_round(3); + + assert_ok!(Stake::execute_leave_delegators(Origin::signed(6), 6, 10)); + // keep paying 6 (note: inflation is in terms of total issuance so that's why 1 is 21) + let mut new2 = vec![ + Event::DelegatorExitScheduled(3, 6, 5), + Event::CollatorChosen(5, 1, 50), + Event::CollatorChosen(5, 2, 40), + Event::CollatorChosen(5, 3, 20), + Event::CollatorChosen(5, 4, 20), + Event::CollatorChosen(5, 5, 10), + Event::NewRound(20, 4, 5, 140), + Event::CollatorChosen(6, 1, 50), + Event::CollatorChosen(6, 2, 40), + Event::CollatorChosen(6, 3, 20), + Event::CollatorChosen(6, 4, 20), + Event::CollatorChosen(6, 5, 10), + Event::NewRound(25, 5, 5, 140), + Event::Rewarded(2, 1, 157), + Event::DelegatorDueReward(2, 1, 6, 48), + Event::DelegatorDueReward(2, 1, 7, 48), + Event::DelegatorDueReward(2, 1, 10, 48), + Event::Rewarded(3, 1, 157), + Event::DelegatorDueReward(3, 1, 6, 48), + Event::DelegatorDueReward(3, 1, 7, 48), + Event::DelegatorDueReward(3, 1, 10, 48), + Event::CollatorRewardsDistributed(1, PayoutRounds::All), + Event::DelegatorLeftCandidate(6, 1, 10, 40), + Event::DelegatorLeft(6, 10), + ]; + expected.append(&mut new2); + assert_eq_events!(expected.clone()); + // 6 won't be paid for this round because they left already + set_author(7, 1, 100); + roll_to(35); + + payout_collator_for_round(4); + payout_collator_for_round(5); + + // keep paying 6 + let mut new3 = vec![ + Event::CollatorChosen(7, 1, 40), + Event::CollatorChosen(7, 2, 40), + Event::CollatorChosen(7, 3, 20), + Event::CollatorChosen(7, 4, 20), + Event::CollatorChosen(7, 5, 10), + Event::NewRound(30, 6, 5, 130), + Event::CollatorChosen(8, 1, 40), + Event::CollatorChosen(8, 2, 40), + Event::CollatorChosen(8, 3, 20), + Event::CollatorChosen(8, 4, 20), + Event::CollatorChosen(8, 5, 10), + Event::NewRound(35, 7, 5, 130), + Event::Rewarded(5, 1, 157), + Event::DelegatorDueReward(5, 1, 6, 48), + Event::DelegatorDueReward(5, 1, 7, 48), + Event::DelegatorDueReward(5, 1, 10, 48), + Event::Rewarded(4, 1, 157), + Event::DelegatorDueReward(4, 1, 6, 48), + Event::DelegatorDueReward(4, 1, 7, 48), + Event::DelegatorDueReward(4, 1, 10, 48), + Event::CollatorRewardsDistributed(1, PayoutRounds::All), + ]; + expected.append(&mut new3); + assert_eq_events!(expected.clone()); + set_author(8, 1, 100); + roll_to(40); + + payout_collator_for_round(6); + + // no more paying 6 + let mut new4 = vec![ + Event::CollatorChosen(9, 1, 40), + Event::CollatorChosen(9, 2, 40), + Event::CollatorChosen(9, 3, 20), + Event::CollatorChosen(9, 4, 20), + Event::CollatorChosen(9, 5, 10), + Event::NewRound(40, 8, 5, 130), + Event::Rewarded(6, 1, 157), + Event::DelegatorDueReward(6, 1, 6, 48), + Event::DelegatorDueReward(6, 1, 7, 48), + Event::DelegatorDueReward(6, 1, 10, 48), + Event::CollatorRewardsDistributed(1, PayoutRounds::All), + ]; + expected.append(&mut new4); + assert_eq_events!(expected.clone()); + set_author(9, 1, 100); + assert_ok!(Stake::delegate(Origin::signed(8), 1, 10, None, 10, 10)); + roll_to(45); + + payout_collator_for_round(7); + + // new delegation is not rewarded yet + let mut new5 = vec![ + Event::Delegation(8, 10, 1, DelegatorAdded::AddedToTop { new_total: 50 }), + Event::CollatorChosen(10, 1, 50), + Event::CollatorChosen(10, 2, 40), + Event::CollatorChosen(10, 3, 20), + Event::CollatorChosen(10, 4, 20), + Event::CollatorChosen(10, 5, 10), + Event::NewRound(45, 9, 5, 140), + Event::Rewarded(7, 1, 182), + Event::DelegatorDueReward(7, 1, 7, 61), + Event::DelegatorDueReward(7, 1, 10, 61), + Event::CollatorRewardsDistributed(1, PayoutRounds::All), + ]; + expected.append(&mut new5); + assert_eq_events!(expected.clone()); + set_author(10, 1, 100); + roll_to(50); + payout_collator_for_round(8); + // new delegation not rewarded yet + let mut new6 = vec![ + Event::CollatorChosen(11, 1, 50), + Event::CollatorChosen(11, 2, 40), + Event::CollatorChosen(11, 3, 20), + Event::CollatorChosen(11, 4, 20), + Event::CollatorChosen(11, 5, 10), + Event::NewRound(50, 10, 5, 140), + Event::Rewarded(8, 1, 182), + Event::DelegatorDueReward(8, 1, 7, 61), + Event::DelegatorDueReward(8, 1, 10, 61), + Event::CollatorRewardsDistributed(1, PayoutRounds::All), + ]; + expected.append(&mut new6); + assert_eq_events!(expected.clone()); + roll_to(55); + payout_collator_for_round(9); + // new delegation is rewarded for first time + // 2 rounds after joining (`RewardPaymentDelay` = 2) + let mut new7 = vec![ + Event::CollatorChosen(12, 1, 50), + Event::CollatorChosen(12, 2, 40), + Event::CollatorChosen(12, 3, 20), + Event::CollatorChosen(12, 4, 20), + Event::CollatorChosen(12, 5, 10), + Event::NewRound(55, 11, 5, 140), + Event::Rewarded(9, 1, 182), + Event::DelegatorDueReward(9, 1, 7, 61), + Event::DelegatorDueReward(9, 1, 10, 61), + Event::CollatorRewardsDistributed(1, PayoutRounds::All), + ]; + expected.append(&mut new7); + assert_eq_events!(expected); + roll_to(60); + payout_collator_for_round(10); + // new delegation is rewarded for first time + // 2 rounds after joining (`RewardPaymentDelay` = 2) + let mut new8 = vec![ + Event::CollatorChosen(13, 1, 50), + Event::CollatorChosen(13, 2, 40), + Event::CollatorChosen(13, 3, 20), + Event::CollatorChosen(13, 4, 20), + Event::CollatorChosen(13, 5, 10), + Event::NewRound(60, 12, 5, 140), + Event::Rewarded(10, 1, 157), + Event::DelegatorDueReward(10, 1, 7, 48), + Event::DelegatorDueReward(10, 1, 8, 48), + Event::DelegatorDueReward(10, 1, 10, 48), + Event::CollatorRewardsDistributed(1, PayoutRounds::All), + ]; + expected.append(&mut new8); + assert_eq_events!(expected); + }); +} + +#[test] +fn delegations_merged_before_reward_payout() { + ExtBuilder::default() + .with_staking_tokens(vec![ + (999, 280, 0), + (1, 20, 1), + (2, 20, 1), + (3, 20, 1), + (4, 20, 1), + (5, 120, 1), + ]) + .with_default_token_candidates(vec![(1, 20), (2, 20), (3, 20), (4, 20)]) + .with_delegations(vec![(5, 1, 30), (5, 2, 30), (5, 3, 30), (5, 4, 30)]) + .build() + .execute_with(|| { + roll_to(8); + set_author(1, 1, 1); + set_author(1, 2, 1); + set_author(1, 3, 1); + set_author(1, 4, 1); + + roll_to(16); + + payout_collator_for_round(1); + + assert_eq!(StakeCurrency::free_balance(0u32, &1), 39); + let expected_events = vec![ + Event::CollatorChosen(2, 1, 50), + Event::CollatorChosen(2, 2, 50), + Event::CollatorChosen(2, 3, 50), + Event::CollatorChosen(2, 4, 50), + Event::NewRound(5, 1, 4, 200), + Event::CollatorChosen(3, 1, 50), + Event::CollatorChosen(3, 2, 50), + Event::CollatorChosen(3, 3, 50), + Event::CollatorChosen(3, 4, 50), + Event::NewRound(10, 2, 4, 200), + Event::CollatorChosen(4, 1, 50), + Event::CollatorChosen(4, 2, 50), + Event::CollatorChosen(4, 3, 50), + Event::CollatorChosen(4, 4, 50), + Event::NewRound(15, 3, 4, 200), + Event::Rewarded(1, 3, 39), + Event::DelegatorDueReward(1, 3, 5, 36), + Event::CollatorRewardsDistributed(3, PayoutRounds::All), + Event::Rewarded(1, 1, 39), + Event::DelegatorDueReward(1, 1, 5, 36), + Event::CollatorRewardsDistributed(1, PayoutRounds::All), + Event::Rewarded(1, 4, 39), + Event::DelegatorDueReward(1, 4, 5, 36), + Event::CollatorRewardsDistributed(4, PayoutRounds::All), + Event::Rewarded(1, 2, 39), + Event::DelegatorDueReward(1, 2, 5, 36), + Event::CollatorRewardsDistributed(2, PayoutRounds::All), + ]; + assert_eq_events!(expected_events); + }); +} + +#[test] +// MaxDelegatorsPerCandidate = 4 +fn bottom_delegations_are_empty_when_top_delegations_not_full() { + ExtBuilder::default() + .with_default_staking_token(vec![(1, 20), (2, 10), (3, 10), (4, 10), (5, 10)]) + .with_default_token_candidates(vec![(1, 20)]) + .build() + .execute_with(|| { + // no top delegators => no bottom delegators + let collator_state = Stake::candidate_state(1).unwrap(); + assert!(collator_state.top_delegations.is_empty()); + assert!(collator_state.bottom_delegations.is_empty()); + // 1 delegator => 1 top delegator, 0 bottom delegators + assert_ok!(Stake::delegate(Origin::signed(2), 1, 10, None, 10, 10)); + let collator_state = Stake::candidate_state(1).unwrap(); + assert_eq!(collator_state.top_delegations.len(), 1usize); + assert!(collator_state.bottom_delegations.is_empty()); + // 2 delegators => 2 top delegators, 0 bottom delegators + assert_ok!(Stake::delegate(Origin::signed(3), 1, 10, None, 10, 10)); + let collator_state = Stake::candidate_state(1).unwrap(); + assert_eq!(collator_state.top_delegations.len(), 2usize); + assert!(collator_state.bottom_delegations.is_empty()); + // 3 delegators => 3 top delegators, 0 bottom delegators + assert_ok!(Stake::delegate(Origin::signed(4), 1, 10, None, 10, 10)); + let collator_state = Stake::candidate_state(1).unwrap(); + assert_eq!(collator_state.top_delegations.len(), 3usize); + assert!(collator_state.bottom_delegations.is_empty()); + // 4 delegators => 4 top delegators, 0 bottom delegators + assert_ok!(Stake::delegate(Origin::signed(5), 1, 10, None, 10, 10)); + let collator_state = Stake::candidate_state(1).unwrap(); + assert_eq!(collator_state.top_delegations.len(), 4usize); + assert!(collator_state.bottom_delegations.is_empty()); + }); +} + +#[test] +// MaxDelegatorsPerCandidate = 4 +fn candidate_pool_updates_when_total_counted_changes() { + ExtBuilder::default() + .with_default_staking_token(vec![ + (1, 20), + (3, 19), + (4, 20), + (5, 21), + (6, 22), + (7, 15), + (8, 16), + (9, 17), + (10, 18), + ]) + .with_default_token_candidates(vec![(1, 20)]) + .with_delegations(vec![ + (3, 1, 11), + (4, 1, 12), + (5, 1, 13), + (6, 1, 14), + (7, 1, 15), + (8, 1, 16), + (9, 1, 17), + (10, 1, 18), + ]) + .build() + .execute_with(|| { + fn is_candidate_pool_bond(account: u64, bond: u128) { + let pool = Stake::candidate_pool(); + for candidate in pool.0 { + if candidate.owner == account { + println!( + "Stake::candidate_state(candidate.owner): {:?}", + Stake::candidate_state(candidate.owner) + ); + assert_eq!(candidate.amount, bond); + } + } + } + // 15 + 16 + 17 + 18 + 20 = 86 (top 4 + self bond) + is_candidate_pool_bond(1, 86); + assert_ok!(Stake::schedule_delegator_bond_more(Origin::signed(3), 1, 8, None)); + roll_to(10); + // 3: 11 -> 19 => 3 is in top, bumps out 7 + assert_ok!(Stake::execute_delegation_request(Origin::signed(3), 3, 1, None)); + // 16 + 17 + 18 + 19 + 20 = 90 (top 4 + self bond) + is_candidate_pool_bond(1, 90); + assert_ok!(Stake::schedule_delegator_bond_more(Origin::signed(4), 1, 8, None)); + roll_to(20); + // 4: 12 -> 20 => 4 is in top, bumps out 8 + assert_ok!(Stake::execute_delegation_request(Origin::signed(4), 4, 1, None)); + // 17 + 18 + 19 + 20 + 20 = 94 (top 4 + self bond) + is_candidate_pool_bond(1, 94); + assert_ok!(Stake::schedule_delegator_bond_less(Origin::signed(10), 1, 3)); + roll_to(30); + // 10: 18 -> 15 => 10 bumped to bottom, 8 bumped to top (- 18 + 16 = -2 for count) + assert_ok!(Stake::execute_delegation_request(Origin::signed(10), 10, 1, None)); + // 16 + 17 + 19 + 20 + 20 = 92 (top 4 + self bond) + is_candidate_pool_bond(1, 92); + assert_ok!(Stake::schedule_delegator_bond_less(Origin::signed(9), 1, 4)); + roll_to(40); + assert_ok!(Stake::execute_delegation_request(Origin::signed(9), 9, 1, None)); + // 15 + 16 + 19 + 20 + 20 = 90 (top 4 + self bond) + is_candidate_pool_bond(1, 90); + }); +} + +#[test] +// MaxDelegatorsPerCandidate = 4 +fn only_top_collators_are_counted() { + ExtBuilder::default() + .with_default_staking_token(vec![ + (1, 20), + (3, 19), + (4, 20), + (5, 21), + (6, 22), + (7, 15), + (8, 16), + (9, 17), + (10, 18), + ]) + .with_default_token_candidates(vec![(1, 20)]) + .with_delegations(vec![ + (3, 1, 11), + (4, 1, 12), + (5, 1, 13), + (6, 1, 14), + (7, 1, 15), + (8, 1, 16), + (9, 1, 17), + (10, 1, 18), + ]) + .build() + .execute_with(|| { + // sanity check that 3-10 are delegators immediately + for i in 3..11 { + assert!(Stake::is_delegator(&i)); + } + let collator_state = Stake::candidate_state(1).unwrap(); + // 15 + 16 + 17 + 18 + 20 = 86 (top 4 + self bond) + assert_eq!(collator_state.total_counted, 86); + // 11 + 12 + 13 + 14 = 50 + assert_eq!(collator_state.total_counted + 50, collator_state.total_backing); + // bump bottom to the top + assert_ok!(Stake::schedule_delegator_bond_more(Origin::signed(3), 1, 8, None)); + assert_event_emitted!(Event::DelegationIncreaseScheduled(3, 1, 8, 2)); + roll_to(10); + assert_ok!(Stake::execute_delegation_request(Origin::signed(3), 3, 1, None)); + assert_event_emitted!(Event::DelegationIncreased(3, 1, 8, true)); + let collator_state = Stake::candidate_state(1).unwrap(); + // 16 + 17 + 18 + 19 + 20 = 90 (top 4 + self bond) + assert_eq!(collator_state.total_counted, 90); + // 12 + 13 + 14 + 15 = 54 + assert_eq!(collator_state.total_counted + 54, collator_state.total_backing); + // bump bottom to the top + assert_ok!(Stake::schedule_delegator_bond_more(Origin::signed(4), 1, 8, None)); + assert_event_emitted!(Event::DelegationIncreaseScheduled(4, 1, 8, 4)); + roll_to(20); + assert_ok!(Stake::execute_delegation_request(Origin::signed(4), 4, 1, None)); + assert_event_emitted!(Event::DelegationIncreased(4, 1, 8, true)); + let collator_state = Stake::candidate_state(1).unwrap(); + // 17 + 18 + 19 + 20 + 20 = 94 (top 4 + self bond) + assert_eq!(collator_state.total_counted, 94); + // 13 + 14 + 15 + 16 = 58 + assert_eq!(collator_state.total_counted + 58, collator_state.total_backing); + // bump bottom to the top + assert_ok!(Stake::schedule_delegator_bond_more(Origin::signed(5), 1, 8, None)); + assert_event_emitted!(Event::DelegationIncreaseScheduled(5, 1, 8, 6)); + roll_to(30); + assert_ok!(Stake::execute_delegation_request(Origin::signed(5), 5, 1, None)); + assert_event_emitted!(Event::DelegationIncreased(5, 1, 8, true)); + let collator_state = Stake::candidate_state(1).unwrap(); + // 18 + 19 + 20 + 21 + 20 = 98 (top 4 + self bond) + assert_eq!(collator_state.total_counted, 98); + // 14 + 15 + 16 + 17 = 62 + assert_eq!(collator_state.total_counted + 62, collator_state.total_backing); + // bump bottom to the top + assert_ok!(Stake::schedule_delegator_bond_more(Origin::signed(6), 1, 8, None)); + assert_event_emitted!(Event::DelegationIncreaseScheduled(6, 1, 8, 8)); + roll_to(40); + assert_ok!(Stake::execute_delegation_request(Origin::signed(6), 6, 1, None)); + assert_event_emitted!(Event::DelegationIncreased(6, 1, 8, true)); + let collator_state = Stake::candidate_state(1).unwrap(); + // 19 + 20 + 21 + 22 + 20 = 102 (top 4 + self bond) + assert_eq!(collator_state.total_counted, 102); + // 15 + 16 + 17 + 18 = 66 + assert_eq!(collator_state.total_counted + 66, collator_state.total_backing); + }); +} + +#[test] +fn delegation_events_convey_correct_position() { + ExtBuilder::default() + .with_default_staking_token(vec![ + (1, 100), + (2, 100), + (3, 100), + (4, 100), + (5, 100), + (6, 100), + (7, 100), + (8, 100), + (9, 100), + (10, 100), + ]) + .with_default_token_candidates(vec![(1, 20), (2, 20)]) + .with_delegations(vec![(3, 1, 11), (4, 1, 12), (5, 1, 13), (6, 1, 14)]) + .build() + .execute_with(|| { + let collator1_state = Stake::candidate_state(1).unwrap(); + // 11 + 12 + 13 + 14 + 20 = 70 (top 4 + self bond) + assert_eq!(collator1_state.total_counted, 70); + assert_eq!(collator1_state.total_counted, collator1_state.total_backing); + // Top delegations are full, new highest delegation is made + assert_ok!(Stake::delegate(Origin::signed(7), 1, 15, None, 10, 10)); + assert_event_emitted!(Event::Delegation( + 7, + 15, + 1, + DelegatorAdded::AddedToTop { new_total: 74 }, + )); + let collator1_state = Stake::candidate_state(1).unwrap(); + // 12 + 13 + 14 + 15 + 20 = 70 (top 4 + self bond) + assert_eq!(collator1_state.total_counted, 74); + // 11 = 11 + assert_eq!(collator1_state.total_counted + 11, collator1_state.total_backing); + // New delegation is added to the bottom + assert_ok!(Stake::delegate(Origin::signed(8), 1, 10, None, 10, 10)); + assert_event_emitted!(Event::Delegation(8, 10, 1, DelegatorAdded::AddedToBottom)); + let collator1_state = Stake::candidate_state(1).unwrap(); + // 12 + 13 + 14 + 15 + 20 = 70 (top 4 + self bond) + assert_eq!(collator1_state.total_counted, 74); + // 10 + 11 = 21 + assert_eq!(collator1_state.total_counted + 21, collator1_state.total_backing); + // 8 increases delegation to the top + assert_ok!(Stake::schedule_delegator_bond_more(Origin::signed(8), 1, 3, None)); + assert_event_emitted!(Event::DelegationIncreaseScheduled(8, 1, 3, 2)); + roll_to(10); + assert_ok!(Stake::execute_delegation_request(Origin::signed(8), 8, 1, None)); + assert_event_emitted!(Event::DelegationIncreased(8, 1, 3, true)); + let collator1_state = Stake::candidate_state(1).unwrap(); + // 13 + 13 + 14 + 15 + 20 = 75 (top 4 + self bond) + assert_eq!(collator1_state.total_counted, 75); + // 11 + 12 = 23 + assert_eq!(collator1_state.total_counted + 23, collator1_state.total_backing); + // 3 increases delegation but stays in bottom + assert_ok!(Stake::schedule_delegator_bond_more(Origin::signed(3), 1, 1, None)); + assert_event_emitted!(Event::DelegationIncreaseScheduled(3, 1, 1, 4)); + roll_to(20); + assert_ok!(Stake::execute_delegation_request(Origin::signed(3), 3, 1, None)); + assert_event_emitted!(Event::DelegationIncreased(3, 1, 1, false)); + let collator1_state = Stake::candidate_state(1).unwrap(); + // 13 + 13 + 14 + 15 + 20 = 75 (top 4 + self bond) + assert_eq!(collator1_state.total_counted, 75); + // 12 + 12 = 24 + assert_eq!(collator1_state.total_counted + 24, collator1_state.total_backing); + // 6 decreases delegation but stays in top + assert_ok!(Stake::schedule_delegator_bond_less(Origin::signed(6), 1, 2)); + assert_event_emitted!(Event::DelegationDecreaseScheduled(6, 1, 2, 6)); + roll_to(30); + assert_ok!(Stake::execute_delegation_request(Origin::signed(6), 6, 1, None)); + assert_event_emitted!(Event::DelegationDecreased(6, 1, 2, true)); + let collator1_state = Stake::candidate_state(1).unwrap(); + // 12 + 13 + 13 + 15 + 20 = 73 (top 4 + self bond)Æ’ + assert_eq!(collator1_state.total_counted, 73); + // 12 + 12 = 24 + assert_eq!(collator1_state.total_counted + 24, collator1_state.total_backing); + // 6 decreases delegation and is bumped to bottom + assert_ok!(Stake::schedule_delegator_bond_less(Origin::signed(6), 1, 1)); + assert_event_emitted!(Event::DelegationDecreaseScheduled(6, 1, 1, 8)); + roll_to(40); + assert_ok!(Stake::execute_delegation_request(Origin::signed(6), 6, 1, None)); + assert_event_emitted!(Event::DelegationDecreased(6, 1, 1, false)); + let collator1_state = Stake::candidate_state(1).unwrap(); + // 12 + 13 + 13 + 15 + 20 = 73 (top 4 + self bond) + assert_eq!(collator1_state.total_counted, 73); + // 11 + 12 = 23 + assert_eq!(collator1_state.total_counted + 23, collator1_state.total_backing); + }); +} + +#[test] +fn start_and_new_session_works() { + ExtBuilder::default() + .with_default_staking_token(vec![(1, 100), (2, 100), (3, 100), (4, 100), (5, 100)]) + .with_default_token_candidates(vec![(1, 20), (2, 20)]) + .build() + .execute_with(|| { + let mut expected = vec![]; + assert_eq_events!(expected.clone()); + + assert_eq!(Stake::at_stake(0, 1).bond, 20); + assert_eq!(Stake::at_stake(0, 2).bond, 20); + + assert_eq!(Stake::at_stake(1, 1).bond, 20); + assert_eq!(Stake::at_stake(1, 2).bond, 20); + + roll_to(5); + + assert_eq!(Stake::at_stake(2, 1).bond, 20); + assert_eq!(Stake::at_stake(2, 2).bond, 20); + + assert_eq!( + >::new_session(Default::default()), + Some(vec![1, 2]) + ); + + let mut new = vec![ + Event::CollatorChosen(2, 1, 20), + Event::CollatorChosen(2, 2, 20), + Event::NewRound(5, 1, 2, 40), + ]; + expected.append(&mut new); + assert_eq_events!(expected.clone()); + + assert_ok!(Stake::join_candidates( + Origin::signed(3), + 10u128, + 1u32, + None, + 2u32, + 10000u32 + )); + + roll_to(10); + + assert_eq!(Stake::at_stake(3, 1).bond, 20); + assert_eq!(Stake::at_stake(3, 2).bond, 20); + assert_eq!(Stake::at_stake(3, 3).bond, 10); + + assert_eq!( + >::new_session(Default::default()), + Some(vec![1, 2, 3]) + ); + + let mut new1 = vec![ + Event::JoinedCollatorCandidates(3, 10, 50), + Event::CollatorChosen(3, 1, 20), + Event::CollatorChosen(3, 2, 20), + Event::CollatorChosen(3, 3, 10), + Event::NewRound(10, 2, 3, 50), + ]; + expected.append(&mut new1); + assert_eq_events!(expected.clone()); + + assert_ok!(Stake::join_candidates( + Origin::signed(4), + 10u128, + 1u32, + None, + 3u32, + 10000u32 + )); + assert_ok!(Stake::join_candidates( + Origin::signed(5), + 10u128, + 1u32, + None, + 4u32, + 10000u32 + )); + + roll_to(15); + + assert_eq!(Stake::at_stake(4, 1).bond, 20); + assert_eq!(Stake::at_stake(4, 2).bond, 20); + assert_eq!(Stake::at_stake(4, 3).bond, 10); + assert_eq!(Stake::at_stake(4, 4).bond, 10); + assert_eq!(Stake::at_stake(4, 5).bond, 10); + + assert_eq!( + >::new_session(Default::default()), + Some(vec![1, 2, 3, 4, 5]) + ); + + let mut new2 = vec![ + Event::JoinedCollatorCandidates(4, 10, 60), + Event::JoinedCollatorCandidates(5, 10, 70), + Event::CollatorChosen(4, 1, 20), + Event::CollatorChosen(4, 2, 20), + Event::CollatorChosen(4, 3, 10), + Event::CollatorChosen(4, 4, 10), + Event::CollatorChosen(4, 5, 10), + Event::NewRound(15, 3, 5, 70), + ]; + expected.append(&mut new2); + assert_eq_events!(expected.clone()); + }); +} + +#[test] +fn adding_removing_staking_token_works() { + ExtBuilder::default() + .with_staking_tokens(vec![ + (999, 100, 0), + (1, 100, 1), + (2, 100, 2), + (3, 100, 3), + (4, 100, 4), + (5, 100, 5), + (6, 100, 6), + (7, 100, 7), + (8, 100, 1), + (9, 100, 2), + (10, 100, 3), + ]) + .with_candidates(vec![(1, 20, 1), (2, 20, 2)]) + .build() + .execute_with(|| { + assert_eq!(Stake::staking_liquidity_tokens().get(&1), Some(&Some((1u128, 1u128)))); + assert_eq!(Stake::staking_liquidity_tokens().get(&2), Some(&Some((2u128, 1u128)))); + + assert_eq!(Stake::staking_liquidity_tokens().get(&3), None); + assert_eq!(Stake::staking_liquidity_tokens().get(&4), None); + assert_eq!(Stake::staking_liquidity_tokens().get(&5), None); + assert_eq!(Stake::staking_liquidity_tokens().get(&6), None); + assert_eq!(Stake::staking_liquidity_tokens().get(&7), None); + + assert_ok!(Stake::join_candidates( + Origin::signed(8), + 10u128, + 1u32, + None, + 100u32, + 10000u32 + )); + assert_ok!(Stake::join_candidates( + Origin::signed(9), + 10u128, + 2u32, + None, + 100u32, + 10000u32 + )); + + assert_noop!( + Stake::join_candidates(Origin::signed(3), 10u128, 3u32, None, 100u32, 10000u32), + Error::::StakingLiquidityTokenNotListed + ); + assert_noop!( + Stake::join_candidates(Origin::signed(4), 10u128, 4u32, None, 100u32, 10000u32), + Error::::StakingLiquidityTokenNotListed + ); + assert_noop!( + Stake::join_candidates(Origin::signed(5), 10u128, 5u32, None, 100u32, 10000u32), + Error::::StakingLiquidityTokenNotListed + ); + assert_noop!( + Stake::join_candidates(Origin::signed(6), 10u128, 6u32, None, 100u32, 10000u32), + Error::::StakingLiquidityTokenNotListed + ); + assert_noop!( + Stake::join_candidates(Origin::signed(7), 10u128, 7u32, None, 100u32, 10000u32), + Error::::StakingLiquidityTokenNotListed + ); + + // Add 3 as a staking token + assert_ok!(Stake::add_staking_liquidity_token( + Origin::root(), + PairedOrLiquidityToken::Liquidity(3u32), + 100u32 + )); + assert_ok!(Stake::join_candidates( + Origin::signed(3), + 10u128, + 3u32, + None, + 100u32, + 10000u32 + )); + assert_eq!(Stake::staking_liquidity_tokens().get(&3), Some(&None)); + // Check that the rest remain the same + assert_eq!(Stake::staking_liquidity_tokens().get(&4), None); + assert_eq!(Stake::staking_liquidity_tokens().get(&5), None); + assert_eq!(Stake::staking_liquidity_tokens().get(&6), None); + assert_eq!(Stake::staking_liquidity_tokens().get(&7), None); + assert_noop!( + Stake::join_candidates(Origin::signed(4), 10u128, 4u32, None, 100u32, 10000u32), + Error::::StakingLiquidityTokenNotListed + ); + assert_noop!( + Stake::join_candidates(Origin::signed(5), 10u128, 5u32, None, 100u32, 10000u32), + Error::::StakingLiquidityTokenNotListed + ); + assert_noop!( + Stake::join_candidates(Origin::signed(6), 10u128, 6u32, None, 100u32, 10000u32), + Error::::StakingLiquidityTokenNotListed + ); + assert_noop!( + Stake::join_candidates(Origin::signed(7), 10u128, 7u32, None, 100u32, 10000u32), + Error::::StakingLiquidityTokenNotListed + ); + + roll_to(5); + + // Check that 3 gets valuated and others don't + assert_eq!(Stake::staking_liquidity_tokens().get(&3), Some(&Some((5u128, 1u128)))); + // Check that the rest remain the same + assert_eq!(Stake::staking_liquidity_tokens().get(&4), None); + assert_eq!(Stake::staking_liquidity_tokens().get(&5), None); + assert_eq!(Stake::staking_liquidity_tokens().get(&6), None); + assert_eq!(Stake::staking_liquidity_tokens().get(&7), None); + assert_noop!( + Stake::join_candidates(Origin::signed(4), 10u128, 4u32, None, 100u32, 10000u32), + Error::::StakingLiquidityTokenNotListed + ); + assert_noop!( + Stake::join_candidates(Origin::signed(5), 10u128, 5u32, None, 100u32, 10000u32), + Error::::StakingLiquidityTokenNotListed + ); + assert_noop!( + Stake::join_candidates(Origin::signed(6), 10u128, 6u32, None, 100u32, 10000u32), + Error::::StakingLiquidityTokenNotListed + ); + assert_noop!( + Stake::join_candidates(Origin::signed(7), 10u128, 7u32, None, 100u32, 10000u32), + Error::::StakingLiquidityTokenNotListed + ); + + // Adding same liquidity token doesn't work + assert_noop!( + Stake::add_staking_liquidity_token( + Origin::root(), + PairedOrLiquidityToken::Liquidity(3u32), + 100u32 + ), + Error::::StakingLiquidityTokenAlreadyListed + ); + // Remove a liquidity not yet added - noop + assert_noop!( + Stake::remove_staking_liquidity_token( + Origin::root(), + PairedOrLiquidityToken::Liquidity(4u32), + 100u32 + ), + Error::::StakingLiquidityTokenNotListed + ); + + // Remove a liquidity token + assert_ok!(Stake::remove_staking_liquidity_token( + Origin::root(), + PairedOrLiquidityToken::Liquidity(3u32), + 100u32 + )); + // Candidate cannot join using it. + assert_noop!( + Stake::join_candidates(Origin::signed(10), 10u128, 3u32, None, 100u32, 10000u32), + Error::::StakingLiquidityTokenNotListed + ); + + roll_to(10); + + // Removed token is no longer valuated + assert_eq!(Stake::staking_liquidity_tokens().get(&3), None); + + // Add more staking tokens + assert_ok!(Stake::add_staking_liquidity_token( + Origin::root(), + PairedOrLiquidityToken::Liquidity(4u32), + 100u32 + )); + assert_ok!(Stake::add_staking_liquidity_token( + Origin::root(), + PairedOrLiquidityToken::Liquidity(5u32), + 100u32 + )); + assert_ok!(Stake::add_staking_liquidity_token( + Origin::root(), + PairedOrLiquidityToken::Liquidity(6u32), + 100u32 + )); + assert_ok!(Stake::add_staking_liquidity_token( + Origin::root(), + PairedOrLiquidityToken::Liquidity(7u32), + 100u32 + )); + + // Candidates can join using the newly added tokens + assert_ok!(Stake::join_candidates( + Origin::signed(4), + 10u128, + 4u32, + None, + 100u32, + 10000u32 + )); + assert_ok!(Stake::join_candidates( + Origin::signed(6), + 10u128, + 6u32, + None, + 100u32, + 10000u32 + )); + assert_ok!(Stake::join_candidates( + Origin::signed(7), + 10u128, + 7u32, + None, + 100u32, + 10000u32 + )); + + roll_to(15); + + assert_eq!(Stake::staking_liquidity_tokens().get(&1), Some(&Some((1u128, 1u128)))); + assert_eq!(Stake::staking_liquidity_tokens().get(&2), Some(&Some((2u128, 1u128)))); + // No entry + assert_eq!(Stake::staking_liquidity_tokens().get(&3), None); + assert_eq!(Stake::staking_liquidity_tokens().get(&4), Some(&Some((1u128, 1u128)))); + // Valuated even though no candidates or delegates use it + assert_eq!(Stake::staking_liquidity_tokens().get(&5), Some(&Some((1u128, 2u128)))); + assert_eq!(Stake::staking_liquidity_tokens().get(&6), Some(&Some((1u128, 5u128)))); + // Valuated as zero + assert_eq!(Stake::staking_liquidity_tokens().get(&7), Some(&None)); + }); +} + +#[test] +fn delegation_tokens_work() { + ExtBuilder::default() + .with_staking_tokens(vec![ + (999, 100, 0), + (1, 100, 1), + (2, 100, 2), + (3, 100, 3), + (4, 100, 4), + (5, 100, 5), + (6, 100, 6), + (7, 100, 7), + (8, 100, 1), + (8, 100, 2), + (9, 100, 1), + (9, 100, 2), + (10, 100, 3), + (11, 100, 7), + ]) + .with_candidates(vec![ + (1, 10, 1), + (2, 10, 2), + (3, 20, 3), + (4, 20, 4), + (5, 20, 5), + (6, 20, 6), + (7, 20, 7), + ]) + .with_delegations(vec![(8, 1, 5), (8, 2, 10), (9, 1, 5), (10, 3, 10), (11, 7, 10)]) + .build() + .execute_with(|| { + assert_noop!( + Stake::delegate(Origin::signed(9), 3, 10, None, 100u32, 100u32), + DispatchError::Module(ModuleError { + index: 1, + error: [0; 4], + message: Some("BalanceTooLow") + }) + ); + }); +} + +#[test] +fn token_valuations_works() { + ExtBuilder::default() + .with_staking_tokens(vec![ + (999, 100, 0), + (1, 100, 1), + (2, 100, 2), + (3, 100, 3), + (4, 100, 4), + (5, 100, 5), + (6, 300, 6), + (7, 100, 7), + (8, 100, 1), + (8, 100, 2), + (9, 100, 1), + (9, 100, 2), + (10, 100, 3), + (11, 100, 7), + ]) + .with_candidates(vec![ + (1, 10, 1), + (2, 10, 2), + (3, 10, 3), + (4, 20, 4), + (5, 20, 5), + (6, 200, 6), + (7, 10, 7), + ]) + .with_delegations(vec![(8, 1, 5), (8, 2, 10), (9, 1, 5), (10, 3, 10), (11, 7, 10)]) + .build() + .execute_with(|| { + assert_ok!(Stake::set_total_selected(Origin::root(), 10)); + + assert_eq!( + Stake::candidate_pool().0, + vec![ + Bond { owner: 1, amount: 20, liquidity_token: 1 }, + Bond { owner: 2, amount: 20, liquidity_token: 2 }, + Bond { owner: 3, amount: 20, liquidity_token: 3 }, + Bond { owner: 4, amount: 20, liquidity_token: 4 }, + Bond { owner: 5, amount: 20, liquidity_token: 5 }, + Bond { owner: 6, amount: 200, liquidity_token: 6 }, + Bond { owner: 7, amount: 20, liquidity_token: 7 } + ] + ); + + assert_eq!(Stake::at_stake(0, 1).bond, 10); + assert_eq!(Stake::at_stake(0, 1).total, 20); + assert_eq!(Stake::at_stake(0, 2).bond, 10); + assert_eq!(Stake::at_stake(0, 2).total, 20); + assert_eq!(Stake::at_stake(0, 3).bond, 10); + assert_eq!(Stake::at_stake(0, 3).total, 20); + assert_eq!(Stake::at_stake(0, 4).bond, 20); + assert_eq!(Stake::at_stake(0, 4).total, 20); + assert_eq!(Stake::at_stake(0, 5).bond, 0); + assert_eq!(Stake::at_stake(0, 5).total, 0); + assert_eq!(Stake::at_stake(0, 6).bond, 200); + assert_eq!(Stake::at_stake(0, 6).total, 200); + + assert_eq!(Stake::at_stake(1, 1).bond, 10); + assert_eq!(Stake::at_stake(1, 1).total, 20); + assert_eq!(Stake::at_stake(1, 2).bond, 10); + assert_eq!(Stake::at_stake(1, 2).total, 20); + assert_eq!(Stake::at_stake(1, 3).bond, 10); + assert_eq!(Stake::at_stake(1, 3).total, 20); + assert_eq!(Stake::at_stake(1, 4).bond, 20); + assert_eq!(Stake::at_stake(1, 4).total, 20); + assert_eq!(Stake::at_stake(1, 5).bond, 0); + assert_eq!(Stake::at_stake(1, 5).total, 0); + assert_eq!(Stake::at_stake(1, 6).bond, 200); + assert_eq!(Stake::at_stake(1, 6).total, 200); + assert_eq!(Stake::at_stake(1, 7).bond, 0); + assert_eq!(Stake::at_stake(1, 7).total, 0); + + roll_to(5); + + assert_eq!(Stake::at_stake(2, 1).bond, 10); + assert_eq!(Stake::at_stake(2, 1).total, 20); + assert_eq!(Stake::at_stake(2, 2).bond, 10); + assert_eq!(Stake::at_stake(2, 2).total, 20); + assert_eq!(Stake::at_stake(2, 3).bond, 10); + assert_eq!(Stake::at_stake(2, 3).total, 20); + assert_eq!(Stake::at_stake(2, 4).bond, 20); + assert_eq!(Stake::at_stake(2, 4).total, 20); + assert_eq!(Stake::at_stake(2, 5).bond, 20); + assert_eq!(Stake::at_stake(2, 5).total, 20); + assert_eq!(Stake::at_stake(2, 6).bond, 200); + assert_eq!(Stake::at_stake(2, 6).total, 200); + assert_eq!(Stake::at_stake(2, 7).bond, 0); + assert_eq!(Stake::at_stake(2, 7).total, 0); + + let mut expected = vec![ + Event::TotalSelectedSet(5, 10), + Event::CollatorChosen(2, 1, 20), + Event::CollatorChosen(2, 2, 40), + Event::CollatorChosen(2, 3, 100), + Event::CollatorChosen(2, 4, 20), + Event::CollatorChosen(2, 5, 10), + Event::CollatorChosen(2, 6, 40), + Event::NewRound(5, 1, 6, 230), + ]; + assert_eq_events!(expected.clone()); + + assert_ok!(Stake::remove_staking_liquidity_token( + Origin::root(), + PairedOrLiquidityToken::Liquidity(3u32), + 100u32 + )); + + roll_to(10); + + assert_eq!(Stake::at_stake(3, 1).bond, 10); + assert_eq!(Stake::at_stake(3, 1).total, 20); + assert_eq!(Stake::at_stake(3, 2).bond, 10); + assert_eq!(Stake::at_stake(3, 2).total, 20); + assert_eq!(Stake::at_stake(3, 3).bond, 0); + assert_eq!(Stake::at_stake(3, 3).total, 0); + assert_eq!(Stake::at_stake(3, 4).bond, 20); + assert_eq!(Stake::at_stake(3, 4).total, 20); + assert_eq!(Stake::at_stake(3, 5).bond, 20); + assert_eq!(Stake::at_stake(3, 5).total, 20); + assert_eq!(Stake::at_stake(3, 6).bond, 200); + assert_eq!(Stake::at_stake(3, 6).total, 200); + assert_eq!(Stake::at_stake(3, 7).bond, 0); + assert_eq!(Stake::at_stake(3, 7).total, 0); + + let mut new = vec![ + Event::CollatorChosen(3, 1, 20), + Event::CollatorChosen(3, 2, 40), + Event::CollatorChosen(3, 4, 20), + Event::CollatorChosen(3, 5, 10), + Event::CollatorChosen(3, 6, 40), + Event::NewRound(10, 2, 5, 130), + ]; + expected.append(&mut new); + assert_eq_events!(expected.clone()); + }); +} + +#[test] +fn paired_or_liquidity_token_works() { + ExtBuilder::default() + .with_default_staking_token(vec![(1, 100), (2, 100), (3, 100), (4, 100), (5, 100)]) + .with_default_token_candidates(vec![(1, 20), (2, 20)]) + .build() + .execute_with(|| { + assert_ok!(Stake::add_staking_liquidity_token( + Origin::root(), + PairedOrLiquidityToken::Paired(7000u32), + 100u32 + )); + assert_eq!(Stake::staking_liquidity_tokens().get(&70), Some(&None)); + + assert_ok!(Stake::add_staking_liquidity_token( + Origin::root(), + PairedOrLiquidityToken::Liquidity(700u32), + 100u32 + )); + assert_eq!(Stake::staking_liquidity_tokens().get(&700), Some(&None)); + + assert_ok!(Stake::remove_staking_liquidity_token( + Origin::root(), + PairedOrLiquidityToken::Liquidity(70u32), + 100u32 + ),); + assert_eq!(Stake::staking_liquidity_tokens().get(&70), None); + + assert_ok!(Stake::remove_staking_liquidity_token( + Origin::root(), + PairedOrLiquidityToken::Paired(70000u32), + 100u32 + )); + assert_eq!(Stake::staking_liquidity_tokens().get(&700), None); + }); +} + +// Agrregator must be selected instead of collators under that aggregator +// The aggregator must have total weight of all the collators under him +#[test] +fn token_valuations_works_with_aggregators() { + ExtBuilder::default() + .with_staking_tokens(vec![ + (999, 100, 0), + (1, 100, 1), + (2, 100, 2), + (3, 100, 3), + (4, 100, 4), + (5, 100, 5), + (6, 300, 6), + (7, 100, 7), + (8, 100, 1), + (8, 100, 2), + (9, 100, 1), + (9, 100, 2), + (10, 100, 3), + (11, 100, 7), + ]) + .with_candidates(vec![(1, 20, 1), (2, 30, 2), (3, 10, 3)]) + .with_delegations(vec![(8, 1, 5), (8, 2, 10), (9, 1, 5), (10, 3, 10)]) + .build() + .execute_with(|| { + >::put(1u32); + assert_eq!(>::get(), 1u32); + + assert_eq!( + Stake::candidate_pool().0, + vec![ + Bond { owner: 1, amount: 30, liquidity_token: 1 }, + Bond { owner: 2, amount: 40, liquidity_token: 2 }, + Bond { owner: 3, amount: 20, liquidity_token: 3 }, + ] + ); + + assert_ok!(Stake::aggregator_update_metadata( + Origin::signed(4), + vec![1, 2], + MetadataUpdateAction::ExtendApprovedCollators + )); + assert_ok!(Stake::update_candidate_aggregator(Origin::signed(1), Some(4))); + assert_ok!(Stake::update_candidate_aggregator(Origin::signed(2), Some(4))); + + roll_to(5); + + assert_eq!(Stake::at_stake(2, 1).bond, 20); + assert_eq!(Stake::at_stake(2, 1).total, 30); + assert_eq!(Stake::at_stake(2, 2).bond, 30); + assert_eq!(Stake::at_stake(2, 2).total, 40); + + roll_to(10); + + assert_eq!(Stake::at_stake(3, 1).bond, 20); + assert_eq!(Stake::at_stake(3, 1).total, 30); + assert_eq!(Stake::at_stake(3, 2).bond, 30); + assert_eq!(Stake::at_stake(3, 2).total, 40); + + let mut expected = vec![ + Event::AggregatorMetadataUpdated(4), + Event::CandidateAggregatorUpdated(1, Some(4)), + Event::CandidateAggregatorUpdated(2, Some(4)), + Event::CollatorChosen(2, 1, 30), + Event::CollatorChosen(2, 2, 80), + Event::NewRound(5, 1, 2, 110), + Event::CollatorChosen(3, 1, 30), + Event::CollatorChosen(3, 2, 80), + Event::NewRound(10, 2, 2, 110), + ]; + assert_eq_events!(expected.clone()); + + assert_ok!(Stake::remove_staking_liquidity_token( + Origin::root(), + PairedOrLiquidityToken::Liquidity(2u32), + 100u32 + )); + + roll_to(15); + + assert_eq!(Stake::at_stake(4, 3).bond, 10); + assert_eq!(Stake::at_stake(4, 3).total, 20); + + let mut new = vec![Event::CollatorChosen(4, 3, 100), Event::NewRound(15, 3, 1, 100)]; + expected.append(&mut new); + assert_eq_events!(expected.clone()); + }); +} + +// Agrregator must be selected instead of collators under that aggregator +// The aggregator must have total weight of all the collators under him +#[test] +fn round_aggregator_info_is_updated() { + ExtBuilder::default() + .with_staking_tokens(vec![ + (999, 100, 0), + (1, 100, 1), + (2, 100, 2), + (3, 100, 3), + (4, 100, 4), + (5, 100, 5), + (6, 300, 6), + (7, 100, 7), + (8, 100, 1), + (8, 100, 2), + (9, 100, 1), + (9, 100, 2), + (10, 100, 3), + (11, 100, 7), + ]) + .with_candidates(vec![(1, 20, 1), (2, 30, 2), (3, 10, 3)]) + .with_delegations(vec![(8, 1, 5), (8, 2, 10), (9, 1, 5), (10, 3, 10)]) + .build() + .execute_with(|| { + >::put(1u32); + assert_eq!(>::get(), 1u32); + + assert_eq!( + Stake::candidate_pool().0, + vec![ + Bond { owner: 1, amount: 30, liquidity_token: 1 }, + Bond { owner: 2, amount: 40, liquidity_token: 2 }, + Bond { owner: 3, amount: 20, liquidity_token: 3 }, + ] + ); + + assert_ok!(Stake::aggregator_update_metadata( + Origin::signed(4), + vec![1, 2], + MetadataUpdateAction::ExtendApprovedCollators + )); + assert_ok!(Stake::update_candidate_aggregator(Origin::signed(1), Some(4))); + assert_ok!(Stake::update_candidate_aggregator(Origin::signed(2), Some(4))); + + roll_to(5); + + assert_eq!(Stake::at_stake(2, 1).bond, 20); + assert_eq!(Stake::at_stake(2, 1).total, 30); + assert_eq!(Stake::at_stake(2, 2).bond, 30); + assert_eq!(Stake::at_stake(2, 2).total, 40); + + let agggregator_collator_info: Vec<( + ::AccountId, + Balance, + )> = RoundAggregatorInfo::::get(2) + .unwrap() + .get(&4) + .unwrap() + .into_iter() + .map(|(a, b)| (a.clone(), b.clone())) + .collect::<_>(); + assert_eq!(agggregator_collator_info, [(1, 30), (2, 80)]); + }); +} + +// Agrregator must be selected instead of collators under that aggregator +// The aggregator must have total weight of all the collators under him +#[test] +fn payouts_with_aggregators_work() { + ExtBuilder::default() + .with_staking_tokens(vec![ + (999, 100, 0), + (1, 100, 1), + (2, 100, 2), + (3, 100, 3), + (4, 100, 4), + (5, 100, 5), + (6, 300, 6), + (7, 100, 7), + (8, 100, 1), + (8, 100, 2), + (9, 100, 1), + (9, 100, 2), + (10, 100, 3), + (11, 100, 7), + ]) + .with_candidates(vec![(1, 20, 1), (2, 30, 2), (3, 10, 3)]) + .with_delegations(vec![(8, 1, 5), (8, 2, 10), (9, 1, 5), (10, 3, 10)]) + .build() + .execute_with(|| { + >::put(2u32); + assert_eq!(>::get(), 2u32); + + assert_eq!( + Stake::candidate_pool().0, + vec![ + Bond { owner: 1, amount: 30, liquidity_token: 1 }, + Bond { owner: 2, amount: 40, liquidity_token: 2 }, + Bond { owner: 3, amount: 20, liquidity_token: 3 }, + ] + ); + + assert_ok!(Stake::aggregator_update_metadata( + Origin::signed(4), + vec![1, 2], + MetadataUpdateAction::ExtendApprovedCollators + )); + assert_ok!(Stake::update_candidate_aggregator(Origin::signed(1), Some(4))); + assert_ok!(Stake::update_candidate_aggregator(Origin::signed(2), Some(4))); + + roll_to(5); + + assert_eq!(Stake::at_stake(2, 1).bond, 20); + assert_eq!(Stake::at_stake(2, 1).total, 30); + assert_eq!(Stake::at_stake(2, 2).bond, 30); + assert_eq!(Stake::at_stake(2, 2).total, 40); + + let agggregator_collator_info: Vec<( + ::AccountId, + Balance, + )> = RoundAggregatorInfo::::get(2) + .unwrap() + .get(&4) + .unwrap() + .into_iter() + .map(|(a, b)| (a.clone(), b.clone())) + .collect::<_>(); + assert_eq!(agggregator_collator_info, [(1, 30), (2, 80)]); + + roll_to(10); + + set_author(2, 4, 100); + set_author(2, 3, 100); + + roll_to(20); + + let collator_info_reward_info = RoundCollatorRewardInfo::::get(1, 2).unwrap(); + let collator_info_reward_info_delegators: Vec<(AccountId, Balance)> = + collator_info_reward_info + .delegator_rewards + .into_iter() + .map(|(a, b)| (a.clone(), b.clone())) + .collect::<_>(); + assert_eq!(collator_info_reward_info.collator_reward, 29); + assert_eq!(collator_info_reward_info_delegators, [(8, 5), (9, 5)]); + + let collator_info_reward_info = RoundCollatorRewardInfo::::get(2, 2).unwrap(); + let collator_info_reward_info_delegators: Vec<(AccountId, Balance)> = + collator_info_reward_info + .delegator_rewards + .into_iter() + .map(|(a, b)| (a.clone(), b.clone())) + .collect::<_>(); + assert_eq!(collator_info_reward_info.collator_reward, 88); + assert_eq!(collator_info_reward_info_delegators, [(8, 22)]); + + let collator_info_reward_info = RoundCollatorRewardInfo::::get(3, 2).unwrap(); + let collator_info_reward_info_delegators: Vec<(AccountId, Balance)> = + collator_info_reward_info + .delegator_rewards + .into_iter() + .map(|(a, b)| (a.clone(), b.clone())) + .collect::<_>(); + assert_eq!(collator_info_reward_info.collator_reward, 91); + assert_eq!(collator_info_reward_info_delegators, [(10, 61)]); + }); +} + +#[test] +fn can_join_candidates_and_be_selected_with_native_token() { + ExtBuilder::default() + .with_default_staking_token(vec![ + (1, 1000), + (2, 1000), + (3, 1000), + (4, 1000), + (5, 1000), + (6, 1000), + (7, 33), + (8, 33), + (9, 33), + ]) + .with_default_token_candidates(vec![(1, 100), (2, 90), (3, 80), (4, 70), (5, 60), (6, 50)]) + .build() + .execute_with(|| { + roll_to(8); + + let expected = vec![ + Event::CollatorChosen(2, 1, 100), + Event::CollatorChosen(2, 2, 90), + Event::CollatorChosen(2, 3, 80), + Event::CollatorChosen(2, 4, 70), + Event::CollatorChosen(2, 5, 60), + Event::NewRound(5, 1, 5, 400), + ]; + assert_eq_events!(expected); + + assert_ok!(Stake::join_candidates( + Origin::signed(99999), + 1_000_000u128, + 0u32, + None, + 6u32, + 10000u32 + )); + assert_last_event!(MetaEvent::Stake(Event::JoinedCollatorCandidates( + 99999, + 1_000_000u128, + 1_000_000u128, + ))); + + roll_to(9); + + let expected = vec![ + Event::CollatorChosen(2, 1, 100), + Event::CollatorChosen(2, 2, 90), + Event::CollatorChosen(2, 3, 80), + Event::CollatorChosen(2, 4, 70), + Event::CollatorChosen(2, 5, 60), + Event::NewRound(5, 1, 5, 400), + Event::JoinedCollatorCandidates(99999, 1000000, 1000000), + Event::CollatorChosen(3, 1, 100), + Event::CollatorChosen(3, 2, 90), + Event::CollatorChosen(3, 3, 80), + Event::CollatorChosen(3, 4, 70), + Event::CollatorChosen(3, 99999, 500000), + Event::NewRound(10, 2, 5, 500340), + ]; + assert_eq_events!(expected); + + assert_eq!(StakeCurrency::reserved_balance(MGA_TOKEN_ID, &99999), 1_000_000u128); + }); +} + +#[test] +fn test_claiming_rewards_for_more_periods_than_asked_due_to_optimization_based_on_delegators_count() +{ + ExtBuilder::default() + .with_staking_tokens(vec![ + (999, 280, 0), + (1, 20, 1), + (2, 20, 1), + (3, 20, 1), + (4, 20, 1), + (5, 30, 1), + (6, 30, 1), + (7, 30, 1), + (8, 30, 1), + ]) + .with_default_token_candidates(vec![(1, 20), (2, 20), (3, 20), (4, 20)]) + .with_delegations(vec![(5, 1, 30), (6, 1, 30), (7, 1, 30), (8, 1, 30)]) + .build() + .execute_with(|| { + set_author(1, 1, 1); + set_author(1, 2, 1); + set_author(1, 3, 1); + set_author(1, 4, 1); + roll_to(6); + set_author(2, 1, 1); + set_author(2, 2, 1); + set_author(2, 3, 1); + set_author(2, 4, 1); + roll_to(21); + + assert_eq!(2, RoundCollatorRewardInfo::::iter_prefix(2).count()); + + Stake::payout_collator_rewards(crate::mock::RuntimeOrigin::signed(999), 2, Some(1)) + .unwrap(); + + let expected_events = vec![ + Event::CollatorChosen(2, 1, 140), + Event::CollatorChosen(2, 2, 20), + Event::CollatorChosen(2, 3, 20), + Event::CollatorChosen(2, 4, 20), + Event::NewRound(5, 1, 4, 200), + Event::CollatorChosen(3, 1, 140), + Event::CollatorChosen(3, 2, 20), + Event::CollatorChosen(3, 3, 20), + Event::CollatorChosen(3, 4, 20), + Event::NewRound(10, 2, 4, 200), + Event::CollatorChosen(4, 1, 140), + Event::CollatorChosen(4, 2, 20), + Event::CollatorChosen(4, 3, 20), + Event::CollatorChosen(4, 4, 20), + Event::NewRound(15, 3, 4, 200), + Event::CollatorChosen(5, 1, 140), + Event::CollatorChosen(5, 2, 20), + Event::CollatorChosen(5, 3, 20), + Event::CollatorChosen(5, 4, 20), + Event::NewRound(20, 4, 4, 200), + Event::Rewarded(1, 2, 76), + Event::Rewarded(2, 2, 76), + Event::CollatorRewardsDistributed(2, PayoutRounds::All), + ]; + assert_eq_events!(expected_events); + }); +} + +#[test] +fn test_claiming_rewards_for_exactly_one_period_when_delegators_count_is_equal_to_max_available() { + ExtBuilder::default() + .with_staking_tokens(vec![ + (999, 280, 0), + (1, 20, 1), + (2, 20, 1), + (3, 20, 1), + (4, 20, 1), + (5, 30, 1), + (6, 30, 1), + (7, 30, 1), + (8, 30, 1), + ]) + .with_default_token_candidates(vec![(1, 20), (2, 20), (3, 20), (4, 20)]) + .with_delegations(vec![(5, 1, 30), (6, 1, 30), (7, 1, 30), (8, 1, 30)]) + .build() + .execute_with(|| { + set_author(1, 1, 1); + set_author(1, 2, 1); + set_author(1, 3, 1); + set_author(1, 4, 1); + roll_to(6); + set_author(2, 1, 1); + set_author(2, 2, 1); + set_author(2, 3, 1); + set_author(2, 4, 1); + roll_to(21); + + assert_eq!(2, RoundCollatorRewardInfo::::iter_prefix(1).count()); + + Stake::payout_collator_rewards(crate::mock::RuntimeOrigin::signed(999), 1, Some(1)) + .unwrap(); + + let expected_events = vec![ + Event::CollatorChosen(2, 1, 140), + Event::CollatorChosen(2, 2, 20), + Event::CollatorChosen(2, 3, 20), + Event::CollatorChosen(2, 4, 20), + Event::NewRound(5, 1, 4, 200), + Event::CollatorChosen(3, 1, 140), + Event::CollatorChosen(3, 2, 20), + Event::CollatorChosen(3, 3, 20), + Event::CollatorChosen(3, 4, 20), + Event::NewRound(10, 2, 4, 200), + Event::CollatorChosen(4, 1, 140), + Event::CollatorChosen(4, 2, 20), + Event::CollatorChosen(4, 3, 20), + Event::CollatorChosen(4, 4, 20), + Event::NewRound(15, 3, 4, 200), + Event::CollatorChosen(5, 1, 140), + Event::CollatorChosen(5, 2, 20), + Event::CollatorChosen(5, 3, 20), + Event::CollatorChosen(5, 4, 20), + Event::NewRound(20, 4, 4, 200), + Event::Rewarded(1, 1, 23), + Event::DelegatorDueReward(1, 1, 5, 13), + Event::DelegatorDueReward(1, 1, 6, 13), + Event::DelegatorDueReward(1, 1, 7, 13), + Event::DelegatorDueReward(1, 1, 8, 13), + Event::CollatorRewardsDistributed(1, PayoutRounds::Partial(vec![1])), + ]; + assert_eq_events!(expected_events); + }); +} + +#[test] +fn test_claiming_rewards_for_all_periods_in_pesimistic_scenario_with_max_delegators_for_exactly_n_blocks( +) { + ExtBuilder::default() + .with_staking_tokens(vec![ + (999, 280, 0), + (1, 20, 1), + (2, 20, 1), + (3, 20, 1), + (4, 20, 1), + (5, 30, 1), + (6, 30, 1), + (7, 30, 1), + (8, 30, 1), + ]) + .with_default_token_candidates(vec![(1, 20), (2, 20), (3, 20), (4, 20)]) + .with_delegations(vec![(5, 1, 30), (6, 1, 30), (7, 1, 30), (8, 1, 30)]) + .build() + .execute_with(|| { + set_author(1, 1, 1); + set_author(1, 2, 1); + set_author(1, 3, 1); + set_author(1, 4, 1); + roll_to(6); + set_author(2, 1, 1); + set_author(2, 2, 1); + set_author(2, 3, 1); + set_author(2, 4, 1); + roll_to(21); + + assert_eq!(2, RoundCollatorRewardInfo::::iter_prefix(1).count()); + + Stake::payout_collator_rewards(crate::mock::RuntimeOrigin::signed(999), 1, Some(2)) + .unwrap(); + + let expected_events = vec![ + Event::CollatorChosen(2, 1, 140), + Event::CollatorChosen(2, 2, 20), + Event::CollatorChosen(2, 3, 20), + Event::CollatorChosen(2, 4, 20), + Event::NewRound(5, 1, 4, 200), + Event::CollatorChosen(3, 1, 140), + Event::CollatorChosen(3, 2, 20), + Event::CollatorChosen(3, 3, 20), + Event::CollatorChosen(3, 4, 20), + Event::NewRound(10, 2, 4, 200), + Event::CollatorChosen(4, 1, 140), + Event::CollatorChosen(4, 2, 20), + Event::CollatorChosen(4, 3, 20), + Event::CollatorChosen(4, 4, 20), + Event::NewRound(15, 3, 4, 200), + Event::CollatorChosen(5, 1, 140), + Event::CollatorChosen(5, 2, 20), + Event::CollatorChosen(5, 3, 20), + Event::CollatorChosen(5, 4, 20), + Event::NewRound(20, 4, 4, 200), + Event::Rewarded(1, 1, 23), + Event::DelegatorDueReward(1, 1, 5, 13), + Event::DelegatorDueReward(1, 1, 6, 13), + Event::DelegatorDueReward(1, 1, 7, 13), + Event::DelegatorDueReward(1, 1, 8, 13), + Event::Rewarded(2, 1, 23), + Event::DelegatorDueReward(2, 1, 5, 13), + Event::DelegatorDueReward(2, 1, 6, 13), + Event::DelegatorDueReward(2, 1, 7, 13), + Event::DelegatorDueReward(2, 1, 8, 13), + Event::CollatorRewardsDistributed(1, PayoutRounds::All), + ]; + assert_eq_events!(expected_events); + }); +} + +#[test] +fn test_triggre_error_when_there_are_no_rewards_to_payout() { + ExtBuilder::default() + .with_staking_tokens(vec![(999, 280, 0), (1, 20, 1)]) + // .with_default_token_candidates(vec![(1, 20), (2, 20), (3, 20), (4, 20)]) + // .with_delegations(vec![(5, 1, 30), (6,1,30), (7,1,30), (8,1,30), ]) + .build() + .execute_with(|| { + assert_noop!( + Stake::payout_collator_rewards(crate::mock::RuntimeOrigin::signed(999), 33, None), + Error::::CollatorRoundRewardsDNE + ); + }); +} diff --git a/gasp-node/pallets/parachain-staking/src/traits.rs b/gasp-node/pallets/parachain-staking/src/traits.rs new file mode 100644 index 000000000..f989fec9c --- /dev/null +++ b/gasp-node/pallets/parachain-staking/src/traits.rs @@ -0,0 +1,43 @@ +// Copyright 2019-2022 PureStake Inc. +// This file is part of Moonbeam. + +// Moonbeam 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. + +// Moonbeam 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 Moonbeam. If not, see . + +//! traits for parachain-staking + +pub trait OnCollatorPayout { + fn on_collator_payout( + for_round: crate::RoundIndex, + collator_id: AccountId, + amount: Balance, + ) -> frame_support::pallet_prelude::Weight; +} +impl OnCollatorPayout for () { + fn on_collator_payout( + _for_round: crate::RoundIndex, + _collator_id: AccountId, + _amount: Balance, + ) -> frame_support::pallet_prelude::Weight { + 0 + } +} + +pub trait OnNewRound { + fn on_new_round(round_index: crate::RoundIndex) -> frame_support::pallet_prelude::Weight; +} +impl OnNewRound for () { + fn on_new_round(_round_index: crate::RoundIndex) -> frame_support::pallet_prelude::Weight { + 0 + } +} diff --git a/gasp-node/pallets/parachain-staking/src/types.rs b/gasp-node/pallets/parachain-staking/src/types.rs new file mode 100644 index 000000000..b5c0e02ea --- /dev/null +++ b/gasp-node/pallets/parachain-staking/src/types.rs @@ -0,0 +1,1545 @@ +// Copyright 2019-2022 PureStake Inc. +// This file is part of Moonbeam. + +// Moonbeam 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. + +// Moonbeam 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 Moonbeam. If not, see . + +//! Types for parachain-staking + +use crate::{ + set::OrderedSet, BalanceOf, BottomDelegations, CandidateInfo, Config, DelegatorState, Error, + Event, Pallet, Round, RoundIndex, TopDelegations, Total, +}; +use frame_support::{pallet_prelude::*, traits::ReservableCurrency}; +use parity_scale_codec::{Decode, Encode}; +use sp_runtime::{ + traits::{AtLeast32BitUnsigned, Saturating, Zero}, + Perbill, Percent, RuntimeDebug, +}; +use sp_std::{cmp::Ordering, collections::btree_map::BTreeMap, prelude::*}; + +#[derive(Clone, Encode, Decode, RuntimeDebug, TypeInfo)] +pub struct Bond { + pub owner: AccountId, + pub amount: Balance, +} + +impl Default for Bond { + fn default() -> Bond { + Bond { + owner: A::decode(&mut sp_runtime::traits::TrailingZeroInput::zeroes()) + .expect("infinite length input; no invalid inputs for type; qed"), + amount: B::default(), + } + } +} + +impl Bond { + pub fn from_owner(owner: A) -> Self { + Bond { + owner, + amount: B::default(), + } + } +} + +impl Eq for Bond {} + +impl Ord for Bond { + fn cmp(&self, other: &Self) -> Ordering { + self.owner.cmp(&other.owner) + } +} + +impl PartialOrd for Bond { + fn partial_cmp(&self, other: &Self) -> Option { + Some(self.cmp(other)) + } +} + +impl PartialEq for Bond { + fn eq(&self, other: &Self) -> bool { + self.owner == other.owner + } +} + +#[derive(Copy, Clone, PartialEq, Eq, Encode, Decode, RuntimeDebug, TypeInfo)] +/// The activity status of the collator +pub enum CollatorStatus { + /// Committed to be online and producing valid blocks (not equivocating) + Active, + /// Temporarily inactive and excused for inactivity + Idle, + /// Bonded until the inner round + Leaving(RoundIndex), +} + +impl Default for CollatorStatus { + fn default() -> CollatorStatus { + CollatorStatus::Active + } +} + +#[derive(Encode, Decode, RuntimeDebug, TypeInfo)] +/// Snapshot of collator state at the start of the round for which they are selected +pub struct CollatorSnapshot { + /// The total value locked by the collator. + pub bond: Balance, + + /// The rewardable delegations. This list is a subset of total delegators, where certain + /// delegators are adjusted based on their scheduled + /// [DelegationChange::Revoke] or [DelegationChange::Decrease] action. + pub delegations: Vec>, + + /// The total counted value locked for the collator, including the self bond + total staked by + /// top delegators. + pub total: Balance, +} + +impl PartialEq for CollatorSnapshot { + fn eq(&self, other: &Self) -> bool { + let must_be_true = self.bond == other.bond && self.total == other.total; + if !must_be_true { + return false; + } + for ( + Bond { + owner: o1, + amount: a1, + }, + Bond { + owner: o2, + amount: a2, + }, + ) in self.delegations.iter().zip(other.delegations.iter()) + { + if o1 != o2 || a1 != a2 { + return false; + } + } + true + } +} + +impl Default for CollatorSnapshot { + fn default() -> CollatorSnapshot { + CollatorSnapshot { + bond: B::default(), + delegations: Vec::new(), + total: B::default(), + } + } +} + +#[derive(Default, Encode, Decode, RuntimeDebug, TypeInfo)] +/// Info needed to make delayed payments to stakers after round end +pub struct DelayedPayout { + /// Total round reward (result of compute_issuance() at round end) + pub round_issuance: Balance, + /// The total inflation paid this round to stakers (e.g. less parachain bond fund) + pub total_staking_reward: Balance, + /// Snapshot of collator commission rate at the end of the round + pub collator_commission: Perbill, +} + +#[derive(Encode, Decode, RuntimeDebug, TypeInfo)] +/// DEPRECATED +/// Collator state with commission fee, bonded stake, and delegations +pub struct Collator2 { + /// The account of this collator + pub id: AccountId, + /// This collator's self stake. + pub bond: Balance, + /// Set of all nominator AccountIds (to prevent >1 nomination per AccountId) + pub nominators: OrderedSet, + /// Top T::MaxDelegatorsPerCollator::get() nominators, ordered greatest to least + pub top_nominators: Vec>, + /// Bottom nominators (unbounded), ordered least to greatest + pub bottom_nominators: Vec>, + /// Sum of top delegations + self.bond + pub total_counted: Balance, + /// Sum of all delegations + self.bond = (total_counted + uncounted) + pub total_backing: Balance, + /// Current status of the collator + pub state: CollatorStatus, +} + +impl From> for CollatorCandidate { + fn from(other: Collator2) -> CollatorCandidate { + CollatorCandidate { + id: other.id, + bond: other.bond, + delegators: other.nominators, + top_delegations: other.top_nominators, + bottom_delegations: other.bottom_nominators, + total_counted: other.total_counted, + total_backing: other.total_backing, + request: None, + state: other.state, + } + } +} + +#[derive(PartialEq, Clone, Copy, Encode, Decode, RuntimeDebug, TypeInfo)] +/// Request scheduled to change the collator candidate self-bond +pub struct CandidateBondLessRequest { + pub amount: Balance, + pub when_executable: RoundIndex, +} + +#[derive(Encode, Decode, RuntimeDebug, TypeInfo)] +/// DEPRECATED, replaced by `CandidateMetadata` and two storage instances of `Delegations` +/// Collator candidate state with self bond + delegations +pub struct CollatorCandidate { + /// The account of this collator + pub id: AccountId, + /// This collator's self stake. + pub bond: Balance, + /// Set of all delegator AccountIds (to prevent >1 delegation per AccountId) + pub delegators: OrderedSet, + /// Top T::MaxDelegatorsPerCollator::get() delegations, ordered greatest to least + pub top_delegations: Vec>, + /// Bottom delegations (unbounded), ordered least to greatest + pub bottom_delegations: Vec>, + /// Sum of top delegations + self.bond + pub total_counted: Balance, + /// Sum of all delegations + self.bond = (total_counted + uncounted) + pub total_backing: Balance, + /// Maximum 1 pending request to decrease candidate self bond at any given time + pub request: Option>, + /// Current status of the collator + pub state: CollatorStatus, +} + +#[derive(Clone, Encode, Decode, RuntimeDebug, TypeInfo)] +/// Type for top and bottom delegation storage item +pub struct Delegations { + pub delegations: Vec>, + pub total: Balance, +} + +impl Default for Delegations { + fn default() -> Delegations { + Delegations { + delegations: Vec::new(), + total: B::default(), + } + } +} + +impl + Delegations +{ + pub fn sort_greatest_to_least(&mut self) { + self.delegations.sort_by(|a, b| b.amount.cmp(&a.amount)); + } + /// Insert sorted greatest to least and increase .total accordingly + /// Insertion respects first come first serve so new delegations are pushed after existing + /// delegations if the amount is the same + pub fn insert_sorted_greatest_to_least(&mut self, delegation: Bond) { + self.total = self.total.saturating_add(delegation.amount); + // if delegations nonempty && last_element == delegation.amount => push input and return + if !self.delegations.is_empty() { + // if last_element == delegation.amount => push the delegation and return early + if self.delegations[self.delegations.len() - 1].amount == delegation.amount { + self.delegations.push(delegation); + // early return + return; + } + } + // else binary search insertion + match self + .delegations + .binary_search_by(|x| delegation.amount.cmp(&x.amount)) + { + // sorted insertion on sorted vec + // enforces first come first serve for equal bond amounts + Ok(i) => { + let mut new_index = i + 1; + while new_index <= (self.delegations.len() - 1) { + if self.delegations[new_index].amount == delegation.amount { + new_index = new_index.saturating_add(1); + } else { + self.delegations.insert(new_index, delegation); + return; + } + } + self.delegations.push(delegation) + } + Err(i) => self.delegations.insert(i, delegation), + } + } + /// Return the capacity status for top delegations + pub fn top_capacity(&self) -> CapacityStatus { + match &self.delegations { + x if x.len() as u32 >= T::MaxTopDelegationsPerCandidate::get() => CapacityStatus::Full, + x if x.is_empty() => CapacityStatus::Empty, + _ => CapacityStatus::Partial, + } + } + /// Return the capacity status for bottom delegations + pub fn bottom_capacity(&self) -> CapacityStatus { + match &self.delegations { + x if x.len() as u32 >= T::MaxBottomDelegationsPerCandidate::get() => { + CapacityStatus::Full + } + x if x.is_empty() => CapacityStatus::Empty, + _ => CapacityStatus::Partial, + } + } + /// Return last delegation amount without popping the delegation + pub fn lowest_delegation_amount(&self) -> Balance { + self.delegations + .last() + .map(|x| x.amount) + .unwrap_or(Balance::zero()) + } + /// Return highest delegation amount + pub fn highest_delegation_amount(&self) -> Balance { + self.delegations + .first() + .map(|x| x.amount) + .unwrap_or(Balance::zero()) + } +} + +#[derive(PartialEq, Encode, Decode, RuntimeDebug, TypeInfo)] +/// Capacity status for top or bottom delegations +pub enum CapacityStatus { + /// Reached capacity + Full, + /// Empty aka contains no delegations + Empty, + /// Partially full (nonempty and not full) + Partial, +} + +#[derive(Encode, Decode, RuntimeDebug, TypeInfo)] +/// All candidate info except the top and bottom delegations +pub struct CandidateMetadata { + /// This candidate's self bond amount + pub bond: Balance, + /// Total number of delegations to this candidate + pub delegation_count: u32, + /// Self bond + sum of top delegations + pub total_counted: Balance, + /// The smallest top delegation amount + pub lowest_top_delegation_amount: Balance, + /// The highest bottom delegation amount + pub highest_bottom_delegation_amount: Balance, + /// The smallest bottom delegation amount + pub lowest_bottom_delegation_amount: Balance, + /// Capacity status for top delegations + pub top_capacity: CapacityStatus, + /// Capacity status for bottom delegations + pub bottom_capacity: CapacityStatus, + /// Maximum 1 pending request to decrease candidate self bond at any given time + pub request: Option>, + /// Current status of the collator + pub status: CollatorStatus, +} + +impl< + Balance: Copy + + Zero + + PartialOrd + + sp_std::ops::AddAssign + + sp_std::ops::SubAssign + + sp_std::ops::Sub + + sp_std::fmt::Debug + + Saturating, + > CandidateMetadata +{ + pub fn new(bond: Balance) -> Self { + CandidateMetadata { + bond, + delegation_count: 0u32, + total_counted: bond, + lowest_top_delegation_amount: Zero::zero(), + highest_bottom_delegation_amount: Zero::zero(), + lowest_bottom_delegation_amount: Zero::zero(), + top_capacity: CapacityStatus::Empty, + bottom_capacity: CapacityStatus::Empty, + request: None, + status: CollatorStatus::Active, + } + } + pub fn is_active(&self) -> bool { + matches!(self.status, CollatorStatus::Active) + } + pub fn is_leaving(&self) -> bool { + matches!(self.status, CollatorStatus::Leaving(_)) + } + pub fn schedule_leave(&mut self) -> Result<(RoundIndex, RoundIndex), DispatchError> { + ensure!(!self.is_leaving(), Error::::CandidateAlreadyLeaving); + let now = >::get().current; + let when = now + T::LeaveCandidatesDelay::get(); + self.status = CollatorStatus::Leaving(when); + Ok((now, when)) + } + pub fn can_leave(&self) -> DispatchResult { + if let CollatorStatus::Leaving(when) = self.status { + ensure!( + >::get().current >= when, + Error::::CandidateCannotLeaveYet + ); + Ok(()) + } else { + Err(Error::::CandidateNotLeaving.into()) + } + } + pub fn go_offline(&mut self) { + self.status = CollatorStatus::Idle; + } + pub fn go_online(&mut self) { + self.status = CollatorStatus::Active; + } + pub fn bond_more(&mut self, who: T::AccountId, more: Balance) -> DispatchResult + where + BalanceOf: From, + { + T::Currency::reserve(&who, more.into())?; + let new_total = >::get().saturating_add(more.into()); + >::put(new_total); + self.bond = self.bond.saturating_add(more); + self.total_counted = self.total_counted.saturating_add(more); + >::deposit_event(Event::CandidateBondedMore { + candidate: who.clone(), + amount: more.into(), + new_total_bond: self.bond.into(), + }); + Ok(()) + } + /// Schedule executable decrease of collator candidate self bond + /// Returns the round at which the collator can execute the pending request + pub fn schedule_bond_less( + &mut self, + less: Balance, + ) -> Result + where + BalanceOf: Into, + { + // ensure no pending request + ensure!( + self.request.is_none(), + Error::::PendingCandidateRequestAlreadyExists + ); + // ensure bond above min after decrease + ensure!(self.bond > less, Error::::CandidateBondBelowMin); + ensure!( + self.bond - less >= T::MinCandidateStk::get().into(), + Error::::CandidateBondBelowMin + ); + let when_executable = >::get().current + T::CandidateBondLessDelay::get(); + self.request = Some(CandidateBondLessRequest { + amount: less, + when_executable, + }); + Ok(when_executable) + } + /// Execute pending request to decrease the collator self bond + /// Returns the event to be emitted + pub fn execute_bond_less(&mut self, who: T::AccountId) -> DispatchResult + where + BalanceOf: From, + { + let request = self + .request + .ok_or(Error::::PendingCandidateRequestsDNE)?; + ensure!( + request.when_executable <= >::get().current, + Error::::PendingCandidateRequestNotDueYet + ); + T::Currency::unreserve(&who, request.amount.into()); + let new_total_staked = >::get().saturating_sub(request.amount.into()); + >::put(new_total_staked); + // Arithmetic assumptions are self.bond > less && self.bond - less > CollatorMinBond + // (assumptions enforced by `schedule_bond_less`; if storage corrupts, must re-verify) + self.bond = self.bond.saturating_sub(request.amount); + self.total_counted = self.total_counted.saturating_sub(request.amount); + let event = Event::CandidateBondedLess { + candidate: who.clone().into(), + amount: request.amount.into(), + new_bond: self.bond.into(), + }; + // reset s.t. no pending request + self.request = None; + // update candidate pool value because it must change if self bond changes + if self.is_active() { + Pallet::::update_active(who.into(), self.total_counted.into()); + } + Pallet::::deposit_event(event); + Ok(()) + } + /// Cancel candidate bond less request + pub fn cancel_bond_less(&mut self, who: T::AccountId) -> DispatchResult + where + BalanceOf: From, + { + let request = self + .request + .ok_or(Error::::PendingCandidateRequestsDNE)?; + let event = Event::CancelledCandidateBondLess { + candidate: who.clone().into(), + amount: request.amount.into(), + execute_round: request.when_executable, + }; + self.request = None; + Pallet::::deposit_event(event); + Ok(()) + } + /// Reset top delegations metadata + pub fn reset_top_data( + &mut self, + candidate: T::AccountId, + top_delegations: &Delegations>, + ) where + BalanceOf: Into + From, + { + self.lowest_top_delegation_amount = top_delegations.lowest_delegation_amount().into(); + self.top_capacity = top_delegations.top_capacity::(); + let old_total_counted = self.total_counted; + self.total_counted = self.bond.saturating_add(top_delegations.total.into()); + // CandidatePool value for candidate always changes if top delegations total changes + // so we moved the update into this function to deduplicate code and patch a bug that + // forgot to apply the update when increasing top delegation + if old_total_counted != self.total_counted && self.is_active() { + Pallet::::update_active(candidate, self.total_counted.into()); + } + } + /// Reset bottom delegations metadata + pub fn reset_bottom_data( + &mut self, + bottom_delegations: &Delegations>, + ) where + BalanceOf: Into, + { + self.lowest_bottom_delegation_amount = bottom_delegations.lowest_delegation_amount().into(); + self.highest_bottom_delegation_amount = + bottom_delegations.highest_delegation_amount().into(); + self.bottom_capacity = bottom_delegations.bottom_capacity::(); + } + /// Add delegation + /// Returns whether delegator was added and an optional negative total counted remainder + /// for if a bottom delegation was kicked + /// MUST ensure no delegation exists for this candidate in the `DelegatorState` before call + pub fn add_delegation( + &mut self, + candidate: &T::AccountId, + delegation: Bond>, + ) -> Result<(DelegatorAdded, Option), DispatchError> + where + BalanceOf: Into + From, + { + let mut less_total_staked = None; + let delegator_added = match self.top_capacity { + CapacityStatus::Full => { + // top is full, insert into top iff the lowest_top < amount + if self.lowest_top_delegation_amount < delegation.amount.into() { + // bumps lowest top to the bottom inside this function call + less_total_staked = self.add_top_delegation::(candidate, delegation); + DelegatorAdded::AddedToTop { + new_total: self.total_counted, + } + } else { + // if bottom is full, only insert if greater than lowest bottom (which will + // be bumped out) + if matches!(self.bottom_capacity, CapacityStatus::Full) { + ensure!( + delegation.amount.into() > self.lowest_bottom_delegation_amount, + Error::::CannotDelegateLessThanOrEqualToLowestBottomWhenFull + ); + // need to subtract from total staked + less_total_staked = Some(self.lowest_bottom_delegation_amount); + } + // insert into bottom + self.add_bottom_delegation::(false, candidate, delegation); + DelegatorAdded::AddedToBottom + } + } + // top is either empty or partially full + _ => { + self.add_top_delegation::(candidate, delegation); + DelegatorAdded::AddedToTop { + new_total: self.total_counted, + } + } + }; + Ok((delegator_added, less_total_staked)) + } + /// Add delegation to top delegation + /// Returns Option + /// Only call if lowest top delegation is less than delegation.amount || !top_full + pub fn add_top_delegation( + &mut self, + candidate: &T::AccountId, + delegation: Bond>, + ) -> Option + where + BalanceOf: Into + From, + { + let mut less_total_staked = None; + let mut top_delegations = >::get(candidate) + .expect("CandidateInfo existence => TopDelegations existence"); + let max_top_delegations_per_candidate = T::MaxTopDelegationsPerCandidate::get(); + if top_delegations.delegations.len() as u32 == max_top_delegations_per_candidate { + // pop lowest top delegation + let new_bottom_delegation = top_delegations.delegations.pop().expect(""); + top_delegations.total = top_delegations + .total + .saturating_sub(new_bottom_delegation.amount); + if matches!(self.bottom_capacity, CapacityStatus::Full) { + less_total_staked = Some(self.lowest_bottom_delegation_amount); + } + self.add_bottom_delegation::(true, candidate, new_bottom_delegation); + } + // insert into top + top_delegations.insert_sorted_greatest_to_least(delegation); + // update candidate info + self.reset_top_data::(candidate.clone(), &top_delegations); + if less_total_staked.is_none() { + // only increment delegation count if we are not kicking a bottom delegation + self.delegation_count = self.delegation_count.saturating_add(1u32); + } + >::insert(&candidate, top_delegations); + less_total_staked + } + /// Add delegation to bottom delegations + /// Check before call that if capacity is full, inserted delegation is higher than lowest + /// bottom delegation (and if so, need to adjust the total storage item) + /// CALLER MUST ensure(lowest_bottom_to_be_kicked.amount < delegation.amount) + pub fn add_bottom_delegation( + &mut self, + bumped_from_top: bool, + candidate: &T::AccountId, + delegation: Bond>, + ) where + BalanceOf: Into + From, + { + let mut bottom_delegations = >::get(candidate) + .expect("CandidateInfo existence => BottomDelegations existence"); + // if bottom is full, kick the lowest bottom (which is expected to be lower than input + // as per check) + let increase_delegation_count = if bottom_delegations.delegations.len() as u32 + == T::MaxBottomDelegationsPerCandidate::get() + { + let lowest_bottom_to_be_kicked = bottom_delegations + .delegations + .pop() + .expect("if at full capacity (>0), then >0 bottom delegations exist; qed"); + // EXPECT lowest_bottom_to_be_kicked.amount < delegation.amount enforced by caller + // if lowest_bottom_to_be_kicked.amount == delegation.amount, we will still kick + // the lowest bottom to enforce first come first served + bottom_delegations.total = bottom_delegations + .total + .saturating_sub(lowest_bottom_to_be_kicked.amount); + // update delegator state + // unreserve kicked bottom + T::Currency::unreserve( + &lowest_bottom_to_be_kicked.owner, + lowest_bottom_to_be_kicked.amount, + ); + // total staked is updated via propagation of lowest bottom delegation amount prior + // to call + let mut delegator_state = >::get(&lowest_bottom_to_be_kicked.owner) + .expect("Delegation existence => DelegatorState existence"); + let leaving = delegator_state.delegations.0.len() == 1usize; + delegator_state.rm_delegation(candidate); + >::delegation_remove_request_with_state( + &candidate, + &lowest_bottom_to_be_kicked.owner, + &mut delegator_state, + ); + + Pallet::::deposit_event(Event::DelegationKicked { + delegator: lowest_bottom_to_be_kicked.owner.clone(), + candidate: candidate.clone(), + unstaked_amount: lowest_bottom_to_be_kicked.amount, + }); + if leaving { + >::remove(&lowest_bottom_to_be_kicked.owner); + Pallet::::deposit_event(Event::DelegatorLeft { + delegator: lowest_bottom_to_be_kicked.owner, + unstaked_amount: lowest_bottom_to_be_kicked.amount, + }); + } else { + >::insert(&lowest_bottom_to_be_kicked.owner, delegator_state); + } + false + } else { + !bumped_from_top + }; + // only increase delegation count if new bottom delegation (1) doesn't come from top && + // (2) doesn't pop the lowest delegation from the bottom + if increase_delegation_count { + self.delegation_count = self.delegation_count.saturating_add(1u32); + } + bottom_delegations.insert_sorted_greatest_to_least(delegation); + self.reset_bottom_data::(&bottom_delegations); + >::insert(candidate, bottom_delegations); + } + /// Remove delegation + /// Removes from top if amount is above lowest top or top is not full + /// Return Ok(if_total_counted_changed) + pub fn rm_delegation_if_exists( + &mut self, + candidate: &T::AccountId, + delegator: T::AccountId, + amount: Balance, + ) -> Result + where + BalanceOf: Into + From, + { + let amount_geq_lowest_top = amount >= self.lowest_top_delegation_amount; + let top_is_not_full = !matches!(self.top_capacity, CapacityStatus::Full); + let lowest_top_eq_highest_bottom = + self.lowest_top_delegation_amount == self.highest_bottom_delegation_amount; + let delegation_dne_err: DispatchError = Error::::DelegationDNE.into(); + if top_is_not_full || (amount_geq_lowest_top && !lowest_top_eq_highest_bottom) { + self.rm_top_delegation::(candidate, delegator) + } else if amount_geq_lowest_top && lowest_top_eq_highest_bottom { + let result = self.rm_top_delegation::(candidate, delegator.clone()); + if result == Err(delegation_dne_err) { + // worst case removal + self.rm_bottom_delegation::(candidate, delegator) + } else { + result + } + } else { + self.rm_bottom_delegation::(candidate, delegator) + } + } + /// Remove top delegation, bumps top bottom delegation if exists + pub fn rm_top_delegation( + &mut self, + candidate: &T::AccountId, + delegator: T::AccountId, + ) -> Result + where + BalanceOf: Into + From, + { + let old_total_counted = self.total_counted; + // remove top delegation + let mut top_delegations = >::get(candidate) + .expect("CandidateInfo exists => TopDelegations exists"); + let mut actual_amount_option: Option> = None; + top_delegations.delegations = top_delegations + .delegations + .clone() + .into_iter() + .filter(|d| { + if d.owner != delegator { + true + } else { + actual_amount_option = Some(d.amount); + false + } + }) + .collect(); + let actual_amount = actual_amount_option.ok_or(Error::::DelegationDNE)?; + top_delegations.total = top_delegations.total.saturating_sub(actual_amount); + // if bottom nonempty => bump top bottom to top + if !matches!(self.bottom_capacity, CapacityStatus::Empty) { + let mut bottom_delegations = + >::get(candidate).expect("bottom is nonempty as just checked"); + // expect already stored greatest to least by bond amount + let highest_bottom_delegation = bottom_delegations.delegations.remove(0); + bottom_delegations.total = bottom_delegations + .total + .saturating_sub(highest_bottom_delegation.amount); + self.reset_bottom_data::(&bottom_delegations); + >::insert(candidate, bottom_delegations); + // insert highest bottom into top delegations + top_delegations.insert_sorted_greatest_to_least(highest_bottom_delegation); + } + // update candidate info + self.reset_top_data::(candidate.clone(), &top_delegations); + self.delegation_count = self.delegation_count.saturating_sub(1u32); + >::insert(candidate, top_delegations); + // return whether total counted changed + Ok(old_total_counted == self.total_counted) + } + /// Remove bottom delegation + /// Returns if_total_counted_changed: bool + pub fn rm_bottom_delegation( + &mut self, + candidate: &T::AccountId, + delegator: T::AccountId, + ) -> Result + where + BalanceOf: Into, + { + // remove bottom delegation + let mut bottom_delegations = >::get(candidate) + .expect("CandidateInfo exists => BottomDelegations exists"); + let mut actual_amount_option: Option> = None; + bottom_delegations.delegations = bottom_delegations + .delegations + .clone() + .into_iter() + .filter(|d| { + if d.owner != delegator { + true + } else { + actual_amount_option = Some(d.amount); + false + } + }) + .collect(); + let actual_amount = actual_amount_option.ok_or(Error::::DelegationDNE)?; + bottom_delegations.total = bottom_delegations.total.saturating_sub(actual_amount); + // update candidate info + self.reset_bottom_data::(&bottom_delegations); + self.delegation_count = self.delegation_count.saturating_sub(1u32); + >::insert(candidate, bottom_delegations); + Ok(false) + } + /// Increase delegation amount + pub fn increase_delegation( + &mut self, + candidate: &T::AccountId, + delegator: T::AccountId, + bond: BalanceOf, + more: BalanceOf, + ) -> Result + where + BalanceOf: Into + From, + { + let lowest_top_eq_highest_bottom = + self.lowest_top_delegation_amount == self.highest_bottom_delegation_amount; + let bond_geq_lowest_top = bond.into() >= self.lowest_top_delegation_amount; + let delegation_dne_err: DispatchError = Error::::DelegationDNE.into(); + if bond_geq_lowest_top && !lowest_top_eq_highest_bottom { + // definitely in top + self.increase_top_delegation::(candidate, delegator.clone(), more) + } else if bond_geq_lowest_top && lowest_top_eq_highest_bottom { + // update top but if error then update bottom (because could be in bottom because + // lowest_top_eq_highest_bottom) + let result = self.increase_top_delegation::(candidate, delegator.clone(), more); + if result == Err(delegation_dne_err) { + self.increase_bottom_delegation::(candidate, delegator, bond, more) + } else { + result + } + } else { + self.increase_bottom_delegation::(candidate, delegator, bond, more) + } + } + /// Increase top delegation + pub fn increase_top_delegation( + &mut self, + candidate: &T::AccountId, + delegator: T::AccountId, + more: BalanceOf, + ) -> Result + where + BalanceOf: Into + From, + { + let mut top_delegations = >::get(candidate) + .expect("CandidateInfo exists => TopDelegations exists"); + let mut in_top = false; + top_delegations.delegations = top_delegations + .delegations + .clone() + .into_iter() + .map(|d| { + if d.owner != delegator { + d + } else { + in_top = true; + let new_amount = d.amount.saturating_add(more); + Bond { + owner: d.owner, + amount: new_amount, + } + } + }) + .collect(); + ensure!(in_top, Error::::DelegationDNE); + top_delegations.total = top_delegations.total.saturating_add(more); + top_delegations.sort_greatest_to_least(); + self.reset_top_data::(candidate.clone(), &top_delegations); + >::insert(candidate, top_delegations); + Ok(true) + } + /// Increase bottom delegation + pub fn increase_bottom_delegation( + &mut self, + candidate: &T::AccountId, + delegator: T::AccountId, + bond: BalanceOf, + more: BalanceOf, + ) -> Result + where + BalanceOf: Into + From, + { + let mut bottom_delegations = + >::get(candidate).ok_or(Error::::CandidateDNE)?; + let mut delegation_option: Option>> = None; + let in_top_after = if (bond.saturating_add(more)).into() > self.lowest_top_delegation_amount + { + // bump it from bottom + bottom_delegations.delegations = bottom_delegations + .delegations + .clone() + .into_iter() + .filter(|d| { + if d.owner != delegator { + true + } else { + delegation_option = Some(Bond { + owner: d.owner.clone(), + amount: d.amount.saturating_add(more), + }); + false + } + }) + .collect(); + let delegation = delegation_option.ok_or(Error::::DelegationDNE)?; + bottom_delegations.total = bottom_delegations.total.saturating_sub(bond); + // add it to top + let mut top_delegations = >::get(candidate) + .expect("CandidateInfo existence => TopDelegations existence"); + // if top is full, pop lowest top + if matches!(top_delegations.top_capacity::(), CapacityStatus::Full) { + // pop lowest top delegation + let new_bottom_delegation = top_delegations + .delegations + .pop() + .expect("Top capacity full => Exists at least 1 top delegation"); + top_delegations.total = top_delegations + .total + .saturating_sub(new_bottom_delegation.amount); + bottom_delegations.insert_sorted_greatest_to_least(new_bottom_delegation); + } + // insert into top + top_delegations.insert_sorted_greatest_to_least(delegation); + self.reset_top_data::(candidate.clone(), &top_delegations); + >::insert(candidate, top_delegations); + true + } else { + let mut in_bottom = false; + // just increase the delegation + bottom_delegations.delegations = bottom_delegations + .delegations + .clone() + .into_iter() + .map(|d| { + if d.owner != delegator { + d + } else { + in_bottom = true; + Bond { + owner: d.owner, + amount: d.amount.saturating_add(more), + } + } + }) + .collect(); + ensure!(in_bottom, Error::::DelegationDNE); + bottom_delegations.total = bottom_delegations.total.saturating_add(more); + bottom_delegations.sort_greatest_to_least(); + false + }; + self.reset_bottom_data::(&bottom_delegations); + >::insert(candidate, bottom_delegations); + Ok(in_top_after) + } + /// Decrease delegation + pub fn decrease_delegation( + &mut self, + candidate: &T::AccountId, + delegator: T::AccountId, + bond: Balance, + less: BalanceOf, + ) -> Result + where + BalanceOf: Into + From, + { + let lowest_top_eq_highest_bottom = + self.lowest_top_delegation_amount == self.highest_bottom_delegation_amount; + let bond_geq_lowest_top = bond >= self.lowest_top_delegation_amount; + let delegation_dne_err: DispatchError = Error::::DelegationDNE.into(); + if bond_geq_lowest_top && !lowest_top_eq_highest_bottom { + // definitely in top + self.decrease_top_delegation::(candidate, delegator.clone(), bond.into(), less) + } else if bond_geq_lowest_top && lowest_top_eq_highest_bottom { + // update top but if error then update bottom (because could be in bottom because + // lowest_top_eq_highest_bottom) + let result = + self.decrease_top_delegation::(candidate, delegator.clone(), bond.into(), less); + if result == Err(delegation_dne_err) { + self.decrease_bottom_delegation::(candidate, delegator, less) + } else { + result + } + } else { + self.decrease_bottom_delegation::(candidate, delegator, less) + } + } + /// Decrease top delegation + pub fn decrease_top_delegation( + &mut self, + candidate: &T::AccountId, + delegator: T::AccountId, + bond: BalanceOf, + less: BalanceOf, + ) -> Result + where + BalanceOf: Into + From, + { + // The delegation after the `decrease-delegation` will be strictly less than the + // highest bottom delegation + let bond_after_less_than_highest_bottom = + bond.saturating_sub(less).into() < self.highest_bottom_delegation_amount; + // The top delegations is full and the bottom delegations has at least one delegation + let full_top_and_nonempty_bottom = matches!(self.top_capacity, CapacityStatus::Full) + && !matches!(self.bottom_capacity, CapacityStatus::Empty); + let mut top_delegations = + >::get(candidate).ok_or(Error::::CandidateDNE)?; + let in_top_after = if bond_after_less_than_highest_bottom && full_top_and_nonempty_bottom { + let mut delegation_option: Option>> = None; + // take delegation from top + top_delegations.delegations = top_delegations + .delegations + .clone() + .into_iter() + .filter(|d| { + if d.owner != delegator { + true + } else { + top_delegations.total = top_delegations.total.saturating_sub(d.amount); + delegation_option = Some(Bond { + owner: d.owner.clone(), + amount: d.amount.saturating_sub(less), + }); + false + } + }) + .collect(); + let delegation = delegation_option.ok_or(Error::::DelegationDNE)?; + // pop highest bottom by reverse and popping + let mut bottom_delegations = >::get(candidate) + .expect("CandidateInfo existence => BottomDelegations existence"); + let highest_bottom_delegation = bottom_delegations.delegations.remove(0); + bottom_delegations.total = bottom_delegations + .total + .saturating_sub(highest_bottom_delegation.amount); + // insert highest bottom into top + top_delegations.insert_sorted_greatest_to_least(highest_bottom_delegation); + // insert previous top into bottom + bottom_delegations.insert_sorted_greatest_to_least(delegation); + self.reset_bottom_data::(&bottom_delegations); + >::insert(candidate, bottom_delegations); + false + } else { + // keep it in the top + let mut is_in_top = false; + top_delegations.delegations = top_delegations + .delegations + .clone() + .into_iter() + .map(|d| { + if d.owner != delegator { + d + } else { + is_in_top = true; + Bond { + owner: d.owner, + amount: d.amount.saturating_sub(less), + } + } + }) + .collect(); + ensure!(is_in_top, Error::::DelegationDNE); + top_delegations.total = top_delegations.total.saturating_sub(less); + top_delegations.sort_greatest_to_least(); + true + }; + self.reset_top_data::(candidate.clone(), &top_delegations); + >::insert(candidate, top_delegations); + Ok(in_top_after) + } + /// Decrease bottom delegation + pub fn decrease_bottom_delegation( + &mut self, + candidate: &T::AccountId, + delegator: T::AccountId, + less: BalanceOf, + ) -> Result + where + BalanceOf: Into, + { + let mut bottom_delegations = >::get(candidate) + .expect("CandidateInfo exists => BottomDelegations exists"); + let mut in_bottom = false; + bottom_delegations.delegations = bottom_delegations + .delegations + .clone() + .into_iter() + .map(|d| { + if d.owner != delegator { + d + } else { + in_bottom = true; + Bond { + owner: d.owner, + amount: d.amount.saturating_sub(less), + } + } + }) + .collect(); + ensure!(in_bottom, Error::::DelegationDNE); + bottom_delegations.sort_greatest_to_least(); + self.reset_bottom_data::(&bottom_delegations); + >::insert(candidate, bottom_delegations); + Ok(false) + } +} + +// Temporary manual implementation for migration testing purposes +impl PartialEq for CollatorCandidate { + fn eq(&self, other: &Self) -> bool { + let must_be_true = self.id == other.id + && self.bond == other.bond + && self.total_counted == other.total_counted + && self.total_backing == other.total_backing + && self.request == other.request + && self.state == other.state; + if !must_be_true { + return false; + } + for (x, y) in self.delegators.0.iter().zip(other.delegators.0.iter()) { + if x != y { + return false; + } + } + for ( + Bond { + owner: o1, + amount: a1, + }, + Bond { + owner: o2, + amount: a2, + }, + ) in self + .top_delegations + .iter() + .zip(other.top_delegations.iter()) + { + if o1 != o2 || a1 != a2 { + return false; + } + } + for ( + Bond { + owner: o1, + amount: a1, + }, + Bond { + owner: o2, + amount: a2, + }, + ) in self + .bottom_delegations + .iter() + .zip(other.bottom_delegations.iter()) + { + if o1 != o2 || a1 != a2 { + return false; + } + } + true + } +} + +/// Convey relevant information describing if a delegator was added to the top or bottom +/// Delegations added to the top yield a new total +#[derive(Clone, Copy, PartialEq, Encode, Decode, RuntimeDebug, TypeInfo)] +pub enum DelegatorAdded { + AddedToTop { new_total: B }, + AddedToBottom, +} + +impl< + A: Ord + Clone + sp_std::fmt::Debug, + B: AtLeast32BitUnsigned + + Ord + + Copy + + sp_std::ops::AddAssign + + sp_std::ops::SubAssign + + sp_std::fmt::Debug, + > CollatorCandidate +{ + pub fn is_active(&self) -> bool { + self.state == CollatorStatus::Active + } +} + +impl From> for CollatorSnapshot { + fn from(other: CollatorCandidate) -> CollatorSnapshot { + CollatorSnapshot { + bond: other.bond, + delegations: other.top_delegations, + total: other.total_counted, + } + } +} + +#[derive(Clone, PartialEq, Encode, Decode, RuntimeDebug, TypeInfo)] +pub enum DelegatorStatus { + /// Active with no scheduled exit + Active, +} + +#[derive(Clone, Encode, Decode, RuntimeDebug, TypeInfo)] +/// Delegator state +pub struct Delegator { + /// Delegator account + pub id: AccountId, + /// All current delegations + pub delegations: OrderedSet>, + /// Total balance locked for this delegator + pub total: Balance, + /// Sum of pending revocation amounts + bond less amounts + pub less_total: Balance, + /// Status for this delegator + pub status: DelegatorStatus, +} + +// Temporary manual implementation for migration testing purposes +impl PartialEq for Delegator { + fn eq(&self, other: &Self) -> bool { + let must_be_true = self.id == other.id + && self.total == other.total + && self.less_total == other.less_total + && self.status == other.status; + if !must_be_true { + return false; + } + for ( + Bond { + owner: o1, + amount: a1, + }, + Bond { + owner: o2, + amount: a2, + }, + ) in self.delegations.0.iter().zip(other.delegations.0.iter()) + { + if o1 != o2 || a1 != a2 { + return false; + } + } + true + } +} + +impl< + AccountId: Ord + Clone, + Balance: Copy + + sp_std::ops::AddAssign + + sp_std::ops::Add + + sp_std::ops::SubAssign + + sp_std::ops::Sub + + Ord + + Zero + + Default + + Saturating, + > Delegator +{ + pub fn new(id: AccountId, collator: AccountId, amount: Balance) -> Self { + Delegator { + id, + delegations: OrderedSet::from(vec![Bond { + owner: collator, + amount, + }]), + total: amount, + less_total: Balance::zero(), + status: DelegatorStatus::Active, + } + } + + pub fn is_active(&self) -> bool { + matches!(self.status, DelegatorStatus::Active) + } + + pub fn add_delegation(&mut self, bond: Bond) -> bool { + let amt = bond.amount; + if self.delegations.insert(bond) { + self.total = self.total.saturating_add(amt); + true + } else { + false + } + } + // Return Some(remaining balance), must be more than MinDelegatorStk + // Return None if delegation not found + pub fn rm_delegation(&mut self, collator: &AccountId) -> Option { + let mut amt: Option = None; + let delegations = self + .delegations + .0 + .iter() + .filter_map(|x| { + if &x.owner == collator { + amt = Some(x.amount); + None + } else { + Some(x.clone()) + } + }) + .collect(); + if let Some(balance) = amt { + self.delegations = OrderedSet::from(delegations); + self.total = self.total.saturating_sub(balance); + Some(self.total) + } else { + None + } + } + pub fn increase_delegation( + &mut self, + candidate: AccountId, + amount: Balance, + ) -> DispatchResult + where + BalanceOf: From, + T::AccountId: From, + Delegator>: From>, + { + let delegator_id: T::AccountId = self.id.clone().into(); + let candidate_id: T::AccountId = candidate.clone().into(); + let balance_amt: BalanceOf = amount.into(); + // increase delegation + for x in &mut self.delegations.0 { + if x.owner == candidate { + let before_amount: BalanceOf = x.amount.into(); + x.amount = x.amount.saturating_add(amount); + self.total = self.total.saturating_add(amount); + // update collator state delegation + let mut collator_state = + >::get(&candidate_id).ok_or(Error::::CandidateDNE)?; + T::Currency::reserve(&self.id.clone().into(), balance_amt)?; + let before = collator_state.total_counted; + let in_top = collator_state.increase_delegation::( + &candidate_id, + delegator_id.clone(), + before_amount, + balance_amt, + )?; + let after = collator_state.total_counted; + if collator_state.is_active() && (before != after) { + Pallet::::update_active(candidate_id.clone(), after); + } + >::insert(&candidate_id, collator_state); + let new_total_staked = >::get().saturating_add(balance_amt); + >::put(new_total_staked); + let nom_st: Delegator> = self.clone().into(); + >::insert(&delegator_id, nom_st); + Pallet::::deposit_event(Event::DelegationIncreased { + delegator: delegator_id, + candidate: candidate_id, + amount: balance_amt, + in_top: in_top, + }); + return Ok(()); + } + } + Err(Error::::DelegationDNE.into()) + } + + /// Retrieves the bond amount that a delegator has provided towards a collator. + /// Returns `None` if missing. + pub fn get_bond_amount(&self, collator: &AccountId) -> Option { + self.delegations + .0 + .iter() + .find(|b| &b.owner == collator) + .map(|b| b.amount) + } +} + +pub mod deprecated { + #![allow(deprecated)] + + use super::*; + + #[deprecated(note = "use DelegationAction")] + #[derive(Clone, Eq, PartialEq, Encode, Decode, RuntimeDebug, TypeInfo)] + /// Changes requested by the delegator + /// - limit of 1 ongoing change per delegation + pub enum DelegationChange { + Revoke, + Decrease, + } + + #[deprecated(note = "use ScheduledRequest")] + #[derive(Clone, Eq, PartialEq, Encode, Decode, RuntimeDebug, TypeInfo)] + pub struct DelegationRequest { + pub collator: AccountId, + pub amount: Balance, + pub when_executable: RoundIndex, + pub action: DelegationChange, + } + + #[deprecated(note = "use DelegationScheduledRequests storage item")] + #[derive(Clone, Encode, PartialEq, Decode, RuntimeDebug, TypeInfo)] + /// Pending requests to mutate delegations for each delegator + pub struct PendingDelegationRequests { + /// Number of pending revocations (necessary for determining whether revoke is exit) + pub revocations_count: u32, + /// Map from collator -> Request (enforces at most 1 pending request per delegation) + pub requests: BTreeMap>, + /// Sum of pending revocation amounts + bond less amounts + pub less_total: Balance, + } + + impl Default for PendingDelegationRequests { + fn default() -> PendingDelegationRequests { + PendingDelegationRequests { + revocations_count: 0u32, + requests: BTreeMap::new(), + less_total: B::zero(), + } + } + } + + impl< + A: Ord + Clone, + B: Zero + + Ord + + Copy + + Clone + + sp_std::ops::AddAssign + + sp_std::ops::Add + + sp_std::ops::SubAssign + + sp_std::ops::Sub + + Saturating, + > PendingDelegationRequests + { + /// New default (empty) pending requests + pub fn new() -> Self { + Self::default() + } + } + + #[deprecated(note = "use new crate::types::Delegator struct")] + #[derive(Clone, Encode, Decode, RuntimeDebug, TypeInfo)] + /// Delegator state + pub struct Delegator { + /// Delegator account + pub id: AccountId, + /// All current delegations + pub delegations: OrderedSet>, + /// Total balance locked for this delegator + pub total: Balance, + /// Requests to change delegations, relevant iff active + pub requests: PendingDelegationRequests, + /// Status for this delegator + pub status: DelegatorStatus, + } +} + +#[derive(Clone, Encode, Decode, RuntimeDebug, TypeInfo)] +/// DEPRECATED in favor of Delegator +/// Nominator state +pub struct Nominator2 { + /// All current delegations + pub delegations: OrderedSet>, + /// Delegations scheduled to be revoked + pub revocations: OrderedSet, + /// Total balance locked for this nominator + pub total: Balance, + /// Total number of revocations scheduled to be executed + pub scheduled_revocations_count: u32, + /// Total amount to be unbonded once revocations are executed + pub scheduled_revocations_total: Balance, + /// Status for this nominator + pub status: DelegatorStatus, +} + +// /// Temporary function to migrate state +// pub(crate) fn migrate_nominator_to_delegator_state( +// id: T::AccountId, +// nominator: Nominator2>, +// ) -> Delegator> { +// Delegator { +// id, +// delegations: nominator.delegations, +// total: nominator.total, +// requests: PendingDelegationRequests::new(), +// status: nominator.status, +// } +// } + +#[derive(Copy, Clone, PartialEq, Eq, Encode, Decode, RuntimeDebug, TypeInfo)] +/// The current round index and transition information +pub struct RoundInfo { + /// Current round index + pub current: RoundIndex, + /// The first block of the current round + pub first: BlockNumber, + /// The length of the current round in number of blocks + pub length: u32, +} +impl< + B: Copy + sp_std::ops::Add + sp_std::ops::Sub + From + PartialOrd, + > RoundInfo +{ + pub fn new(current: RoundIndex, first: B, length: u32) -> RoundInfo { + RoundInfo { + current, + first, + length, + } + } + /// Check if the round should be updated + pub fn should_update(&self, now: B) -> bool { + now - self.first >= self.length.into() + } + /// New round + pub fn update(&mut self, now: B) { + self.current = self.current.saturating_add(1u32); + self.first = now; + } +} +impl< + B: Copy + sp_std::ops::Add + sp_std::ops::Sub + From + PartialOrd, + > Default for RoundInfo +{ + fn default() -> RoundInfo { + RoundInfo::new(1u32, 1u32.into(), 20u32) + } +} + +#[derive(Clone, PartialEq, Eq, Encode, Decode, RuntimeDebug, TypeInfo)] +/// Reserve information { account, percent_of_inflation } +pub struct ParachainBondConfig { + /// Account which receives funds intended for parachain bond + pub account: AccountId, + /// Percent of inflation set aside for parachain bond account + pub percent: Percent, +} +impl Default for ParachainBondConfig { + fn default() -> ParachainBondConfig { + ParachainBondConfig { + account: A::decode(&mut sp_runtime::traits::TrailingZeroInput::zeroes()) + .expect("infinite length input; no invalid inputs for type; qed"), + percent: Percent::zero(), + } + } +} diff --git a/gasp-node/pallets/parachain-staking/src/weights.rs b/gasp-node/pallets/parachain-staking/src/weights.rs new file mode 100644 index 000000000..39dec5230 --- /dev/null +++ b/gasp-node/pallets/parachain-staking/src/weights.rs @@ -0,0 +1,642 @@ +// This file is part of Mangata. + +// Copyright (C) 2020-2022 Mangata Foundation. +// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 + +// This program 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. + +// This program 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 this program. If not, see . + +//! Autogenerated weights for parachain_staking +//! +//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev +//! DATE: 2022-03-24, STEPS: `20`, REPEAT: 10, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! EXECUTION: Some(Wasm), WASM-EXECUTION: Compiled, CHAIN: Some("dev"), DB CACHE: 1024 + +// Executed Command: +// /home/ubuntu/mangata-node/scripts/..//target/release/mangata-node +// benchmark +// --chain +// dev +// --execution +// wasm +// --wasm-execution +// compiled +// --pallet +// parachain_staking +// --extrinsic +// * +// --steps +// 20 +// --repeat +// 10 +// --output +// ./benchmarks/parachain_staking_weights.rs +// --template +// ./templates/module-weight-template.hbs + +#![cfg_attr(rustfmt, rustfmt_skip)] +#![allow(unused_parens)] +#![allow(unused_imports)] +#![allow(clippy::unnecessary_cast)] + +use frame_support::{traits::Get, weights::{Weight, constants::RocksDbWeight}}; +use sp_std::marker::PhantomData; + +/// Weight functions needed for parachain_staking. +pub trait WeightInfo { + fn set_total_selected() -> Weight; + fn set_collator_commission() -> Weight; + fn join_candidates(x: u32, y: u32, ) -> Weight; + fn schedule_leave_candidates(x: u32, ) -> Weight; + fn execute_leave_candidates(x: u32, ) -> Weight; + fn cancel_leave_candidates(x: u32, ) -> Weight; + fn go_offline() -> Weight; + fn go_online() -> Weight; + fn schedule_candidate_bond_more() -> Weight; + fn schedule_candidate_bond_less() -> Weight; + fn execute_candidate_bond_more() -> Weight; + fn execute_candidate_bond_less() -> Weight; + fn cancel_candidate_bond_more() -> Weight; + fn cancel_candidate_bond_less() -> Weight; + fn delegate(x: u32, y: u32, ) -> Weight; + fn schedule_leave_delegators() -> Weight; + fn execute_leave_delegators(x: u32, ) -> Weight; + fn cancel_leave_delegators() -> Weight; + fn schedule_revoke_delegation() -> Weight; + fn schedule_delegator_bond_more() -> Weight; + fn schedule_delegator_bond_less() -> Weight; + fn execute_revoke_delegation() -> Weight; + fn execute_delegator_bond_more() -> Weight; + fn execute_delegator_bond_less() -> Weight; + fn cancel_revoke_delegation() -> Weight; + fn cancel_delegator_bond_more() -> Weight; + fn cancel_delegator_bond_less() -> Weight; + fn add_staking_liquidity_token(x: u32, ) -> Weight; + fn remove_staking_liquidity_token(x: u32, ) -> Weight; + fn passive_session_change() -> Weight; + fn active_session_change(x: u32, y: u32, z: u32) -> Weight; + fn payout_collator_rewards() -> Weight; + fn payout_delegator_reward() -> Weight; + fn update_candidate_aggregator() -> Weight; + fn aggregator_update_metadata() -> Weight; +} + +/// Weights for parachain_staking using the Mangata node and recommended hardware. +pub struct SubstrateWeight(PhantomData); +impl WeightInfo for SubstrateWeight { + // Storage: ParachainStaking TotalSelected (r:1 w:1) + fn set_total_selected() -> Weight { + Weight::from_parts(14_867_000, 0) + .saturating_add(T::DbWeight::get().reads(1 as u64)) + .saturating_add(T::DbWeight::get().writes(1 as u64)) + } + // Storage: ParachainStaking CollatorCommission (r:1 w:1) + fn set_collator_commission() -> Weight { + Weight::from_parts(14_948_000, 0) + .saturating_add(T::DbWeight::get().reads(1 as u64)) + .saturating_add(T::DbWeight::get().writes(1 as u64)) + } + // Storage: ParachainStaking CandidateState (r:1 w:1) + // Storage: ParachainStaking DelegatorState (r:1 w:0) + // Storage: ParachainStaking StakingLiquidityTokens (r:1 w:0) + // Storage: ParachainStaking CandidatePool (r:1 w:1) + // Storage: Tokens Accounts (r:1 w:1) + // Storage: ParachainStaking Total (r:1 w:1) + fn join_candidates(x: u32, y: u32, ) -> Weight { + Weight::from_parts(57_975_000, 0) + // Standard Error: 3_000 + .saturating_add((Weight::from_parts(452_000, 0)).saturating_mul(x as u64)) + // Standard Error: 1_000 + .saturating_add((Weight::from_parts(95_000, 0)).saturating_mul(y as u64)) + .saturating_add(T::DbWeight::get().reads(6 as u64)) + .saturating_add(T::DbWeight::get().writes(4 as u64)) + } + // Storage: ParachainStaking CandidateState (r:1 w:1) + // Storage: ParachainStaking Round (r:1 w:0) + // Storage: ParachainStaking CandidatePool (r:1 w:1) + fn schedule_leave_candidates(x: u32, ) -> Weight { + Weight::from_parts(34_598_000, 0) + // Standard Error: 3_000 + .saturating_add((Weight::from_parts(428_000, 0)).saturating_mul(x as u64)) + .saturating_add(T::DbWeight::get().reads(3 as u64)) + .saturating_add(T::DbWeight::get().writes(2 as u64)) + } + // Storage: ParachainStaking CandidateState (r:1 w:1) + // Storage: ParachainStaking Round (r:1 w:0) + // Storage: Tokens Accounts (r:2 w:2) + // Storage: ParachainStaking DelegatorState (r:1 w:1) + // Storage: ParachainStaking Total (r:1 w:1) + fn execute_leave_candidates(x: u32, ) -> Weight { + Weight::from_parts(19_556_000, 0) + // Standard Error: 19_000 + .saturating_add((Weight::from_parts(19_556_000, 0)).saturating_mul(x as u64)) + .saturating_add(T::DbWeight::get().reads(2 as u64)) + .saturating_add(T::DbWeight::get().reads((2 as u64).saturating_mul(x as u64))) + .saturating_add(T::DbWeight::get().writes(1 as u64)) + .saturating_add(T::DbWeight::get().writes((2 as u64).saturating_mul(x as u64))) + } + // Storage: ParachainStaking CandidateState (r:1 w:1) + // Storage: ParachainStaking CandidatePool (r:1 w:1) + fn cancel_leave_candidates(x: u32, ) -> Weight { + Weight::from_parts(31_884_000, 0) + // Standard Error: 3_000 + .saturating_add((Weight::from_parts(421_000, 0)).saturating_mul(x as u64)) + .saturating_add(T::DbWeight::get().reads(2 as u64)) + .saturating_add(T::DbWeight::get().writes(2 as u64)) + } + // Storage: ParachainStaking CandidateState (r:1 w:1) + // Storage: ParachainStaking CandidatePool (r:1 w:1) + // Storage: ParachainStaking Round (r:1 w:0) + fn go_offline() -> Weight { + Weight::from_parts(34_946_000, 0) + .saturating_add(T::DbWeight::get().reads(3 as u64)) + .saturating_add(T::DbWeight::get().writes(2 as u64)) + } + // Storage: ParachainStaking CandidateState (r:1 w:1) + // Storage: ParachainStaking CandidatePool (r:1 w:1) + // Storage: ParachainStaking Round (r:1 w:0) + fn go_online() -> Weight { + Weight::from_parts(33_628_000, 0) + .saturating_add(T::DbWeight::get().reads(3 as u64)) + .saturating_add(T::DbWeight::get().writes(2 as u64)) + } + // Storage: ParachainStaking CandidateState (r:1 w:1) + // Storage: Tokens Accounts (r:1 w:0) + // Storage: ParachainStaking Round (r:1 w:0) + fn schedule_candidate_bond_more() -> Weight { + Weight::from_parts(36_194_000, 0) + .saturating_add(T::DbWeight::get().reads(3 as u64)) + .saturating_add(T::DbWeight::get().writes(1 as u64)) + } + // Storage: ParachainStaking CandidateState (r:1 w:1) + // Storage: ParachainStaking Round (r:1 w:0) + fn schedule_candidate_bond_less() -> Weight { + Weight::from_parts(29_009_000, 0) + .saturating_add(T::DbWeight::get().reads(2 as u64)) + .saturating_add(T::DbWeight::get().writes(1 as u64)) + } + // Storage: ParachainStaking CandidateState (r:1 w:1) + // Storage: ParachainStaking Round (r:1 w:0) + // Storage: Tokens Accounts (r:1 w:1) + // Storage: ParachainStaking Total (r:1 w:1) + // Storage: ParachainStaking CandidatePool (r:1 w:1) + fn execute_candidate_bond_more() -> Weight { + Weight::from_parts(61_070_000, 0) + .saturating_add(T::DbWeight::get().reads(5 as u64)) + .saturating_add(T::DbWeight::get().writes(4 as u64)) + } + // Storage: ParachainStaking CandidateState (r:1 w:1) + // Storage: ParachainStaking Round (r:1 w:0) + // Storage: Tokens Accounts (r:1 w:1) + // Storage: ParachainStaking Total (r:1 w:1) + // Storage: ParachainStaking CandidatePool (r:1 w:1) + fn execute_candidate_bond_less() -> Weight { + Weight::from_parts(57_922_000, 0) + .saturating_add(T::DbWeight::get().reads(5 as u64)) + .saturating_add(T::DbWeight::get().writes(4 as u64)) + } + // Storage: ParachainStaking CandidateState (r:1 w:1) + fn cancel_candidate_bond_more() -> Weight { + Weight::from_parts(26_206_000, 0) + .saturating_add(T::DbWeight::get().reads(1 as u64)) + .saturating_add(T::DbWeight::get().writes(1 as u64)) + } + // Storage: ParachainStaking CandidateState (r:1 w:1) + fn cancel_candidate_bond_less() -> Weight { + Weight::from_parts(25_974_000, 0) + .saturating_add(T::DbWeight::get().reads(1 as u64)) + .saturating_add(T::DbWeight::get().writes(1 as u64)) + } + // Storage: ParachainStaking CandidateState (r:1 w:1) + // Storage: ParachainStaking DelegatorState (r:1 w:1) + // Storage: Tokens Accounts (r:1 w:1) + // Storage: ParachainStaking CandidatePool (r:1 w:1) + // Storage: ParachainStaking Total (r:1 w:1) + fn delegate(x: u32, y: u32, ) -> Weight { + Weight::from_parts(62_286_000, 0) + // Standard Error: 9_000 + .saturating_add((Weight::from_parts(689_000, 0)).saturating_mul(x as u64)) + // Standard Error: 30_000 + .saturating_add((Weight::from_parts(639_000, 0)).saturating_mul(y as u64)) + .saturating_add(T::DbWeight::get().reads(5 as u64)) + .saturating_add(T::DbWeight::get().writes(5 as u64)) + } + // Storage: ParachainStaking DelegatorState (r:1 w:1) + // Storage: ParachainStaking Round (r:1 w:0) + fn schedule_leave_delegators() -> Weight { + Weight::from_parts(30_002_000, 0) + .saturating_add(T::DbWeight::get().reads(2 as u64)) + .saturating_add(T::DbWeight::get().writes(1 as u64)) + } + // Storage: ParachainStaking DelegatorState (r:1 w:1) + // Storage: ParachainStaking Round (r:1 w:0) + // Storage: ParachainStaking CandidateState (r:1 w:1) + // Storage: Tokens Accounts (r:1 w:1) + // Storage: ParachainStaking CandidatePool (r:1 w:1) + // Storage: ParachainStaking Total (r:1 w:1) + fn execute_leave_delegators(x: u32, ) -> Weight { + Weight::from_parts(0, 0) + // Standard Error: 110_000 + .saturating_add((Weight::from_parts(29_927_000, 0)).saturating_mul(x as u64)) + .saturating_add(T::DbWeight::get().reads(4 as u64)) + .saturating_add(T::DbWeight::get().reads((1 as u64).saturating_mul(x as u64))) + .saturating_add(T::DbWeight::get().writes(3 as u64)) + .saturating_add(T::DbWeight::get().writes((1 as u64).saturating_mul(x as u64))) + } + // Storage: ParachainStaking DelegatorState (r:1 w:1) + fn cancel_leave_delegators() -> Weight { + Weight::from_parts(25_498_000, 0) + .saturating_add(T::DbWeight::get().reads(1 as u64)) + .saturating_add(T::DbWeight::get().writes(1 as u64)) + } + // Storage: ParachainStaking DelegatorState (r:1 w:1) + // Storage: ParachainStaking Round (r:1 w:0) + fn schedule_revoke_delegation() -> Weight { + Weight::from_parts(30_598_000, 0) + .saturating_add(T::DbWeight::get().reads(2 as u64)) + .saturating_add(T::DbWeight::get().writes(1 as u64)) + } + // Storage: ParachainStaking DelegatorState (r:1 w:1) + // Storage: Tokens Accounts (r:1 w:0) + // Storage: ParachainStaking Round (r:1 w:0) + fn schedule_delegator_bond_more() -> Weight { + Weight::from_parts(38_554_000, 0) + .saturating_add(T::DbWeight::get().reads(3 as u64)) + .saturating_add(T::DbWeight::get().writes(1 as u64)) + } + // Storage: ParachainStaking DelegatorState (r:1 w:1) + // Storage: ParachainStaking Round (r:1 w:0) + fn schedule_delegator_bond_less() -> Weight { + Weight::from_parts(30_322_000, 0) + .saturating_add(T::DbWeight::get().reads(2 as u64)) + .saturating_add(T::DbWeight::get().writes(1 as u64)) + } + // Storage: ParachainStaking DelegatorState (r:1 w:1) + // Storage: ParachainStaking Round (r:1 w:0) + // Storage: ParachainStaking CandidateState (r:1 w:1) + // Storage: Tokens Accounts (r:1 w:1) + // Storage: ParachainStaking CandidatePool (r:1 w:1) + // Storage: ParachainStaking Total (r:1 w:1) + fn execute_revoke_delegation() -> Weight { + Weight::from_parts(76_394_000, 0) + .saturating_add(T::DbWeight::get().reads(6 as u64)) + .saturating_add(T::DbWeight::get().writes(5 as u64)) + } + // Storage: ParachainStaking DelegatorState (r:1 w:1) + // Storage: ParachainStaking Round (r:1 w:0) + // Storage: ParachainStaking CandidateState (r:1 w:1) + // Storage: Tokens Accounts (r:1 w:1) + // Storage: ParachainStaking CandidatePool (r:1 w:1) + // Storage: ParachainStaking Total (r:1 w:1) + fn execute_delegator_bond_more() -> Weight { + Weight::from_parts(70_382_000, 0) + .saturating_add(T::DbWeight::get().reads(6 as u64)) + .saturating_add(T::DbWeight::get().writes(5 as u64)) + } + // Storage: ParachainStaking DelegatorState (r:1 w:1) + // Storage: ParachainStaking Round (r:1 w:0) + // Storage: ParachainStaking CandidateState (r:1 w:1) + // Storage: Tokens Accounts (r:1 w:1) + // Storage: ParachainStaking CandidatePool (r:1 w:1) + // Storage: ParachainStaking Total (r:1 w:1) + fn execute_delegator_bond_less() -> Weight { + Weight::from_parts(66_780_000, 0) + .saturating_add(T::DbWeight::get().reads(6 as u64)) + .saturating_add(T::DbWeight::get().writes(5 as u64)) + } + // Storage: ParachainStaking DelegatorState (r:1 w:1) + fn cancel_revoke_delegation() -> Weight { + Weight::from_parts(27_076_000, 0) + .saturating_add(T::DbWeight::get().reads(1 as u64)) + .saturating_add(T::DbWeight::get().writes(1 as u64)) + } + // Storage: ParachainStaking DelegatorState (r:1 w:1) + fn cancel_delegator_bond_more() -> Weight { + Weight::from_parts(32_355_000, 0) + .saturating_add(T::DbWeight::get().reads(1 as u64)) + .saturating_add(T::DbWeight::get().writes(1 as u64)) + } + // Storage: ParachainStaking DelegatorState (r:1 w:1) + fn cancel_delegator_bond_less() -> Weight { + Weight::from_parts(31_925_000, 0) + .saturating_add(T::DbWeight::get().reads(1 as u64)) + .saturating_add(T::DbWeight::get().writes(1 as u64)) + } + // Storage: ParachainStaking StakingLiquidityTokens (r:1 w:1) + fn add_staking_liquidity_token(x: u32, ) -> Weight { + Weight::from_parts(7_373_000, 0) + // Standard Error: 1_000 + .saturating_add((Weight::from_parts(92_000, 0)).saturating_mul(x as u64)) + .saturating_add(T::DbWeight::get().reads(1 as u64)) + .saturating_add(T::DbWeight::get().writes(1 as u64)) + } + // Storage: ParachainStaking StakingLiquidityTokens (r:1 w:1) + fn remove_staking_liquidity_token(x: u32, ) -> Weight { + Weight::from_parts(7_078_000, 0) + // Standard Error: 1_000 + .saturating_add((Weight::from_parts(95_000, 0)).saturating_mul(x as u64)) + .saturating_add(T::DbWeight::get().reads(1 as u64)) + .saturating_add(T::DbWeight::get().writes(1 as u64)) + } + // Storage: ParachainStaking Round (r:1 w:0) + fn passive_session_change() -> Weight { + Weight::from_parts(5_166_000, 0) + .saturating_add(T::DbWeight::get().reads(1 as u64)) + } + // Storage: ParachainStaking Round (r:1 w:1) + // Storage: Session CurrentIndex (r:1 w:1) + // Storage: Session QueuedChanged (r:1 w:1) + // Storage: Session QueuedKeys (r:1 w:1) + // Storage: Session DisabledValidators (r:1 w:0) + // Storage: ParachainStaking Points (r:1 w:1) + // Storage: ParachainStaking Staked (r:1 w:2) + // Storage: ParachainStaking InflationConfig (r:1 w:0) + // Storage: Tokens TotalIssuance (r:4 w:1) + // Storage: ParachainStaking ParachainBondInfo (r:1 w:0) + // Storage: Tokens Accounts (r:289 w:289) + // Storage: System Account (r:287 w:287) + // Storage: ParachainStaking CollatorCommission (r:1 w:0) + // Storage: ParachainStaking AwardedPts (r:25 w:24) + // Storage: ParachainStaking AtStake (r:24 w:48) + // Storage: ParachainStaking StakingLiquidityTokens (r:1 w:1) + // Storage: Xyk LiquidityPools (r:3 w:0) + // Storage: Xyk Pools (r:3 w:0) + // Storage: ParachainStaking CandidatePool (r:1 w:0) + // Storage: ParachainStaking TotalSelected (r:1 w:0) + // Storage: ParachainStaking CandidateState (r:24 w:0) + // Storage: Session NextKeys (r:24 w:0) + // Storage: Aura Authorities (r:1 w:0) + // Storage: ParachainStaking SelectedCandidates (r:0 w:1) + // Storage: Session Validators (r:0 w:1) + fn active_session_change(x: u32, y: u32, z: u32, ) -> Weight { + (Weight::from_parts(819_648_670, 0)) + // Standard Error: 16_309 + .saturating_add((Weight::from_parts(15_337_752, 0)).saturating_mul(x as u64)) + // Standard Error: 70_621 + .saturating_add((Weight::from_parts(6_320_523, 0)).saturating_mul(y as u64)) + // Standard Error: 166_526 + .saturating_add((Weight::from_parts(32_822_119, 0)).saturating_mul(z as u64)) + .saturating_add(RocksDbWeight::get().reads(124 as u64)) + .saturating_add(RocksDbWeight::get().reads((4 as u64).saturating_mul(x as u64))) + .saturating_add(RocksDbWeight::get().writes(119 as u64)) + } + + fn payout_collator_rewards() -> Weight{ + Weight::from_parts(0, 0) + .saturating_add(RocksDbWeight::get().reads((20 as u64))) + .saturating_add(RocksDbWeight::get().writes((20 as u64))) + } + + fn payout_delegator_reward() -> Weight{ + Weight::from_parts(0, 0) + .saturating_add(RocksDbWeight::get().reads((20 as u64))) + .saturating_add(RocksDbWeight::get().writes((20 as u64))) + } + // Storage: ParachainStaking CandidateState (r:49 w:0) + // Storage: ParachainStaking DelegatorState (r:1 w:0) + // Storage: ParachainStaking AggregatorMetadata (r:1 w:1) + // Storage: ParachainStaking CandidateAggregator (r:1 w:1) + fn aggregator_update_metadata() -> Weight { + (Weight::from_parts(599_580_000, 0)) + .saturating_add(RocksDbWeight::get().reads(52 as u64)) + .saturating_add(RocksDbWeight::get().writes(2 as u64)) + } + // Storage: ParachainStaking CandidateState (r:1 w:0) + // Storage: ParachainStaking CandidateAggregator (r:1 w:1) + // Storage: ParachainStaking AggregatorMetadata (r:2 w:2) + fn update_candidate_aggregator() -> Weight { + (Weight::from_parts(98_020_000, 0)) + .saturating_add(RocksDbWeight::get().reads(4 as u64)) + .saturating_add(RocksDbWeight::get().writes(3 as u64)) + } + // Storage: ParachainStaking RoundCollatorRewardInfo (r:2 w:1) +} + +// For backwards compatibility and tests +impl WeightInfo for () { + fn set_total_selected() -> Weight { + Weight::from_parts(14_867_000, 0) + .saturating_add(RocksDbWeight::get().reads(1 as u64)) + .saturating_add(RocksDbWeight::get().writes(1 as u64)) + } + fn set_collator_commission() -> Weight { + Weight::from_parts(14_948_000, 0) + .saturating_add(RocksDbWeight::get().reads(1 as u64)) + .saturating_add(RocksDbWeight::get().writes(1 as u64)) + } + fn join_candidates(x: u32, y: u32, ) -> Weight { + Weight::from_parts(57_975_000, 0) + // Standard Error: 3_000 + .saturating_add((Weight::from_parts(452_000, 0)).saturating_mul(x as u64)) + // Standard Error: 1_000 + .saturating_add((Weight::from_parts(95_000, 0)).saturating_mul(y as u64)) + .saturating_add(RocksDbWeight::get().reads(6 as u64)) + .saturating_add(RocksDbWeight::get().writes(4 as u64)) + } + fn schedule_leave_candidates(x: u32, ) -> Weight { + Weight::from_parts(34_598_000, 0) + // Standard Error: 3_000 + .saturating_add((Weight::from_parts(428_000, 0)).saturating_mul(x as u64)) + .saturating_add(RocksDbWeight::get().reads(3 as u64)) + .saturating_add(RocksDbWeight::get().writes(2 as u64)) + } + fn execute_leave_candidates(x: u32, ) -> Weight { + Weight::from_parts(34_089_000, 0) + // Standard Error: 19_000 + .saturating_add((Weight::from_parts(19_556_000, 0)).saturating_mul(x as u64)) + .saturating_add(RocksDbWeight::get().reads(2 as u64)) + .saturating_add(RocksDbWeight::get().reads((2 as u64).saturating_mul(x as u64))) + .saturating_add(RocksDbWeight::get().writes(1 as u64)) + .saturating_add(RocksDbWeight::get().writes((2 as u64).saturating_mul(x as u64))) + } + fn cancel_leave_candidates(x: u32, ) -> Weight { + Weight::from_parts(31_884_000, 0) + // Standard Error: 3_000 + .saturating_add((Weight::from_parts(421_000, 0)).saturating_mul(x as u64)) + .saturating_add(RocksDbWeight::get().reads(2 as u64)) + .saturating_add(RocksDbWeight::get().writes(2 as u64)) + } + fn go_offline() -> Weight { + Weight::from_parts(34_946_000, 0) + .saturating_add(RocksDbWeight::get().reads(3 as u64)) + .saturating_add(RocksDbWeight::get().writes(2 as u64)) + } + fn go_online() -> Weight { + Weight::from_parts(33_628_000, 0) + .saturating_add(RocksDbWeight::get().reads(3 as u64)) + .saturating_add(RocksDbWeight::get().writes(2 as u64)) + } + fn schedule_candidate_bond_more() -> Weight { + Weight::from_parts(36_194_000, 0) + .saturating_add(RocksDbWeight::get().reads(3 as u64)) + .saturating_add(RocksDbWeight::get().writes(1 as u64)) + } + fn schedule_candidate_bond_less() -> Weight { + Weight::from_parts(29_009_000, 0) + .saturating_add(RocksDbWeight::get().reads(2 as u64)) + .saturating_add(RocksDbWeight::get().writes(1 as u64)) + } + fn execute_candidate_bond_more() -> Weight { + Weight::from_parts(61_070_000, 0) + .saturating_add(RocksDbWeight::get().reads(5 as u64)) + .saturating_add(RocksDbWeight::get().writes(4 as u64)) + } + fn execute_candidate_bond_less() -> Weight { + Weight::from_parts(57_922_000, 0) + .saturating_add(RocksDbWeight::get().reads(5 as u64)) + .saturating_add(RocksDbWeight::get().writes(4 as u64)) + } + fn cancel_candidate_bond_more() -> Weight { + Weight::from_parts(26_206_000, 0) + .saturating_add(RocksDbWeight::get().reads(1 as u64)) + .saturating_add(RocksDbWeight::get().writes(1 as u64)) + } + fn cancel_candidate_bond_less() -> Weight { + Weight::from_parts(25_974_000, 0) + .saturating_add(RocksDbWeight::get().reads(1 as u64)) + .saturating_add(RocksDbWeight::get().writes(1 as u64)) + } + fn delegate(x: u32, y: u32, ) -> Weight { + Weight::from_parts(62_286_000, 0) + // Standard Error: 9_000 + .saturating_add((Weight::from_parts(689_000, 0)).saturating_mul(x as u64)) + // Standard Error: 30_000 + .saturating_add((Weight::from_parts(639_000, 0)).saturating_mul(y as u64)) + .saturating_add(RocksDbWeight::get().reads(5 as u64)) + .saturating_add(RocksDbWeight::get().writes(5 as u64)) + } + fn schedule_leave_delegators() -> Weight { + Weight::from_parts(30_002_000, 0) + .saturating_add(RocksDbWeight::get().reads(2 as u64)) + .saturating_add(RocksDbWeight::get().writes(1 as u64)) + } + fn execute_leave_delegators(x: u32, ) -> Weight { + Weight::from_parts(0, 0) + // Standard Error: 110_000 + .saturating_add((Weight::from_parts(29_927_000, 0)).saturating_mul(x as u64)) + .saturating_add(RocksDbWeight::get().reads(4 as u64)) + .saturating_add(RocksDbWeight::get().reads((1 as u64).saturating_mul(x as u64))) + .saturating_add(RocksDbWeight::get().writes(3 as u64)) + .saturating_add(RocksDbWeight::get().writes((1 as u64).saturating_mul(x as u64))) + } + fn cancel_leave_delegators() -> Weight { + Weight::from_parts(25_498_000, 0) + .saturating_add(RocksDbWeight::get().reads(1 as u64)) + .saturating_add(RocksDbWeight::get().writes(1 as u64)) + } + fn schedule_revoke_delegation() -> Weight { + Weight::from_parts(30_598_000, 0) + .saturating_add(RocksDbWeight::get().reads(2 as u64)) + .saturating_add(RocksDbWeight::get().writes(1 as u64)) + } + fn schedule_delegator_bond_more() -> Weight { + Weight::from_parts(38_554_000, 0) + .saturating_add(RocksDbWeight::get().reads(3 as u64)) + .saturating_add(RocksDbWeight::get().writes(1 as u64)) + } + fn schedule_delegator_bond_less() -> Weight { + Weight::from_parts(30_322_000, 0) + .saturating_add(RocksDbWeight::get().reads(2 as u64)) + .saturating_add(RocksDbWeight::get().writes(1 as u64)) + } + fn execute_revoke_delegation() -> Weight { + Weight::from_parts(76_394_000, 0) + .saturating_add(RocksDbWeight::get().reads(6 as u64)) + .saturating_add(RocksDbWeight::get().writes(5 as u64)) + } + fn execute_delegator_bond_more() -> Weight { + Weight::from_parts(70_382_000, 0) + .saturating_add(RocksDbWeight::get().reads(6 as u64)) + .saturating_add(RocksDbWeight::get().writes(5 as u64)) + } + fn execute_delegator_bond_less() -> Weight { + Weight::from_parts(66_780_000, 0) + .saturating_add(RocksDbWeight::get().reads(6 as u64)) + .saturating_add(RocksDbWeight::get().writes(5 as u64)) + } + fn cancel_revoke_delegation() -> Weight { + Weight::from_parts(27_076_000, 0) + .saturating_add(RocksDbWeight::get().reads(1 as u64)) + .saturating_add(RocksDbWeight::get().writes(1 as u64)) + } + fn cancel_delegator_bond_more() -> Weight { + Weight::from_parts(32_355_000, 0) + .saturating_add(RocksDbWeight::get().reads(1 as u64)) + .saturating_add(RocksDbWeight::get().writes(1 as u64)) + } + fn cancel_delegator_bond_less() -> Weight { + Weight::from_parts(31_925_000, 0) + .saturating_add(RocksDbWeight::get().reads(1 as u64)) + .saturating_add(RocksDbWeight::get().writes(1 as u64)) + } + fn add_staking_liquidity_token(x: u32, ) -> Weight { + Weight::from_parts(7_373_000, 0) + // Standard Error: 1_000 + .saturating_add((Weight::from_parts(92_000, 0)).saturating_mul(x as u64)) + .saturating_add(RocksDbWeight::get().reads(1 as u64)) + .saturating_add(RocksDbWeight::get().writes(1 as u64)) + } + fn remove_staking_liquidity_token(x: u32, ) -> Weight { + Weight::from_parts(7_078_000, 0) + // Standard Error: 1_000 + .saturating_add((Weight::from_parts(95_000, 0)).saturating_mul(x as u64)) + .saturating_add(RocksDbWeight::get().reads(1 as u64)) + .saturating_add(RocksDbWeight::get().writes(1 as u64)) + } + fn passive_session_change() -> Weight { + Weight::from_parts(5_166_000, 0) + .saturating_add(RocksDbWeight::get().reads(1 as u64)) + } + fn active_session_change(x: u32, y: u32, z: u32, ) -> Weight { + (Weight::from_parts(819_648_670, 0)) + // Standard Error: 16_309 + .saturating_add((Weight::from_parts(15_337_752, 0)).saturating_mul(x as u64)) + // Standard Error: 70_621 + .saturating_add((Weight::from_parts(6_320_523, 0)).saturating_mul(y as u64)) + // Standard Error: 166_526 + .saturating_add((Weight::from_parts(32_822_119, 0)).saturating_mul(z as u64)) + .saturating_add(RocksDbWeight::get().reads(124 as u64)) + .saturating_add(RocksDbWeight::get().reads((4 as u64).saturating_mul(x as u64))) + .saturating_add(RocksDbWeight::get().writes(119 as u64)) + } + + fn payout_collator_rewards() -> Weight{ + Weight::from_parts(0, 0) + .saturating_add(RocksDbWeight::get().reads((20 as u64))) + .saturating_add(RocksDbWeight::get().writes((20 as u64))) + } + + fn payout_delegator_reward() -> Weight{ + Weight::from_parts(0, 0) + .saturating_add(RocksDbWeight::get().reads((20 as u64))) + .saturating_add(RocksDbWeight::get().writes((20 as u64))) + } + // Storage: ParachainStaking CandidateState (r:49 w:0) + // Storage: ParachainStaking DelegatorState (r:1 w:0) + // Storage: ParachainStaking AggregatorMetadata (r:1 w:1) + // Storage: ParachainStaking CandidateAggregator (r:1 w:1) + fn aggregator_update_metadata() -> Weight { + (Weight::from_parts(599_580_000, 0)) + .saturating_add(RocksDbWeight::get().reads(52 as u64)) + .saturating_add(RocksDbWeight::get().writes(2 as u64)) + } + // Storage: ParachainStaking CandidateState (r:1 w:0) + // Storage: ParachainStaking CandidateAggregator (r:1 w:1) + // Storage: ParachainStaking AggregatorMetadata (r:2 w:2) + fn update_candidate_aggregator() -> Weight { + (Weight::from_parts(98_020_000, 0)) + .saturating_add(RocksDbWeight::get().reads(4 as u64)) + .saturating_add(RocksDbWeight::get().writes(3 as u64)) + } + // Storage: ParachainStaking RoundCollatorRewardInfo (r:2 w:1) +} diff --git a/gasp-node/pallets/proof-of-stake/Cargo.toml b/gasp-node/pallets/proof-of-stake/Cargo.toml new file mode 100644 index 000000000..55204ef76 --- /dev/null +++ b/gasp-node/pallets/proof-of-stake/Cargo.toml @@ -0,0 +1,99 @@ +[package] +authors = ["Mangata team"] +edition = "2018" +name = "pallet-proof-of-stake" +version = "0.1.0" + +[package.metadata.docs.rs] +targets = ["x86_64-unknown-linux-gnu"] + +[dependencies] +codec = { workspace = true, default-features = false } +hex = { workspace = true, default-features = false } +hex-literal = { workspace = true, default-features = false } +log = { workspace = true, default-features = false } +serde = { workspace = true, optional = true } +scale-info = { workspace = true, default-features = false, features = ["derive"] } + +pallet-bootstrap = { default-features = false, path = "../bootstrap" } +pallet-issuance = { default-features = false, path = "../issuance" } + +libm = { git = "https://github.com/rust-lang/libm", rev = "2f3fc968f43d345f9b449938d050a9ea46a04c83", default-features = false } + +frame-benchmarking = { workspace = true, default-features = false } +frame-executive = { workspace = true, default-features = false } +frame-support = { workspace = true, default-features = false } +frame-system = { workspace = true, default-features = false } +frame-try-runtime = { workspace = true, default-features = false, optional = true } +mangata-support = { workspace = true, default-features = false } +mangata-types = { workspace = true, default-features = false } +pallet-vesting-mangata = { workspace = true, default-features = false } +sp-arithmetic = { workspace = true, default-features = false } +sp-core = { workspace = true, default-features = false } +sp-runtime = { workspace = true, default-features = false } +sp-std = { workspace = true, default-features = false } + +orml-tokens = { workspace = true, default-features = false } + +[dev-dependencies] +env_logger.workspace = true +lazy_static.workspace = true +serial_test.workspace = true +similar-asserts.workspace = true +test-case.workspace = true +mockall.workspace = true + +pallet-xyk = { path = "../xyk" } + +sp-io = { workspace = true, default-features = false } + +orml-traits = { workspace = true, default-features = false } + +[features] +default = ["std"] +std = [ + "codec/std", + "frame-benchmarking/std", + "frame-executive/std", + "frame-support/std", + "frame-system/std", + "frame-try-runtime/std", + "hex/std", + "log/std", + "mangata-support/std", + "mangata-types/std", + "orml-tokens/std", + "pallet-bootstrap/std", + "pallet-issuance/std", + "pallet-vesting-mangata/std", + "scale-info/std", + "serde", + "sp-arithmetic/std", + "sp-core/std", + "sp-runtime/std", + "sp-std/std", +] + +runtime-benchmarks = [ + "frame-benchmarking/runtime-benchmarks", + "frame-support/runtime-benchmarks", + "frame-system/runtime-benchmarks", + "mangata-support/runtime-benchmarks", + "orml-tokens/runtime-benchmarks", + "pallet-bootstrap/runtime-benchmarks", + "pallet-issuance/runtime-benchmarks", + "pallet-vesting-mangata/runtime-benchmarks", + "sp-runtime/runtime-benchmarks", +] + +try-runtime = [ + "frame-executive/try-runtime", + "frame-support/try-runtime", + "frame-system/try-runtime", + "frame-try-runtime", + "orml-tokens/try-runtime", + "pallet-bootstrap/try-runtime", + "pallet-issuance/try-runtime", + "pallet-vesting-mangata/try-runtime", + "sp-runtime/try-runtime", +] diff --git a/gasp-node/pallets/proof-of-stake/rpc/Cargo.toml b/gasp-node/pallets/proof-of-stake/rpc/Cargo.toml new file mode 100644 index 000000000..be76e8c86 --- /dev/null +++ b/gasp-node/pallets/proof-of-stake/rpc/Cargo.toml @@ -0,0 +1,40 @@ +[package] +authors = ['Mangata team'] +name = "proof-of-stake-rpc" +version = "2.0.0" +edition = "2018" +description = "RPC calls for Proof of Stake" +license = "GPL-3.0-or-later" + +[dependencies] +codec = { workspace = true } +jsonrpsee = { workspace = true, features = ["server", "client", "macros"] } +serde = { workspace = true, features = ["derive"], optional = true } + +# Substrate packages + +sp-api = { workspace = true, default-features = false } +sp-blockchain = { workspace = true, default-features = false } +sp-rpc = { workspace = true, default-features = false } +sp-core = { workspace = true, default-features = false } +sp-std = { workspace = true, default-features = false } +sp-runtime = { workspace = true, default-features = false } +mangata-types = { workspace = true, default-features = false } + +# local packages + +proof-of-stake-runtime-api = { version = "2.0.0", path = "../runtime-api", default-features = false } + +[features] +default = ["std"] + +std = [ + "serde", + "sp-api/std", + "sp-core/std", + "sp-std/std", + "sp-runtime/std", + "proof-of-stake-runtime-api/std", + "mangata-types/std", + "codec/std", +] diff --git a/gasp-node/pallets/proof-of-stake/rpc/src/lib.rs b/gasp-node/pallets/proof-of-stake/rpc/src/lib.rs new file mode 100644 index 000000000..077b3d7da --- /dev/null +++ b/gasp-node/pallets/proof-of-stake/rpc/src/lib.rs @@ -0,0 +1,136 @@ +// Copyright (C) 2021 Mangata team + +use codec::Codec; +use jsonrpsee::{ + core::{async_trait, RpcResult}, + proc_macros::rpc, + types::error::ErrorObject, +}; +pub use proof_of_stake_runtime_api::ProofOfStakeApi as ProofOfStakeRuntimeApi; +use sp_api::ProvideRuntimeApi; +use sp_blockchain::HeaderBackend; +use sp_rpc::number::NumberOrHex; +use sp_runtime::traits::{Block as BlockT, MaybeDisplay, MaybeFromStr}; +use std::sync::Arc; + +#[rpc(client, server)] +pub trait ProofOfStakeApi { + /// Calculates amount of available native rewards + /// + /// * `account` - user account address + /// * `liquidity_token` - liquidity token id + /// * `at` - optional block hash + #[method(name = "pos_calculate_native_rewards_amount")] + fn calculate_native_rewards_amount( + &self, + account: AccountId, + liquidity_token: TokenId, + at: Option, + ) -> RpcResult; + + /// Calculates amount of available 3rdparty rewards + /// + /// * `account` - user account address + /// * `liquidity_token` - liquidity token id + /// * `reward_token` - particular token that given pool is rewarded with + /// * `at` - optional block hash + #[method(name = "pos_calculate_3rdparty_rewards_amount")] + fn calculate_3rdparty_rewards_amount( + &self, + account: AccountId, + liquidity_token: TokenId, + rewards_token: TokenId, + at: Option, + ) -> RpcResult; + + /// # Calculates amount of all available 3rdparty rewards for given account + /// + /// * `account` - user account address + /// * `at` - optional block hash + #[method(name = "pos_calculate_3rdparty_rewards_all")] + fn calculate_3rdparty_rewards_all( + &self, + account: AccountId, + at: Option, + ) -> RpcResult>; +} + +pub struct ProofOfStake { + client: Arc, + _marker: std::marker::PhantomData, +} + +impl ProofOfStake { + pub fn new(client: Arc) -> Self { + Self { client, _marker: Default::default() } + } +} + +#[async_trait] +impl + ProofOfStakeApiServer<::Hash, Balance, TokenId, AccountId> + for ProofOfStake +where + Block: BlockT, + C: Send + Sync + 'static, + C: ProvideRuntimeApi, + C: HeaderBackend, + C::Api: ProofOfStakeRuntimeApi, + Balance: Codec + MaybeDisplay + MaybeFromStr + Into, + TokenId: Codec + MaybeDisplay + MaybeFromStr, + AccountId: Codec + MaybeDisplay + MaybeFromStr, +{ + fn calculate_native_rewards_amount( + &self, + account: AccountId, + liquidity_token: TokenId, + at: Option<::Hash>, + ) -> RpcResult { + let api = self.client.runtime_api(); + let at = at.unwrap_or(self.client.info().best_hash); + + api.calculate_native_rewards_amount(at, account, liquidity_token) + .map(Into::::into) + .map_err(|e| { + ErrorObject::owned(1, "Unable to serve the request", Some(format!("{:?}", e))) + }) + } + + fn calculate_3rdparty_rewards_amount( + &self, + account: AccountId, + liquidity_token: TokenId, + reward_token: TokenId, + at: Option<::Hash>, + ) -> RpcResult { + let api = self.client.runtime_api(); + let at = at.unwrap_or(self.client.info().best_hash); + + api.calculate_3rdparty_rewards_amount(at, account, liquidity_token, reward_token) + .map(Into::::into) + .map_err(|e| { + ErrorObject::owned(1, "Unable to serve the request", Some(format!("{:?}", e))) + }) + } + + fn calculate_3rdparty_rewards_all( + &self, + account: AccountId, + at: Option<::Hash>, + ) -> RpcResult> { + let api = self.client.runtime_api(); + let at = at.unwrap_or(self.client.info().best_hash); + + api.calculate_3rdparty_rewards_all(at, account) + .map(|vec| { + vec.into_iter() + .map(|(token1, token2, balance)| { + (token1, token2, Into::::into(balance)) + }) + .collect() + }) + .map_err(|e| { + ErrorObject::owned(1, "Unable to serve the request", Some(format!("{:?}", e))) + }) + } +} diff --git a/gasp-node/pallets/proof-of-stake/runtime-api/Cargo.toml b/gasp-node/pallets/proof-of-stake/runtime-api/Cargo.toml new file mode 100644 index 000000000..34ba1dfb0 --- /dev/null +++ b/gasp-node/pallets/proof-of-stake/runtime-api/Cargo.toml @@ -0,0 +1,32 @@ +[package] +authors = ['Mangata team'] +name = "proof-of-stake-runtime-api" +version = "2.0.0" +edition = "2018" +license = "GPL-3.0-or-later" + +[dependencies] +codec = { workspace = true, default-features = false, features = ["derive"] } +serde = { workspace = true, optional = true, features = ["derive"] } +scale-info = { workspace = true, default-features = false, features = ["derive"] } + +sp-api = { workspace = true, default-features = false } +sp-std = { workspace = true, default-features = false } +sp-runtime = { workspace = true, default-features = false } +frame-support = { workspace = true, default-features = false } +frame-system = { workspace = true, default-features = false } +sp-core = { workspace = true, default-features = false } + +[features] +default = ["std"] + +std = [ + "codec/std", + "frame-support/std", + "frame-system/std", + "serde", + "sp-api/std", + "sp-core/std", + "sp-runtime/std", + "sp-std/std", +] diff --git a/gasp-node/pallets/proof-of-stake/runtime-api/src/lib.rs b/gasp-node/pallets/proof-of-stake/runtime-api/src/lib.rs new file mode 100644 index 000000000..cf0401c5b --- /dev/null +++ b/gasp-node/pallets/proof-of-stake/runtime-api/src/lib.rs @@ -0,0 +1,28 @@ +// Copyright (C) 2021 Mangata team +#![cfg_attr(not(feature = "std"), no_std)] +use codec::Codec; +use sp_runtime::traits::{MaybeDisplay, MaybeFromStr}; +use sp_std::vec::Vec; + +sp_api::decl_runtime_apis! { + pub trait ProofOfStakeApi where + Balance: Codec + MaybeDisplay + MaybeFromStr, + TokenId: Codec + MaybeDisplay + MaybeFromStr, + AccountId: Codec + MaybeDisplay + MaybeFromStr,{ + + fn calculate_native_rewards_amount( + user: AccountId, + liquidity_asset_id: TokenId, + ) -> Balance; + + fn calculate_3rdparty_rewards_amount( + user: AccountId, + liquidity_asset_id: TokenId, + reward_asset_id: TokenId, + ) -> Balance; + + fn calculate_3rdparty_rewards_all( + user: AccountId, + ) -> Vec<(TokenId, TokenId, Balance)>; + } +} diff --git a/gasp-node/pallets/proof-of-stake/src/benchmarking.rs b/gasp-node/pallets/proof-of-stake/src/benchmarking.rs new file mode 100644 index 000000000..cde299c98 --- /dev/null +++ b/gasp-node/pallets/proof-of-stake/src/benchmarking.rs @@ -0,0 +1,537 @@ +#![cfg(feature = "runtime-benchmarks")] + +use super::*; + +use frame_benchmarking::{account, benchmarks, whitelisted_caller}; +use frame_support::traits::WithdrawReasons; +use frame_system::RawOrigin; +use mangata_support::traits::{ComputeIssuance, ProofOfStakeRewardsApi}; +use orml_tokens::MultiTokenCurrencyExtended; +use sp_runtime::{traits::One, Permill, SaturatedConversion}; + +use crate::Pallet as PoS; + +const MILION: u128 = 1_000__000_000__000_000; + +trait ToBalance { + fn to_balance(self) -> BalanceOf; +} + +impl ToBalance for u128 { + fn to_balance(self) -> BalanceOf { + self.try_into().ok().expect("u128 should fit into Balance type") + } +} + +fn init() +where + T: frame_system::Config, + T: pallet_issuance::Config, +{ + frame_system::Pallet::::set_block_number(1_u32.into()); + pallet_issuance::Pallet::::initialize(); +} + +type TokensOf = ::Currency; +type AccountIdOf = ::AccountId; +type XykOf = ::Xyk; + +fn forward_to_next_session() +where + T: frame_system::Config, + // T: pallet_issuance::Config, + T: Config, +{ + crate::utils::roll_to_next_session::(); +} + +benchmarks! { + claim_native_rewards{ + // 1. create + // 2. promote + // 3. mint + // 4. wait some + // 5. claim all + + init::(); + let caller: ::AccountId = whitelisted_caller(); + let initial_amount: BalanceOf = 1000000000000000000000_u128.try_into().ok().expect("should fit"); + let native_asset_id = ::NativeCurrencyId::get(); + + loop { + let token_id = TokensOf::::create(&caller, MILION.to_balance::()).unwrap(); + if token_id > native_asset_id { + break; + } + } + + let first_token_id = ::Currency::create(&caller, initial_amount).unwrap(); + let second_token_id = ::Currency::create(&caller, initial_amount).unwrap(); + let liquidity_asset_id = ::Currency::get_next_currency_id(); + + XykOf::::create_pool( + caller.clone(), + first_token_id.into(), + (40000000000000000000_u128/2_u128).to_balance::(), + second_token_id.into(), + (60000000000000000000_u128/2_u128).to_balance::() + ).unwrap(); + + PoS::::update_pool_promotion(RawOrigin::Root.into(), liquidity_asset_id, 1u8).unwrap(); + + assert_eq!( + ::Currency::total_issuance(liquidity_asset_id), + ::Currency::free_balance(liquidity_asset_id, &caller), + ); + + let total_minted_liquidity = ::Currency::total_issuance(liquidity_asset_id); + let half_of_minted_liquidity = total_minted_liquidity / 2_u32.into(); + let quater_of_minted_liquidity = total_minted_liquidity / 4_u32.into(); + + forward_to_next_session::(); + + PoS::::activate_liquidity_for_native_rewards(RawOrigin::Signed(caller.clone()).into(), liquidity_asset_id, quater_of_minted_liquidity, None).unwrap(); + + forward_to_next_session::(); + forward_to_next_session::(); + + assert!(PoS::::calculate_rewards_amount(caller.clone(), liquidity_asset_id).unwrap() > 0_u32.into()); + + }: claim_native_rewards(RawOrigin::Signed(caller.clone().into()), liquidity_asset_id) + verify { + + assert_eq!( + BalanceOf::::zero(), + PoS::::calculate_rewards_amount(caller.clone(), liquidity_asset_id).unwrap() + ); + + } + + + update_pool_promotion { + let caller: T::AccountId = whitelisted_caller(); + let initial_amount: BalanceOf = 1000000000000000000000_u128.try_into().ok().expect("should fit"); + let native_asset_id = ::NativeCurrencyId::get(); + + loop { + let token_id = TokensOf::::create(&caller, MILION.to_balance::()).unwrap(); + if token_id > native_asset_id { + break; + } + } + + let first_token_id = ::Currency::create(&caller, initial_amount).unwrap(); + let second_token_id = ::Currency::create(&caller, initial_amount).unwrap(); + let liquidity_asset_id = ::Currency::get_next_currency_id(); + + XykOf::::create_pool( + caller.clone(), + first_token_id.into(), + (40000000000000000000_u128/2_u128).to_balance::(), + second_token_id.into(), + (60000000000000000000_u128/2_u128).to_balance::() + ).unwrap(); + + }: update_pool_promotion(RawOrigin::Root, liquidity_asset_id, 1u8) + + verify { + assert!( + PoS::::is_enabled(liquidity_asset_id) + ); + } + + activate_liquidity_for_native_rewards{ + // activate : + // 1 crate pool + // 2 promote pool + // 3 activate some + // 4 wait some time + // 5 mint some + + init::(); + let caller: ::AccountId = whitelisted_caller(); + let initial_amount: BalanceOf = 1000000000000000000000_u128.try_into().ok().expect("should fit"); + let native_asset_id = ::NativeCurrencyId::get(); + + loop { + let token_id = TokensOf::::create(&caller, MILION.to_balance::()).unwrap(); + if token_id > native_asset_id { + break; + } + } + + let first_token_id = ::Currency::create(&caller, initial_amount).unwrap(); + let second_token_id = ::Currency::create(&caller, initial_amount).unwrap(); + let liquidity_asset_id = ::Currency::get_next_currency_id(); + + XykOf::::create_pool( + caller.clone(), + first_token_id.into(), + (40000000000000000000_u128/2_u128).to_balance::(), + second_token_id.into(), + (60000000000000000000_u128/2_u128).to_balance::() + ).unwrap(); + + PoS::::update_pool_promotion(RawOrigin::Root.into(), liquidity_asset_id, 1u8).unwrap(); + + assert_eq!( + ::Currency::total_issuance(liquidity_asset_id), + ::Currency::free_balance(liquidity_asset_id, &caller), + ); + + let total_minted_liquidity = ::Currency::total_issuance(liquidity_asset_id); + let half_of_minted_liquidity = total_minted_liquidity / 2_u32.into(); + let quater_of_minted_liquidity = total_minted_liquidity / 4_u32.into(); + + PoS::::activate_liquidity_for_native_rewards(RawOrigin::Signed(caller.clone()).into(), liquidity_asset_id.into(), quater_of_minted_liquidity, None).unwrap(); + + assert_eq!( + PoS::::get_rewards_info(caller.clone(), liquidity_asset_id).activated_amount, + quater_of_minted_liquidity + ); + + forward_to_next_session::(); + + }: activate_liquidity_for_native_rewards(RawOrigin::Signed(caller.clone().into()), liquidity_asset_id.into(), quater_of_minted_liquidity, None) + verify { + + assert_eq!( + PoS::::get_rewards_info(caller.clone(), liquidity_asset_id).activated_amount, + half_of_minted_liquidity + ) + } + + deactivate_liquidity_for_native_rewards{ + // deactivate + // 1 crate pool + // 2 promote pool + // 3 mint some tokens + // deactivate some tokens (all or some - to be checked) + + init::(); + let caller: ::AccountId = whitelisted_caller(); + let initial_amount: BalanceOf = 1000000000000000000000_u128.try_into().ok().expect("should fit"); + let native_asset_id = ::NativeCurrencyId::get(); + + loop { + let token_id = TokensOf::::create(&caller, MILION.to_balance::()).unwrap(); + if token_id > native_asset_id { + break; + } + } + + let amount: BalanceOf = ((40000000000000000000_u128/2_u128) + (60000000000000000000_u128/2_u128)).try_into().ok().expect("should fit"); + let liquidity_asset_id = ::Currency::create(&caller, amount).unwrap(); + PoS::::enable(liquidity_asset_id, 1u8); + + assert_eq!( + ::Currency::total_issuance(liquidity_asset_id), + ::Currency::free_balance(liquidity_asset_id, &caller), + ); + + let total_minted_liquidity = ::Currency::total_issuance(liquidity_asset_id); + let half_of_minted_liquidity = total_minted_liquidity / 2_u32.into(); + let quater_of_minted_liquidity = total_minted_liquidity / 4_u32.into(); + + PoS::::activate_liquidity_for_native_rewards(RawOrigin::Signed(caller.clone().into()).into(), liquidity_asset_id.into(), half_of_minted_liquidity, None).unwrap(); + + assert_eq!( + PoS::::get_rewards_info(caller.clone(), liquidity_asset_id).activated_amount, + half_of_minted_liquidity + ); + + forward_to_next_session::(); + + }: deactivate_liquidity(RawOrigin::Signed(caller.clone().into()), liquidity_asset_id, quater_of_minted_liquidity) + verify { + assert_eq!( + PoS::::get_rewards_info(caller.clone(), liquidity_asset_id).activated_amount, + quater_of_minted_liquidity + ); + } + + reward_pool{ + // 1 crate as many schedules as possible + // 2 wait for one of the schedules to expire + // 3 create new schedule that will replace the expired one + + init::(); + + let schedules_limit = 10u32; + let caller: ::AccountId = whitelisted_caller(); + let native_asset_id = ::NativeCurrencyId::get(); + + loop { + let token_id = TokensOf::::create(&caller, MILION.to_balance::()).unwrap(); + if token_id > native_asset_id { + break; + } + } + + let REWARDS_AMOUNT: u128 = ::Min3rdPartyRewardValutationPerSession::get() * 10u128; + let POOL_VOLUME: u128 = ::Min3rdPartyRewardVolume::get() * 1_000_000u128; + + let native_asset_amount: u128 = POOL_VOLUME * Into::::into(schedules_limit + 1); + TokensOf::::mint(native_asset_id.into(), &caller, native_asset_amount.to_balance::()).unwrap(); + + for _ in 0 .. schedules_limit - 2 { + let token_id = TokensOf::::create(&caller, POOL_VOLUME.to_balance::()).unwrap(); + XykOf::::create_pool(caller.clone(), native_asset_id.into(), POOL_VOLUME.to_balance::(), token_id, POOL_VOLUME.to_balance::()).unwrap(); + let reward_token = token_id + One::one(); + let balance:u128 = TokensOf::::free_balance(reward_token.into(), &caller).into(); + + PoS::::reward_pool( + RawOrigin::Signed(caller.clone().into()).into(), + reward_token, + reward_token.into(), + (REWARDS_AMOUNT).to_balance::(), + 10u32.into(), + ).unwrap(); + } + + let token_id = TokensOf::::create(&caller, POOL_VOLUME.to_balance::()).unwrap(); + XykOf::::create_pool(caller.clone(), native_asset_id.into(), POOL_VOLUME.to_balance::(), token_id.into(), POOL_VOLUME.to_balance::()).unwrap(); + let reward_token = token_id + One::one(); + PoS::::reward_pool( + RawOrigin::Signed(caller.clone().into()).into(), + reward_token, + reward_token.into(), + REWARDS_AMOUNT.to_balance::(), + 2u32.into(), + ).unwrap(); + + forward_to_next_session::(); + forward_to_next_session::(); + forward_to_next_session::(); + + let token_id = TokensOf::::create(&caller, POOL_VOLUME.to_balance::()).unwrap(); + XykOf::::create_pool(caller.clone(), native_asset_id.into(), POOL_VOLUME.to_balance::(), token_id.into(), POOL_VOLUME.to_balance::()).unwrap(); + let reward_token = token_id + One::one(); + + assert_eq!( + PoS::::list_metadata().count, + (schedules_limit - 1 ) as u64 + ); + + }: reward_pool(RawOrigin::Signed(caller.clone().into()), reward_token, reward_token.into(), REWARDS_AMOUNT.to_balance::(), 10u32.into()) + verify { + + assert_eq!( + PoS::::list_metadata().count, + (schedules_limit as u64) + ); + + } + + activate_liquidity_for_3rdparty_rewards{ + // 1 create pool that can be rewarded + // 2 create token that is used as reward + // 3 activate rewards + + init::(); + + let schedules_limit = 10u32; + let caller: ::AccountId = whitelisted_caller(); + let native_asset_id = ::NativeCurrencyId::get(); + let REWARDS_AMOUNT: u128 = ::Min3rdPartyRewardValutationPerSession::get() * (schedules_limit as u128); + let POOL_VOLUME: u128 = ::Min3rdPartyRewardVolume::get() * 1_000_000u128; + + loop { + let token_id = TokensOf::::create(&caller, REWARDS_AMOUNT.to_balance::()).unwrap(); + if token_id > native_asset_id { + break; + } + } + + let native_asset_amount: u128 = POOL_VOLUME * Into::::into(schedules_limit); + TokensOf::::mint(native_asset_id.into(), &caller, native_asset_amount.to_balance::()).unwrap(); + + let first_token_id= TokensOf::::create(&caller, POOL_VOLUME.to_balance::()).unwrap(); + XykOf::::create_pool(caller.clone(), native_asset_id, POOL_VOLUME.to_balance::(), first_token_id, POOL_VOLUME.to_balance::()).unwrap(); + let liquidity_asset_id = first_token_id + One::one(); + + let second_token_id = TokensOf::::create(&caller, POOL_VOLUME.to_balance::()).unwrap(); + XykOf::::create_pool(caller.clone(), native_asset_id.into(), POOL_VOLUME.to_balance::(), second_token_id.into(), POOL_VOLUME.to_balance::()).unwrap(); + let reward_token_id = second_token_id + One::one(); + + + PoS::::reward_pool( + RawOrigin::Signed(caller.clone().into()).into(), + liquidity_asset_id, + reward_token_id.into(), + REWARDS_AMOUNT.to_balance::(), + 2u32.into(), + ).unwrap(); + + }: activate_liquidity_for_3rdparty_rewards(RawOrigin::Signed(caller.clone().into()), liquidity_asset_id.into(), 10_000u128.to_balance::(), reward_token_id, None) + verify { + forward_to_next_session::(); + forward_to_next_session::(); + assert_eq!( + PoS::::calculate_3rdparty_rewards_amount(caller, liquidity_asset_id.into(), reward_token_id).unwrap(), + (REWARDS_AMOUNT/2).to_balance::() + ) + } + + deactivate_liquidity_for_3rdparty_rewards{ + // 1 create pool that can be rewarded + // 2 create token that is used as reward + // 3 activate rewards + // 4 deactivate rewards and unlock them + + init::(); + + let schedules_limit = ::RewardsSchedulesLimit::get(); + let caller: ::AccountId = whitelisted_caller(); + let native_asset_id = ::NativeCurrencyId::get(); + let REWARDS_AMOUNT: u128 = 2u128 * ::Min3rdPartyRewardValutationPerSession::get(); + let POOL_VOLUME: u128 = ::Min3rdPartyRewardVolume::get() * 1_000_000u128; + + loop { + let token_id = TokensOf::::create(&caller, REWARDS_AMOUNT.to_balance::()).unwrap(); + if token_id > native_asset_id { + break; + } + } + + let native_asset_amount: u128 = POOL_VOLUME * Into::::into(schedules_limit); + TokensOf::::mint(native_asset_id.into(), &caller, POOL_VOLUME.to_balance::()).unwrap(); + let first_token_id = TokensOf::::create(&caller, POOL_VOLUME.to_balance::()).unwrap(); + XykOf::::create_pool(caller.clone(), native_asset_id.into(), POOL_VOLUME.to_balance::(), first_token_id.into(), POOL_VOLUME.to_balance::()).unwrap(); + let liquidity_asset_id = first_token_id + One::one(); + + TokensOf::::mint(native_asset_id.into(), &caller, POOL_VOLUME.to_balance::()).unwrap(); + let second_token_id = TokensOf::::create(&caller, POOL_VOLUME.to_balance::()).unwrap(); + XykOf::::create_pool(caller.clone(), native_asset_id.into(), POOL_VOLUME.to_balance::(), second_token_id.into(), POOL_VOLUME.to_balance::()).unwrap(); + let reward_token_id = second_token_id + One::one(); + + PoS::::reward_pool( + RawOrigin::Signed(caller.clone().into()).into(), + liquidity_asset_id, + reward_token_id.into(), + REWARDS_AMOUNT.to_balance::(), + 2u32.into(), + ).unwrap(); + + assert!(TokensOf::::ensure_can_withdraw( + liquidity_asset_id.into(), + &caller, + REWARDS_AMOUNT.to_balance::(), + WithdrawReasons::all(), + Default::default(), + ).is_ok()); + + PoS::::activate_liquidity_for_3rdparty_rewards( + RawOrigin::Signed(caller.clone().into()).into(), + liquidity_asset_id, + 10_000u128.to_balance::(), + reward_token_id, + None + ).unwrap(); + + assert!( + TokensOf::::ensure_can_withdraw( + liquidity_asset_id.into(), + &caller, + POOL_VOLUME.to_balance::(), + WithdrawReasons::all(), + Default::default(), + ).is_err() + ); + + + }: deactivate_liquidity_for_3rdparty_rewards(RawOrigin::Signed(caller.clone().into()), liquidity_asset_id, 10_000u128.to_balance::(), reward_token_id) + verify { + + assert!(TokensOf::::ensure_can_withdraw( + liquidity_asset_id.into(), + &caller, + POOL_VOLUME.to_balance::(), + WithdrawReasons::all(), + Default::default(), + ).is_ok()); + } + + + claim_3rdparty_rewards{ + // 1 create pool that can be rewarded + // 2 create token that is used as reward + // 3 activate rewards + // 4 wait for rewards to be avialble + // 5 claim rewards + + init::(); + + let schedules_limit = ::RewardsSchedulesLimit::get(); + let caller: ::AccountId = whitelisted_caller(); + let native_asset_id = ::NativeCurrencyId::get(); + let REWARDS_AMOUNT: u128 = 2u128 * ::Min3rdPartyRewardValutationPerSession::get(); + let POOL_VOLUME: u128 = ::Min3rdPartyRewardVolume::get() * 1_000_000u128; + + loop { + let token_id = TokensOf::::create(&caller, REWARDS_AMOUNT.to_balance::()).unwrap(); + if token_id > native_asset_id { + break; + } + } + + let native_asset_amount: u128 = POOL_VOLUME * Into::::into(schedules_limit); + TokensOf::::mint(native_asset_id.into(), &caller, native_asset_amount.to_balance::()).unwrap(); + + let first_token_id = TokensOf::::create(&caller, POOL_VOLUME.to_balance::()).unwrap(); + XykOf::::create_pool(caller.clone(), native_asset_id.into(), POOL_VOLUME.to_balance::(), first_token_id.into(), POOL_VOLUME.to_balance::()).unwrap(); + let liquidity_asset_id = first_token_id + One::one(); + + let second_token_id = TokensOf::::create(&caller, POOL_VOLUME.to_balance::()).unwrap(); + XykOf::::create_pool(caller.clone(), native_asset_id.into(), POOL_VOLUME.to_balance::(), second_token_id.into(), POOL_VOLUME.to_balance::()).unwrap(); + let reward_token_id = second_token_id + One::one(); + + PoS::::reward_pool( + RawOrigin::Signed(caller.clone().into()).into(), + liquidity_asset_id, + reward_token_id.into(), + REWARDS_AMOUNT.to_balance::(), + 2u32.into(), + ).unwrap(); + + PoS::::activate_liquidity_for_3rdparty_rewards( + RawOrigin::Signed(caller.clone().into()).into(), + liquidity_asset_id, + 10_000u128.to_balance::(), + reward_token_id, + None + ).unwrap(); + + forward_to_next_session::(); + forward_to_next_session::(); + assert_eq!( + PoS::::calculate_3rdparty_rewards_amount(caller.clone(), liquidity_asset_id, reward_token_id).unwrap(), + (REWARDS_AMOUNT / 2).to_balance::() + ); + forward_to_next_session::(); + assert_eq!( + PoS::::calculate_3rdparty_rewards_amount(caller.clone(), liquidity_asset_id, reward_token_id).unwrap(), + REWARDS_AMOUNT.to_balance::() + ); + forward_to_next_session::(); + assert_eq!( + PoS::::calculate_3rdparty_rewards_amount(caller.clone(), liquidity_asset_id, reward_token_id).unwrap(), + REWARDS_AMOUNT.to_balance::() + ); + + let balance_before:u128 = TokensOf::::free_balance(reward_token_id.into(), &caller).into(); + + }: claim_3rdparty_rewards(RawOrigin::Signed(caller.clone().into()), liquidity_asset_id, reward_token_id) + verify { + + let balance_after:u128 = TokensOf::::free_balance(reward_token_id.into(), &caller).into(); + assert_eq!( + PoS::::calculate_3rdparty_rewards_amount(caller.clone(), liquidity_asset_id, reward_token_id).unwrap(), + 0u128.to_balance::() + ); + + assert_eq!(balance_after - balance_before, REWARDS_AMOUNT); + } + + impl_benchmark_test_suite!(PoS, crate::mock::new_test_ext(), crate::mock::Test) +} diff --git a/gasp-node/pallets/proof-of-stake/src/lib.rs b/gasp-node/pallets/proof-of-stake/src/lib.rs new file mode 100644 index 000000000..8e32aaa8f --- /dev/null +++ b/gasp-node/pallets/proof-of-stake/src/lib.rs @@ -0,0 +1,1782 @@ +#![cfg_attr(not(feature = "std"), no_std)] + +//! # Proof of Stake Module +//! +//! The goal of the Proof of Stake module is to reward people for providing liquidity to the Mangata DEX. +//! +//! ## Types of Rewards +//! +//! ### Native Rewards +//! +//! As described in Mangata tokenomics, during each session, some of the rewards are minted and distributed +//! among promoted pools. The council decides which pool to promote, and each promoted pool has a weight +//! assigned that determines how much of the rewards should +//! be distributed to that pool. +//! +//! The final amount of tokens that a user receives depends on: +//! - the amount of activated liquidity - rewards are distributed proportionally to the amount of +//! activated liquidity. +//! - the liquidity token itself - the more weight is assigned to the pool, the more rewards it receives. +//! - the time of liquidity activation - the longer a user stores liquidity, the more rewards they receive +//! (based on an asymptotic curve). +//! +//! Activated Liquidity cannot be transferred to another account; it is considered locked. The moment +//! liquidity is unlocked, the user loses the ability to claim rewards for that liquidity. +//! +//! #### Storage entries +//! +//! - [`TotalActivatedLiquidity`] - Stores information about the total amount of activated liquidity for +//! each liquidity token. +//! - [`PromotedPoolRewards`] - Stores information about the total amount of rewards for each liquidity +//! token. +//! - [`RewardsInfo`] - Stores information about rewards for liquidity mining. +//! - [`ThirdPartyActivationKind`] - Wrapper over origin ActivateKind that is used in +//! +//! #### Extrinsics +//! +//! - [`Pallet::activate_liquidity`] - Activates liquidity for liquidity mining rewards. +//! - [`Pallet::deactivate_liquidity_for_native_rewards`] - Deactivates liquidity for liquidity mining rewards. +//! - [`Pallet::claim_native_rewards`] - Claims all rewards for all liquidity tokens. +//! - [`Pallet::update_pool_promotion`] - Enables/disables the pool for liquidity mining rewards. +//! +//! ### 3rd Party Rewards +//! +//! Anyone can provide tokens to reward people who store a particular liquidity token. Any +//! liquidity token can be rewarded with any other token provided by the user. Liquidity can be +//! activated for multiple scheduled rewards related to that liquidity token. Tokens will remain +//! locked (untransferable) as long as there is at least one schedule for which these rewards are +//! activated. +//! +//! #### Storage entries +//! +//! - [`RewardsInfoForScheduleRewards`] - Stores information about rewards for scheduled rewards. +//! - [`ScheduleRewardsTotal`] - Stores the amount of rewards per single liquidity token. +//! - [`RewardsSchedules`] - Stores information about scheduled rewards. +//! - [`ScheduleId`] - Stores the unique id of the schedule. +//! - [`RewardTokensPerPool`] - Stores information about which reward tokens are used for a particular +//! liquidity token. +//! - [`TotalActivatedLiquidityForSchedules`] - Stores information about the total amount of activated +//! liquidity for each schedule. +//! - [`ActivatedLiquidityForSchedules`] - Stores information about how much liquidity was activated for +//! each schedule. +//! - [`ActivatedLockedLiquidityForSchedules`] - Stores information about how much liquidity was activated +//! for each schedule and not yet liquidity mining rewards. +//! +//! +//! #### Extrinsics +//! +//! - [`Pallet::reward_pool`] - Schedules rewards for the selected liquidity token. +//! - [`Pallet::activate_liquidity_for_3rdparty_rewards`] - Activates liquidity for scheduled rewards. +//! - [`Pallet::deactivate_liquidity_for_3rdparty_rewards`] - Deactivates liquidity for scheduled rewards. +//! - [`Pallet::claim_3rdparty_rewards`] - Claims all scheduled rewards for all liquidity tokens. +//! +//! ## Reusing a Single Liquidity Token for Multiple Rewards +//! +//! It may happen that a single liquidity token is rewarded with: +//! - Liquidity Mining Rewards - because the pool was promoted by the council. +//! - Scheduled rewards with token X - because Alice decided to do so. +//! - Scheduled rewards with token Y - because Bob decided to do so. +//! +//! In that case, a single liquidity token can be used to obtain rewards from multiple sources. There are +//! several options to do that: +//! +//! - The user can reuse liquidity used for liquidity mining rewards to claim scheduled rewards. In +//! this case, [`Pallet::activate_liquidity_for_3rdparty_rewards`] should be used with [`ActivateKind::LiquidityMining`]. +//! +//! - The user can reuse liquidity used for scheduled rewards (X) to sign up for rewards from other tokens (provided by Bob). In that case, [`Pallet::activate_liquidity_for_3rdparty_rewards`] should be used with [`ActivateKind::ActivatedLiquidity(X)`]. +//! +//! - The user can't directly provide liquidity activated for scheduled rewards to activate it for native rewards. Instead: +//! * Liquidity used for schedule rewards can be deactivated +//! [`Pallet::deactivate_liquidity_for_3rdparty_rewards`]. +//! * Liquidity can be activated for liquidity mining rewards [`Pallet::activate_liquidity`]. +//! * Liquidity can be activated for scheduled rewards [`Pallet::activate_liquidity_for_3rdparty_rewards`] with [`ThirdPartyActivationKind::Mining`]. +//! +//! ## Unlocking tokens used for rewards +//! +//! Once liquidity tokens are used to sign up for rewards they persist on user account but they +//! become reserved/untransferable. In order to unlock them they need to be deactivated. Depending on rewards kind +//! (native or 3rdparty) deactivation process differs. +//! +//! ### Native rewards +//! - If liq tokens are used *only* for native rewards they are locked/unlocked in the same moment +//! liquidity is activated/deactivated. +//! - If liq tokens are `reactivated` (see [`ThirdPartyActivationKind::NativeRewardsLiquidity`]) +//! then: +//! * remaining( **not reactivated**) liq tokens that were not reactivated can be unlocked using [`Pallet::deactivate_liquidity_for_native_rewards`] +//! * reactivated liq tokens can be unlocked only after all of liquidity tokens ( **not only reactivated part** ) are deactivated from 3rdparty tokens and then [`Pallet::deactivate_liquidity_for_native_rewards`] is used +//! +//! ### 3rdparty rewards +//! If liq tokens are used for 3rdparty rewards they are locked in the moment of activation. To get +//! them unlocked you need to unlock all of them(liq tokens) for every 3rdparty rewards schedule/token they were used/activated. +//! Even if you want to deactivate only part of it, all of them needs to be deactivated, afterwards they +//! can be reactivated with no penalty in terms of recevied rewards (you will get same amount of rewards as you would +//! not be deactivating them) +//! +//! ### 3rdparty Rewards +//! +//! It may happen that a single liquidity token is rewarded with: +//! - Liquidity Mining Rewards - because the pool was promoted by the council. +//! - Scheduled rewards with token X - because Alice decided to do so. +//! - Scheduled rewards with token Y - because Bob decided to do so. +//! +//! In that case, a single liquidity token can be used to obtain rewards from multiple sources. There are +//! several options to do that: +//! +//! - The user can reuse liquidity used for liquidity mining rewards to claim scheduled rewards. In +//! this case, [`Pallet::activate_liquidity_for_3rdparty_rewards`] should be used with [`ActivateKind::LiquidityMining`]. +//! +//! - The user can reuse liquidity used for scheduled rewards (X) to sign up for rewards from other tokens (provided by Bob). In that case, [`Pallet::activate_liquidity_for_3rdparty_rewards`] should be used with [`ActivateKind::ActivatedLiquidity(X)`]. +//! +//! - The user can't directly provide liquidity activated for scheduled rewards to activate it for native rewards. Instead: +//! * Liquidity used for schedule rewards can be deactivated +//! [`Pallet::deactivate_liquidity_for_3rdparty_rewards`]. +//! * Liquidity can be activated for liquidity mining rewards [`Pallet::activate_liquidity`]. +//! * Liquidity can be activated for scheduled rewards [`Pallet::activate_liquidity_for_3rdparty_rewards`] with [`ThirdPartyActivationKind::Mining`]. + +use frame_support::pallet_prelude::*; + +pub type ScheduleId = u64; + +#[derive(Encode, Decode, Clone, Default, RuntimeDebug, PartialEq, Eq, TypeInfo, MaxEncodedLen)] +#[codec(mel_bound(T: Config))] +#[scale_info(skip_type_params(T))] + +pub struct Schedule { + scheduled_at: SessionId, + last_session: SessionId, + liq_token: CurrencyIdOf, + reward_token: CurrencyIdOf, + amount_per_session: BalanceOf, +} + +#[derive(Encode, Decode, Default, TypeInfo)] +pub struct SchedulesList { + pub head: Option, + pub tail: Option, + pub pos: Option, + pub count: u64, +} + +use frame_support::{ + dispatch::{DispatchErrorWithPostInfo, DispatchResult, PostDispatchInfo}, + ensure, + storage::bounded_btree_map::BoundedBTreeMap, +}; +use frame_system::ensure_signed; +use mangata_support::pools::{Valuate, ValuateFor}; +use mangata_types::multipurpose_liquidity::ActivateKind; +use orml_tokens::{MultiTokenCurrencyExtended, MultiTokenReservableCurrency}; +use sp_core::U256; +use sp_runtime::traits::AccountIdConversion; + +use frame_support::{ + pallet_prelude::*, + traits::{tokens::currency::MultiTokenCurrency, Contains, ExistenceRequirement, Get}, + transactional, +}; + +use frame_system::pallet_prelude::*; +use mangata_support::traits::{ + ActivationReservesProviderTrait, LiquidityMiningApi, ProofOfStakeRewardsApi, +}; + +#[cfg(feature = "runtime-benchmarks")] +use mangata_support::traits::XykFunctionsTrait; + +use sp_std::collections::btree_map::BTreeMap; + +use sp_runtime::{ + traits::{CheckedAdd, CheckedSub, SaturatedConversion, Zero}, + DispatchError, Perbill, +}; +use sp_std::{convert::TryInto, prelude::*}; + +mod reward_info; +use reward_info::{RewardInfo, RewardsCalculator}; + +mod schedule_rewards_calculator; +use schedule_rewards_calculator::{ + ActivatedLiquidityPerSchedule, ScheduleRewards, ScheduleRewardsCalculator, +}; + +mod benchmarking; + +#[cfg(test)] +mod mock; + +#[cfg(all(test, not(feature = "runtime-benchmarks")))] +mod tests; + +#[cfg(any(feature = "runtime-benchmarks", test))] +mod utils; + +pub(crate) const LOG_TARGET: &str = "pos"; + +// syntactic sugar for logging. +#[macro_export] +macro_rules! log { + ($level:tt, $patter:expr $(, $values:expr)* $(,)?) => { + log::$level!( + target: $crate::LOG_TARGET, + concat!("[{:?}] 💸 ", $patter), >::block_number() $(, $values)* + ) + }; +} + +pub use pallet::*; + +pub mod weights; +pub use weights::WeightInfo; + +type AccountIdOf = ::AccountId; + +type BalanceOf = <::Currency as MultiTokenCurrency< + ::AccountId, +>>::Balance; + +type CurrencyIdOf = <::Currency as MultiTokenCurrency< + ::AccountId, +>>::CurrencyId; + +/// - `LiquidityMining` - already activated liquidity (for liquidity mining rewards) +#[derive(Eq, PartialEq, Clone, Encode, Decode, RuntimeDebug, TypeInfo)] +pub enum ThirdPartyActivationKind { + ActivateKind(Option), + + ActivatedLiquidity(CurrencyId), + NativeRewardsLiquidity, +} + +const PALLET_ID: frame_support::PalletId = frame_support::PalletId(*b"rewards!"); +pub type SessionId = u32; +#[frame_support::pallet] +pub mod pallet { + + use super::*; + + #[pallet::pallet] + #[pallet::without_storage_info] + pub struct Pallet(PhantomData); + + #[pallet::hooks] + impl Hooks> for Pallet { + fn on_initialize(_n: BlockNumberFor) -> Weight { + let session_id = Self::session_index(); + + // NOTE: 1R + if Self::is_new_session() { + SchedulesListMetadata::::mutate(|s| s.pos = None); + return Default::default() + } + + for _ in 0..T::SchedulesPerBlock::get() { + // READS PER ITERTION + // + // ON VALID SCHEDULE (AVERAGE) ====> 1 RW + N*R + N*W + // 1 x READ/WRITE SCHEDULE META(HEAD,TAIL,POS) : ALWAYS + // PER ITER: + // - READ RewardsSchedulesList : ALWAYS + // - WRITE ScheduleRewardsTotal : ALWAYS (pesemisitic) + + // ON OUTDATED SCHEDULE (PESIMITIC) =====> 1 RW + (N-1)*W + 1W + // 1 x READ/WRITE SCHEDULE META(HEAD,TAIL,POS) : ALWAYS + // REMOVE N-1 SCHEDULES IN THE MIDDDLE + // - 1 x WRITE update previous schedudle `next` : ALWAYS (pesemisitic) + // REMOVE LAST ELEM: + // - 1 x write update list tail (already counted in) + // - 1 x WRITE update elem before last : ALWAYS (pesemisitic) + + // NOTE: 1R + let s = SchedulesListMetadata::::get(); + let last_valid = s.pos; + // NOTE: 1R + let pos = match (last_valid, s.head) { + (Some(pos), _) => { + if let Some((_schedule, next)) = RewardsSchedulesList::::get(pos) { + next + } else { + None + } + }, + (None, Some(head)) => Some(head), + _ => None, + }; + + if let Some(pos_val) = pos { + // NOTE: 1R + if let Some((schedule, next)) = RewardsSchedulesList::::get(pos_val) { + if schedule.last_session >= session_id { + if schedule.scheduled_at < session_id { + // NOTE: 1R 1W + ScheduleRewardsTotal::::mutate( + (schedule.liq_token, schedule.reward_token), + |s| s.provide_rewards(session_id, schedule.amount_per_session), + ); + } + // NOTE: 1W + SchedulesListMetadata::::mutate(|s| s.pos = Some(pos_val)); + } else { + // NOTE: 2R + // + let meta = Self::list_metadata(); + match (meta.head, meta.tail) { + (Some(head), Some(tail)) if head == pos_val && head != tail => { + if let Some(next) = next { + // NOTE: 1W + SchedulesListMetadata::::mutate(|s| { + s.head = Some(next); + s.count -= 1; + }); + } + }, + (Some(head), Some(tail)) if tail == pos_val && head == tail => { + // NOTE: 3W + SchedulesListMetadata::::mutate(|s| { + s.tail = None; + s.head = None; + s.pos = None; + s.count = 0; + }); + }, + (Some(head), Some(tail)) if tail == pos_val && head != tail => { + if let Some(last_valid) = last_valid { + // NOTE: 1W + SchedulesListMetadata::::mutate(|s| { + s.tail = Some(last_valid); + s.count -= 1; + }); + // NOTE: 1R 1W + RewardsSchedulesList::::mutate(last_valid, |data| { + if let Some((_schedule, next)) = data.as_mut() { + *next = None + } + }); + } + }, + (Some(_head), Some(_tail)) => { + if let Some(last_valid) = last_valid { + SchedulesListMetadata::::mutate(|s| { + s.count -= 1; + }); + // NOTE: 1R 1W + RewardsSchedulesList::::mutate(last_valid, |data| { + if let Some((_schedule, prev_next)) = data.as_mut() { + *prev_next = next + } + }); + } + }, + _ => {}, + } + } + } + } else { + break + } + } + + // always use same amount of block space even if no schedules were processed + T::DbWeight::get().reads(1) + + T::DbWeight::get().writes(1) + + T::DbWeight::get().reads(T::SchedulesPerBlock::get().into()) + + T::DbWeight::get().writes(T::SchedulesPerBlock::get().into()) + } + } + + #[cfg(feature = "runtime-benchmarks")] + pub trait PoSBenchmarkingConfig: pallet_issuance::Config {} + #[cfg(feature = "runtime-benchmarks")] + impl PoSBenchmarkingConfig for T {} + + #[cfg(not(feature = "runtime-benchmarks"))] + pub trait PoSBenchmarkingConfig {} + #[cfg(not(feature = "runtime-benchmarks"))] + impl PoSBenchmarkingConfig for T {} + + #[pallet::config] + pub trait Config: frame_system::Config + PoSBenchmarkingConfig { + type RuntimeEvent: From> + IsType<::RuntimeEvent>; + type ActivationReservesProvider: ActivationReservesProviderTrait< + Self::AccountId, + BalanceOf, + CurrencyIdOf, + >; + type NativeCurrencyId: Get>; + type Currency: MultiTokenCurrencyExtended + + MultiTokenReservableCurrency; + #[pallet::constant] + /// The account id that holds the liquidity mining issuance + type LiquidityMiningIssuanceVault: Get; + #[pallet::constant] + type RewardsDistributionPeriod: Get; + /// The maximum number of schedules that can be active at one moment + type RewardsSchedulesLimit: Get; + /// The minimum number of rewards per session for schedule rewards + type Min3rdPartyRewardValutationPerSession: Get; + type Min3rdPartyRewardVolume: Get; + type SchedulesPerBlock: Get; + + type WeightInfo: WeightInfo; + + type ValuationApi: ValuateFor< + ::NativeCurrencyId, + CurrencyId = CurrencyIdOf, + Balance = BalanceOf, + >; + + /// Tokens which cannot be transfered by extrinsics/user or use in pool, unless foundation override + type NontransferableTokens: Contains>; + + #[cfg(feature = "runtime-benchmarks")] + type Xyk: XykFunctionsTrait, CurrencyIdOf>; + } + + #[pallet::error] + /// Errors + pub enum Error { + /// Not enought assets + NotEnoughAssets, + /// Math overflow + MathOverflow, + /// Not enough rewards earned + NotEnoughRewardsEarned, + /// Not a promoted pool + NotAPromotedPool, + /// Past time calculation + PastTimeCalculation, + LiquidityCheckpointMathError, + CalculateRewardsMathError, + MathError, + CalculateRewardsAllMathError, + MissingRewardsInfoError, + DeprecatedExtrinsic, + /// Cannot schedule rewards in past + CannotScheduleRewardsInPast, + /// Pool does not exist + PoolDoesNotExist, + /// Too many schedules + TooManySchedules, + /// Too little rewards per session + TooLittleRewards, + /// Too small volume of the pool + TooSmallVolume, + // Liquidity is reused for 3rdparty rewards + LiquidityLockedIn3rdpartyRewards, + // No rewards to claim + NoThirdPartyPartyRewardsToClaim, + // cannot promote solo token + SoloTokenPromotionForbiddenError, + /// Asset cannot be used for rewards + NontransferableToken, + } + + #[pallet::event] + #[pallet::generate_deposit(pub(super) fn deposit_event)] + pub enum Event { + PoolPromotionUpdated(CurrencyIdOf, Option), + LiquidityActivated(T::AccountId, CurrencyIdOf, BalanceOf), + LiquidityDeactivated(T::AccountId, CurrencyIdOf, BalanceOf), + RewardsClaimed(T::AccountId, CurrencyIdOf, BalanceOf), + ThirdPartyRewardsClaimed(T::AccountId, CurrencyIdOf, CurrencyIdOf, BalanceOf), + ThirdPartyLiquidityActivated(T::AccountId, CurrencyIdOf, CurrencyIdOf, BalanceOf), + ThirdPartyLiquidityDeactivated( + T::AccountId, + CurrencyIdOf, + CurrencyIdOf, + BalanceOf, + ), + ThirdPartySuccessfulPoolPromotion( + T::AccountId, + CurrencyIdOf, + CurrencyIdOf, + BalanceOf, + ), + } + + #[pallet::storage] + #[pallet::getter(fn get_rewards_info)] + pub type RewardsInfo = StorageDoubleMap< + _, + Twox64Concat, + AccountIdOf, + Twox64Concat, + CurrencyIdOf, + RewardInfo>, + ValueQuery, + >; + + #[pallet::storage] + /// Stores information about pool weight and accumulated rewards. The accumulated + /// rewards amount is the number of rewards that can be claimed per liquidity + /// token. Here is tracked the number of rewards per liquidity token relationship. + /// Expect larger values when the number of liquidity tokens are smaller. + pub type PromotedPoolRewards = + StorageValue<_, BTreeMap, PromotedPools>, ValueQuery>; + + #[derive(Encode, Decode, Clone, Default, RuntimeDebug, PartialEq, Eq, TypeInfo)] + /// Information about single token rewards + pub struct PromotedPools { + // Weight of the pool, each of the activated tokens has its weight assignedd + // Liquidityt Mining Rewards are distributed based on that weight + pub weight: u8, + /// **Cumulative** number of rewards amount that can be claimed for single + /// activted liquidity token + pub rewards: U256, + } + + #[pallet::storage] + #[pallet::getter(fn total_activated_amount)] + pub type TotalActivatedLiquidity = + StorageMap<_, Twox64Concat, CurrencyIdOf, BalanceOf, ValueQuery>; + + // ////////////////////////////////////////////////////////////////////////////////////////////// + // 3rd Party Rewards + // ////////////////////////////////////////////////////////////////////////////////////////////// + + /// Stores information about pool weight and accumulated rewards + #[pallet::storage] + pub type RewardsInfoForScheduleRewards = StorageDoubleMap< + _, + Twox64Concat, + AccountIdOf, + Twox64Concat, + (CurrencyIdOf, CurrencyIdOf), + RewardInfo>, + ValueQuery, + >; + + /// How much scheduled rewards per single liquidty_token should be distribute_rewards + /// the **value is multiplied by u128::MAX** to avoid floating point arithmetic + #[pallet::storage] + pub type ScheduleRewardsTotal = StorageMap< + _, + Twox64Concat, + (CurrencyIdOf, CurrencyIdOf), + ScheduleRewards>, + ValueQuery, + >; + + #[pallet::storage] + pub type ScheduleRewardsPerLiquidity = + StorageMap<_, Twox64Concat, (CurrencyIdOf, CurrencyIdOf), (U256, u64), ValueQuery>; + + /// List of activated schedules sorted by expiry date + #[pallet::storage] + #[pallet::getter(fn schedules)] + pub type RewardsSchedules = StorageValue< + _, + BoundedBTreeMap< + (BlockNumberFor, CurrencyIdOf, CurrencyIdOf, BalanceOf, u64), + (), + T::RewardsSchedulesLimit, + >, + ValueQuery, + >; + + #[pallet::storage] + #[pallet::getter(fn list_metadata)] + pub type SchedulesListMetadata = StorageValue<_, SchedulesList, ValueQuery>; + + #[pallet::storage] + pub type RewardsSchedulesList = + StorageMap<_, Twox64Concat, ScheduleId, (Schedule, Option), OptionQuery>; + + /// Maps liquidity token to list of tokens that it ever was rewarded with + #[pallet::storage] + pub type RewardTokensPerPool = StorageDoubleMap< + _, + Twox64Concat, + CurrencyIdOf, + Twox64Concat, + CurrencyIdOf, + (), + ValueQuery, + >; + + /// Tracks number of activated liquidity per schedule. It is used for calculation of + /// "cumulative rewrds amount" per 1 liquidity token. Therefore activation/deactivation needs + /// to be deffered same way as schedule rewards are delayed. + #[pallet::storage] + pub type TotalActivatedLiquidityForSchedules = StorageDoubleMap< + _, + Twox64Concat, + CurrencyIdOf, + Twox64Concat, + CurrencyIdOf, + ActivatedLiquidityPerSchedule>, + ValueQuery, + >; + + /// Tracks how much liquidity user activated for particular (liq token, reward token) pair + /// StorageNMap was used because it only require single read to know if user deactivated all + /// liquidity associated with particular liquidity_token that is rewarded. If so part of the + /// liquididty tokens can be unlocked. + #[pallet::storage] + pub type ActivatedLiquidityForSchedules = StorageNMap< + _, + ( + NMapKey>, + NMapKey>, + NMapKey>, + ), + BalanceOf, + OptionQuery, + >; + + /// Tracks how much of the liquidity was activated for schedule rewards and not yet + /// liquidity mining rewards. That information is essential to properly handle token unlcocks + /// when liquidity is deactivated. + #[pallet::storage] + pub type ActivatedLockedLiquidityForSchedules = StorageDoubleMap< + _, + Twox64Concat, + AccountIdOf, + Twox64Concat, + CurrencyIdOf, + BalanceOf, + ValueQuery, + >; + + /// Tracks how much of the liquidity was activated for schedule rewards and not yet + /// liquidity mining rewards. That information is essential to properly handle token unlcocks + /// when liquidity is deactivated. + #[pallet::storage] + pub type ActivatedNativeRewardsLiq = StorageDoubleMap< + _, + Twox64Concat, + AccountIdOf, + Twox64Concat, + CurrencyIdOf, + BalanceOf, + ValueQuery, + >; + + #[pallet::call] + impl Pallet { + /// Claims liquidity mining rewards + #[transactional] + #[pallet::call_index(0)] + #[pallet::weight(<::WeightInfo>::claim_native_rewards())] + #[deprecated(note = "claim_native_rewards should be used instead")] + pub fn claim_rewards_all( + origin: OriginFor, + liquidity_token_id: CurrencyIdOf, + ) -> DispatchResult { + let sender = ensure_signed(origin)?; + + >::claim_rewards_all( + sender, + liquidity_token_id, + )?; + + Ok(()) + } + + /// Enables/disables pool for liquidity mining rewards + #[pallet::call_index(1)] + #[pallet::weight(<::WeightInfo>::update_pool_promotion())] + pub fn update_pool_promotion( + origin: OriginFor, + liquidity_token_id: CurrencyIdOf, + liquidity_mining_issuance_weight: u8, + ) -> DispatchResult { + ensure_root(origin)?; + + ensure!( + T::ValuationApi::check_pool_exist(liquidity_token_id).is_ok(), + Error::::SoloTokenPromotionForbiddenError + ); + + if liquidity_mining_issuance_weight > 0 { + >::enable( + liquidity_token_id, + liquidity_mining_issuance_weight, + ); + } else { + >::disable(liquidity_token_id); + } + Ok(()) + } + + /// Increases number of tokens used for liquidity mining purposes. + /// + /// Parameters: + /// - liquidity_token_id - id of the token + /// - amount - amount of the token + /// - use_balance_from - where from tokens should be used + #[transactional] + #[pallet::call_index(2)] + #[pallet::weight(<::WeightInfo>::activate_liquidity_for_native_rewards())] + #[deprecated(note = "activate_liquidity_for_native_rewards should be used instead")] + pub fn activate_liquidity( + origin: OriginFor, + liquidity_token_id: CurrencyIdOf, + amount: BalanceOf, + use_balance_from: Option, + ) -> DispatchResult { + let sender = ensure_signed(origin)?; + + >::activate_liquidity( + sender, + liquidity_token_id, + amount, + use_balance_from, + ) + } + + /// Decreases number of tokens used for liquidity mining purposes + #[transactional] + #[pallet::call_index(3)] + #[pallet::weight(<::WeightInfo>::deactivate_liquidity_for_native_rewards())] + #[deprecated(note = "deactivate_liquidity_for_native_rewards should be used instead")] + pub fn deactivate_liquidity( + origin: OriginFor, + liquidity_token_id: CurrencyIdOf, + amount: BalanceOf, + ) -> DispatchResult { + let sender = ensure_signed(origin)?; + + Self::deactivate_liquidity_for_native_rewards_impl(sender, liquidity_token_id, amount) + } + + /// Schedules rewards for selected liquidity token + /// - tokens - pair of tokens + /// - amount - amount of the token + /// - schedule_end - id of the last rewarded seession. Rewards will be distributedd equally between sessions in range (now .. + /// schedule_end). Distribution starts from the *next* session till `schedule_end`. + #[transactional] + #[pallet::call_index(4)] + #[pallet::weight(<::WeightInfo>::reward_pool())] + pub fn reward_pool( + origin: OriginFor, + pool_id: CurrencyIdOf, + token_id: CurrencyIdOf, + amount: BalanceOf, + schedule_end: SessionId, + ) -> DispatchResult { + let sender = ensure_signed(origin)?; + + ensure!( + !T::NontransferableTokens::contains(&token_id), + Error::::NontransferableToken + ); + + Self::reward_pool_impl(sender, pool_id, token_id, amount, schedule_end) + } + + /// Increases number of tokens used for liquidity mining purposes. + /// + /// Parameters: + /// - liquidity_token_id - id of the token + /// - amount - amount of the token + /// - use_balance_from - where from tokens should be used. If set to `None` then tokens will + /// be taken from available balance + #[transactional] + #[pallet::call_index(5)] + #[pallet::weight(<::WeightInfo>::activate_liquidity_for_3rdparty_rewards())] + pub fn activate_liquidity_for_3rdparty_rewards( + origin: OriginFor, + liquidity_token_id: CurrencyIdOf, + amount: BalanceOf, + reward_token: CurrencyIdOf, + use_balance_from: Option>>, + ) -> DispatchResultWithPostInfo { + let sender = ensure_signed(origin)?; + + ensure!( + !T::NontransferableTokens::contains(&reward_token), + Error::::NontransferableToken + ); + + Self::activate_liquidity_for_3rdparty_rewards_impl( + sender, + liquidity_token_id, + amount, + use_balance_from.unwrap_or(ThirdPartyActivationKind::ActivateKind(None)), + reward_token, + ) + .map_err(|err| DispatchErrorWithPostInfo { + post_info: PostDispatchInfo { + actual_weight: Some( + <::WeightInfo>::activate_liquidity_for_3rdparty_rewards(), + ), + pays_fee: Pays::Yes, + }, + error: err, + })?; + Ok(Pays::No.into()) + } + + /// Decreases number of tokens used for liquidity mining purposes. + /// + /// Parameters: + /// - liquidity_token_id - id of the token + /// - amount - amount of the token + /// - use_balance_from - where from tokens should be used + #[transactional] + #[pallet::call_index(6)] + #[pallet::weight(<::WeightInfo>::deactivate_liquidity_for_3rdparty_rewards())] + pub fn deactivate_liquidity_for_3rdparty_rewards( + origin: OriginFor, + liquidity_token_id: CurrencyIdOf, + amount: BalanceOf, + + reward_token: CurrencyIdOf, + ) -> DispatchResultWithPostInfo { + let sender = ensure_signed(origin)?; + + Self::deactivate_liquidity_for_3rdparty_rewards_impl( + sender, + liquidity_token_id, + amount, + reward_token, + ) + .map_err(|err| DispatchErrorWithPostInfo { + post_info: PostDispatchInfo { + actual_weight: Some( + <::WeightInfo>::activate_liquidity_for_3rdparty_rewards(), + ), + pays_fee: Pays::Yes, + }, + error: err, + })?; + Ok(Pays::No.into()) + } + + /// Claims liquidity mining rewards + /// - tokens - pair of tokens + /// - amount - amount of the token + /// - reward_token - id of the token that is rewarded + #[transactional] + #[pallet::call_index(7)] + #[pallet::weight(<::WeightInfo>::claim_3rdparty_rewards())] + pub fn claim_3rdparty_rewards( + origin: OriginFor, + liquidity_token_id: CurrencyIdOf, + reward_token: CurrencyIdOf, + ) -> DispatchResultWithPostInfo { + let sender = ensure_signed(origin)?; + + ScheduleRewardsCalculator::::update_cumulative_rewards( + liquidity_token_id, + reward_token, + ); + Self::claim_schedule_rewards_all_impl(sender, liquidity_token_id, reward_token) + .map_err(|err| DispatchErrorWithPostInfo { + post_info: PostDispatchInfo { + actual_weight: Some(<::WeightInfo>::claim_3rdparty_rewards()), + pays_fee: Pays::Yes, + }, + error: err, + })?; + Ok(Pays::No.into()) + } + + /// Increases number of tokens used for liquidity mining purposes. + /// + /// Parameters: + /// - liquidity_token_id - id of the token + /// - amount - amount of the token + /// - use_balance_from - where from tokens should be used + #[transactional] + #[pallet::call_index(8)] + #[pallet::weight(<::WeightInfo>::activate_liquidity_for_native_rewards())] + pub fn activate_liquidity_for_native_rewards( + origin: OriginFor, + liquidity_token_id: CurrencyIdOf, + amount: BalanceOf, + use_balance_from: Option, + ) -> DispatchResult { + let sender = ensure_signed(origin)?; + + Self::activate_liquidity_for_native_rewards_impl( + sender, + liquidity_token_id, + amount, + use_balance_from, + ) + } + + /// Decreases number of tokens used for liquidity mining purposes + #[transactional] + #[pallet::call_index(9)] + #[pallet::weight(<::WeightInfo>::deactivate_liquidity_for_native_rewards())] + pub fn deactivate_liquidity_for_native_rewards( + origin: OriginFor, + liquidity_token_id: CurrencyIdOf, + amount: BalanceOf, + ) -> DispatchResult { + let sender = ensure_signed(origin)?; + + Self::deactivate_liquidity_for_native_rewards_impl(sender, liquidity_token_id, amount) + } + + #[transactional] + #[pallet::call_index(10)] + #[pallet::weight(<::WeightInfo>::claim_native_rewards())] + #[deprecated(note = "claim_native_rewards should be used instead")] + pub fn claim_native_rewards( + origin: OriginFor, + liquidity_token_id: CurrencyIdOf, + ) -> DispatchResult { + let sender = ensure_signed(origin)?; + + >::claim_rewards_all( + sender, + liquidity_token_id, + )?; + + Ok(()) + } + } +} + +impl Pallet { + fn activate_liquidity_for_native_rewards_impl( + user: AccountIdOf, + liquidity_asset_id: CurrencyIdOf, + amount: BalanceOf, + use_balance_from: Option, + ) -> DispatchResult { + Self::ensure_native_rewards_enabled(liquidity_asset_id)?; + + ensure!( + ::ActivationReservesProvider::can_activate( + liquidity_asset_id, + &user, + amount, + use_balance_from.clone() + ), + Error::::NotEnoughAssets + ); + + Self::set_liquidity_minting_checkpoint(user.clone(), liquidity_asset_id, amount)?; + + ::ActivationReservesProvider::activate( + liquidity_asset_id, + &user, + amount, + use_balance_from, + )?; + Pallet::::deposit_event(Event::LiquidityActivated(user, liquidity_asset_id, amount)); + + Ok(()) + } + + pub fn calculate_native_rewards_amount( + user: AccountIdOf, + liquidity_asset_id: CurrencyIdOf, + ) -> Result, DispatchError> { + Self::ensure_native_rewards_enabled(liquidity_asset_id)?; + let rewards_info = RewardsInfo::::try_get(user.clone(), liquidity_asset_id) + .or(Err(DispatchError::from(Error::::MissingRewardsInfoError)))?; + + let current_rewards = if rewards_info.activated_amount.is_zero() { + BalanceOf::::zero() + } else { + let calc = RewardsCalculator::<_, BalanceOf>::mining_rewards::( + user.clone(), + liquidity_asset_id, + )?; + calc.calculate_rewards().map_err(|err| Into::>::into(err))? + }; + + Ok(current_rewards + .checked_add(&rewards_info.rewards_not_yet_claimed) + .and_then(|v| v.checked_sub(&rewards_info.rewards_already_claimed)) + .ok_or(Error::::CalculateRewardsMathError)?) + } + + fn deactivate_liquidity_for_native_rewards_impl( + user: AccountIdOf, + liquidity_asset_id: CurrencyIdOf, + amount: BalanceOf, + ) -> DispatchResult { + if amount > BalanceOf::::zero() { + Self::set_liquidity_burning_checkpoint(user.clone(), liquidity_asset_id, amount)?; + Pallet::::deposit_event(Event::LiquidityDeactivated( + user, + liquidity_asset_id, + amount, + )); + } + Ok(()) + } + + fn activate_liquidity_for_3rdparty_rewards_impl( + user: AccountIdOf, + liquidity_asset_id: CurrencyIdOf, + amount: BalanceOf, + use_balance_from: ThirdPartyActivationKind>, + reward_token: CurrencyIdOf, + ) -> DispatchResult { + Self::ensure_3rdparty_rewards_enabled(liquidity_asset_id)?; + + match use_balance_from { + // 1R 1W + ThirdPartyActivationKind::ActivateKind(ref use_balance_from) => { + ensure!( + ::ActivationReservesProvider::can_activate( + liquidity_asset_id, + &user, + amount, + use_balance_from.clone(), + ), + Error::::NotEnoughAssets + ); + ActivatedLockedLiquidityForSchedules::::mutate( + user.clone(), + liquidity_asset_id, + |val| *val += amount, + ); + }, + // 2R + ThirdPartyActivationKind::ActivatedLiquidity(token_id) => { + let already_activated_amount = RewardsInfoForScheduleRewards::::get( + user.clone(), + (liquidity_asset_id, reward_token), + ) + .activated_amount; + let available_amount = RewardsInfoForScheduleRewards::::get( + user.clone(), + (liquidity_asset_id, token_id), + ) + .activated_amount; + ensure!( + already_activated_amount + amount <= available_amount, + Error::::NotEnoughAssets + ); + }, + ThirdPartyActivationKind::NativeRewardsLiquidity => { + let already_activated_amount = RewardsInfoForScheduleRewards::::get( + user.clone(), + (liquidity_asset_id, reward_token), + ) + .activated_amount; + let available_amount = + RewardsInfo::::get(user.clone(), liquidity_asset_id).activated_amount; + ensure!( + already_activated_amount + amount <= available_amount, + Error::::NotEnoughAssets + ); + ActivatedNativeRewardsLiq::::mutate(user.clone(), liquidity_asset_id, |val| { + *val += amount + }); + }, + } + + Self::set_liquidity_minting_checkpoint_3rdparty( + user.clone(), + liquidity_asset_id, + amount, + reward_token, + )?; + + match use_balance_from { + ThirdPartyActivationKind::ActivateKind(use_balance_from) => { + ::ActivationReservesProvider::activate( + liquidity_asset_id, + &user, + amount, + use_balance_from, + )?; + }, + _ => {}, + } + + Pallet::::deposit_event(Event::ThirdPartyLiquidityActivated( + user, + liquidity_asset_id, + reward_token, + amount, + )); + + Ok(()) + } + + fn deactivate_liquidity_for_3rdparty_rewards_impl( + user: AccountIdOf, + liquidity_asset_id: CurrencyIdOf, + amount: BalanceOf, + rewards_asset_id: CurrencyIdOf, + ) -> DispatchResult { + if amount > BalanceOf::::zero() { + Self::set_liquidity_burning_checkpoint_for_schedule( + user.clone(), + liquidity_asset_id, + amount, + rewards_asset_id, + )?; + Pallet::::deposit_event(Event::ThirdPartyLiquidityDeactivated( + user, + liquidity_asset_id, + rewards_asset_id, + amount, + )); + } + Ok(()) + } + + pub fn calculate_3rdparty_rewards_all( + user: AccountIdOf, + ) -> Vec<(CurrencyIdOf, CurrencyIdOf, BalanceOf)> { + let result = RewardsInfoForScheduleRewards::::iter_prefix(user.clone()) + .map(|((liq_token, reward_token), _)| { + Self::calculate_3rdparty_rewards_amount(user.clone(), liq_token, reward_token) + .map(|amount| (liq_token, reward_token, amount)) + }) + .collect::, _>>(); + let mut result = result.unwrap_or_default(); + result.sort(); + result + } + + pub fn calculate_3rdparty_rewards_amount( + user: AccountIdOf, + liquidity_asset_id: CurrencyIdOf, + rewards_asset_id: CurrencyIdOf, + ) -> Result, DispatchError> { + Self::ensure_3rdparty_rewards_enabled(liquidity_asset_id)?; + + if let Ok(info) = RewardsInfoForScheduleRewards::::try_get( + user.clone(), + (liquidity_asset_id, rewards_asset_id), + ) { + let current_rewards = if info.activated_amount == BalanceOf::::zero() { + BalanceOf::::zero() + } else { + let calc = RewardsCalculator::schedule_rewards::( + user.clone(), + liquidity_asset_id, + rewards_asset_id, + )?; + calc.calculate_rewards().map_err(|err| Into::>::into(err))? + }; + + Ok(current_rewards + .checked_add(&info.rewards_not_yet_claimed) + .and_then(|v| v.checked_sub(&info.rewards_already_claimed)) + .ok_or(Error::::CalculateRewardsMathError)?) + } else { + Ok(BalanceOf::::zero()) + } + } + + fn pallet_account() -> T::AccountId { + PALLET_ID.into_account_truncating() + } + + pub fn session_index() -> u32 { + Self::get_current_rewards_time().unwrap_or_default() + } + + pub fn rewards_period() -> u32 { + T::RewardsDistributionPeriod::get() + } + + pub fn is_new_session() -> bool { + let block_nr = frame_system::Pallet::::block_number().saturated_into::(); + (block_nr + 1) % Self::rewards_period() == 0u32 + } + + fn native_token_id() -> CurrencyIdOf { + ::NativeCurrencyId::get() + } + + fn get_pool_rewards(liquidity_asset_id: CurrencyIdOf) -> Result { + Ok(PromotedPoolRewards::::get() + .get(&liquidity_asset_id) + .map(|v| v.rewards) + .ok_or(Error::::NotAPromotedPool)?) + } + + fn get_current_rewards_time() -> Result { + >::block_number() + .saturated_into::() + .checked_add(1) + .and_then(|v| v.checked_div(T::RewardsDistributionPeriod::get())) + .ok_or(DispatchError::from(Error::::CalculateRewardsMathError)) + } + + fn ensure_native_rewards_enabled( + liquidity_asset_id: CurrencyIdOf, + ) -> Result<(), DispatchError> { + ensure!(Self::get_pool_rewards(liquidity_asset_id).is_ok(), Error::::NotAPromotedPool); + Ok(()) + } + + fn ensure_3rdparty_rewards_enabled( + liquidity_asset_id: CurrencyIdOf, + ) -> Result<(), DispatchError> { + ensure!( + RewardTokensPerPool::::iter_prefix_values(liquidity_asset_id) + .next() + .is_some(), + Error::::NotAPromotedPool + ); + Ok(()) + } + + fn set_liquidity_minting_checkpoint( + user: AccountIdOf, + liquidity_asset_id: CurrencyIdOf, + liquidity_assets_added: BalanceOf, + ) -> DispatchResult { + Self::ensure_native_rewards_enabled(liquidity_asset_id)?; + + { + let calc = RewardsCalculator::mining_rewards::(user.clone(), liquidity_asset_id)?; + let rewards_info = calc + .activate_more(liquidity_assets_added) + .map_err(|err| Into::>::into(err))?; + + RewardsInfo::::insert(user.clone(), liquidity_asset_id, rewards_info); + } + + TotalActivatedLiquidity::::try_mutate(liquidity_asset_id, |active_amount| { + if let Some(val) = active_amount.checked_add(&liquidity_assets_added) { + *active_amount = val; + Ok(()) + } else { + Err(()) + } + }) + .map_err(|_| DispatchError::from(Error::::LiquidityCheckpointMathError))?; + + Ok(()) + } + + fn set_liquidity_minting_checkpoint_3rdparty( + user: AccountIdOf, + liquidity_asset_id: CurrencyIdOf, + liquidity_assets_added: BalanceOf, + liquidity_assets_reward: CurrencyIdOf, + ) -> DispatchResult { + Self::ensure_3rdparty_rewards_enabled(liquidity_asset_id)?; + + ScheduleRewardsCalculator::::update_cumulative_rewards( + liquidity_asset_id, + liquidity_assets_reward, + ); + { + let calc = RewardsCalculator::schedule_rewards::( + user.clone(), + liquidity_asset_id, + liquidity_assets_reward, + )?; + let rewards_info = calc + .activate_more(liquidity_assets_added) + .map_err(|err| Into::>::into(err))?; + RewardsInfoForScheduleRewards::::insert( + user.clone(), + (liquidity_asset_id, liquidity_assets_reward), + rewards_info, + ); + } + + ActivatedLiquidityForSchedules::::try_mutate_exists( + (user.clone(), liquidity_asset_id, liquidity_assets_reward), + |v| { + match v { + Some(x) => { + v.as_mut().map(|a| *a += liquidity_assets_added); + }, + None => { + *v = Some(liquidity_assets_added); + }, + }; + Ok::<(), Error>(()) + }, + )?; + + ScheduleRewardsCalculator::::update_total_activated_liqudity( + liquidity_asset_id, + liquidity_assets_reward, + liquidity_assets_added, + true, + ); + + Ok(()) + } + + fn set_liquidity_burning_checkpoint( + user: AccountIdOf, + liquidity_asset_id: CurrencyIdOf, + liquidity_assets_burned: BalanceOf, + ) -> DispatchResult { + Self::ensure_native_rewards_enabled(liquidity_asset_id)?; + + let rewards_info = RewardsInfo::::try_get(user.clone(), liquidity_asset_id) + .or(Err(DispatchError::from(Error::::MissingRewardsInfoError)))?; + ensure!( + rewards_info.activated_amount >= liquidity_assets_burned, + Error::::NotEnoughAssets + ); + + ensure!( + rewards_info + .activated_amount + .checked_sub(&ActivatedNativeRewardsLiq::::get(user.clone(), liquidity_asset_id)) + .ok_or(Error::::MathOverflow)? >= + liquidity_assets_burned, + Error::::LiquidityLockedIn3rdpartyRewards + ); + + let calc = RewardsCalculator::mining_rewards::(user.clone(), liquidity_asset_id)?; + let rewards_info = calc + .activate_less(liquidity_assets_burned) + .map_err(|err| Into::>::into(err))?; + + RewardsInfo::::insert(user.clone(), liquidity_asset_id, rewards_info); + + TotalActivatedLiquidity::::try_mutate(liquidity_asset_id, |active_amount| { + if let Some(val) = active_amount.checked_sub(&liquidity_assets_burned) { + *active_amount = val; + Ok(()) + } else { + Err(()) + } + }) + .map_err(|_| DispatchError::from(Error::::LiquidityCheckpointMathError))?; + + ::ActivationReservesProvider::deactivate( + liquidity_asset_id, + &user, + liquidity_assets_burned, + ); + + Ok(()) + } + + fn set_liquidity_burning_checkpoint_for_schedule( + user: AccountIdOf, + liquidity_asset_id: CurrencyIdOf, + liquidity_assets_burned: BalanceOf, + reward_token: CurrencyIdOf, + ) -> DispatchResult { + Self::ensure_3rdparty_rewards_enabled(liquidity_asset_id)?; + ScheduleRewardsCalculator::::update_cumulative_rewards(liquidity_asset_id, reward_token); + + let calc = RewardsCalculator::schedule_rewards::( + user.clone(), + liquidity_asset_id, + reward_token, + )?; + + let rewards_info = calc + .activate_less(liquidity_assets_burned) + .map_err(|err| Into::>::into(err))?; + + RewardsInfoForScheduleRewards::::insert( + user.clone(), + (liquidity_asset_id, reward_token), + rewards_info, + ); + + ScheduleRewardsCalculator::::update_total_activated_liqudity( + liquidity_asset_id, + reward_token, + liquidity_assets_burned, + false, + ); + + ActivatedLiquidityForSchedules::::try_mutate_exists( + (user.clone(), liquidity_asset_id, reward_token), + |v| { + v.and_then(|a| { + a.checked_sub(&liquidity_assets_burned).and_then(|val| { + if val > BalanceOf::::zero() { + *v = Some(val); + } else { + *v = None; + } + Some(val) + }) + }) + .ok_or(Error::::MathOverflow) + }, + )?; + + if let None = ActivatedLiquidityForSchedules::::iter_prefix_values(( + user.clone(), + liquidity_asset_id, + )) + .next() + { + let amount = ActivatedLockedLiquidityForSchedules::::mutate( + user.clone(), + liquidity_asset_id, + |val| { + let prev = *val; + *val = BalanceOf::::zero(); + prev + }, + ); + + ::ActivationReservesProvider::deactivate( + liquidity_asset_id, + &user, + amount, + ); + + let _ = + ActivatedNativeRewardsLiq::::mutate(user.clone(), liquidity_asset_id, |val| { + let prev = *val; + *val = BalanceOf::::zero(); + prev + }); + } + + Ok(()) + } + + fn claim_schedule_rewards_all_impl( + user: T::AccountId, + liquidity_asset_id: CurrencyIdOf, + reward_token: CurrencyIdOf, + ) -> Result, DispatchError> { + Self::ensure_3rdparty_rewards_enabled(liquidity_asset_id)?; + + let calc = RewardsCalculator::schedule_rewards::( + user.clone(), + liquidity_asset_id, + reward_token, + )?; + let (rewards_info, total_available_rewards) = + calc.claim_rewards().map_err(|err| Into::>::into(err))?; + + ensure!( + total_available_rewards > Zero::zero(), + Error::::NoThirdPartyPartyRewardsToClaim + ); + + ::Currency::transfer( + reward_token.into(), + &Self::pallet_account(), + &user, + total_available_rewards, + ExistenceRequirement::KeepAlive, + )?; + + RewardsInfoForScheduleRewards::::insert( + user.clone(), + (liquidity_asset_id, reward_token), + rewards_info, + ); + + Pallet::::deposit_event(Event::ThirdPartyRewardsClaimed( + user, + liquidity_asset_id, + reward_token, + total_available_rewards, + )); + + Ok(total_available_rewards) + } + + pub(crate) fn reward_pool_impl( + sender: T::AccountId, + pool_id: CurrencyIdOf, + token_id: CurrencyIdOf, + amount: BalanceOf, + schedule_end: SessionId, + ) -> DispatchResult { + T::ValuationApi::check_pool_exist(pool_id).map_err(|_| Error::::PoolDoesNotExist)?; + + let current_session = Self::session_index(); + ensure!( + schedule_end.saturated_into::() > current_session, + Error::::CannotScheduleRewardsInPast + ); + + let amount_per_session: BalanceOf = schedule_end + .saturated_into::() + .checked_sub(current_session) + .and_then(|v| Into::::into(amount).checked_div(v.into())) + .ok_or(Error::::MathOverflow)? + .try_into() + .or(Err(Error::::MathOverflow))?; + + ensure!( + Self::verify_rewards_min_amount(token_id, amount_per_session), + Error::::TooLittleRewards + ); + + ensure!(Self::verify_rewards_min_volume(token_id), Error::::TooSmallVolume); + + RewardTokensPerPool::::insert(pool_id, token_id, ()); + + T::Currency::transfer( + token_id.into(), + &sender, + &Self::pallet_account(), + amount, + ExistenceRequirement::KeepAlive, + )?; + + let head = SchedulesListMetadata::::get().head; + let tail = SchedulesListMetadata::::get().tail; + + let schedule = Schedule { + scheduled_at: Self::session_index(), + last_session: schedule_end, + liq_token: pool_id, + reward_token: token_id, + amount_per_session, + }; + + match (head, tail) { + (None, None) => { + // first schedule + RewardsSchedulesList::::insert(0, (schedule, None::)); + SchedulesListMetadata::::mutate(|s| { + s.head = Some(0); + s.tail = Some(0); + s.count = 1; + }); + }, + (Some(_head), Some(tail)) => { + RewardsSchedulesList::::mutate(tail, |info| { + if let Some((_schedule, next)) = info.as_mut() { + *next = Some(tail + 1u64) + } + }); + RewardsSchedulesList::::insert(tail + 1, (schedule, None::)); + SchedulesListMetadata::::try_mutate(|s| { + if s.count < T::RewardsSchedulesLimit::get().into() { + s.tail = Some(tail + 1); + s.count += 1; + Ok(s.count) + } else { + Err(Error::::TooManySchedules) + } + })?; + }, + _ => {}, // invariant assures this will never happen + } + + Pallet::::deposit_event(Event::ThirdPartySuccessfulPoolPromotion( + sender, pool_id, token_id, amount, + )); + + Ok(()) + } + + fn verify_rewards_min_amount( + token_id: CurrencyIdOf, + amount_per_session: BalanceOf, + ) -> bool { + if T::ValuationApi::get_valuation_for_paired_for(token_id, amount_per_session).into() >= + T::Min3rdPartyRewardValutationPerSession::get() + { + return true + } + + if token_id == Self::native_token_id() && + amount_per_session.into() >= T::Min3rdPartyRewardValutationPerSession::get() + { + return true + } + + if T::ValuationApi::find_valuation_for(token_id, amount_per_session) + .unwrap_or_default() + .into() >= T::Min3rdPartyRewardValutationPerSession::get() + { + return true + } + + return false + } + + fn verify_rewards_min_volume(token_id: CurrencyIdOf) -> bool { + if token_id == Self::native_token_id() { + return true + } + + if let Some((native_reserves, _)) = + ::ValuationApi::get_reserve_and_lp_supply_for(token_id) + { + return native_reserves.into() >= T::Min3rdPartyRewardVolume::get() + } + + if let Ok((_, ids, reserves)) = T::ValuationApi::find_paired_pool_for(token_id) { + let native_reserves = + if ids.0 == Self::native_token_id() { reserves.0 } else { reserves.1 }; + return native_reserves.into() >= T::Min3rdPartyRewardVolume::get() + } + + return false + } +} + +impl ProofOfStakeRewardsApi, CurrencyIdOf> for Pallet { + #[cfg(feature = "runtime-benchmarks")] + fn enable_3rdparty_rewards( + account: T::AccountId, + pool: CurrencyIdOf, + reward_token_id: CurrencyIdOf, + last_session: u32, + amount: BalanceOf, + ) { + ::ValuationApi::check_pool_exist(pool).expect("pool exist"); + Pallet::::reward_pool_impl( + account.clone(), + pool, + reward_token_id, + amount, + last_session.into(), + ) + .expect("call should pass"); + } + + #[cfg(feature = "runtime-benchmarks")] + fn activate_liquidity_for_3rdparty_rewards( + account: T::AccountId, + liquidity_token: CurrencyIdOf, + amount: BalanceOf, + reward_token_id: CurrencyIdOf, + ) { + Pallet::::activate_liquidity_for_3rdparty_rewards_impl( + account, + liquidity_token, + amount, + ThirdPartyActivationKind::ActivateKind(None), + reward_token_id, + ) + .expect("call should pass") + } + + fn enable(liquidity_token_id: CurrencyIdOf, weight: u8) { + PromotedPoolRewards::::mutate(|promoted_pools| { + promoted_pools + .entry(liquidity_token_id) + .and_modify(|info| info.weight = weight) + .or_insert(PromotedPools { weight, rewards: U256::zero() }); + }); + Pallet::::deposit_event(Event::PoolPromotionUpdated(liquidity_token_id, Some(weight))); + } + + fn disable(liquidity_token_id: CurrencyIdOf) { + PromotedPoolRewards::::mutate(|promoted_pools| { + if let Some(info) = promoted_pools.get_mut(&liquidity_token_id) { + info.weight = 0; + } + }); + Pallet::::deposit_event(Event::PoolPromotionUpdated(liquidity_token_id, None)); + } + + fn is_enabled(liquidity_token_id: CurrencyIdOf) -> bool { + PromotedPoolRewards::::get().contains_key(&liquidity_token_id) + } + + fn claim_rewards_all( + user: T::AccountId, + liquidity_asset_id: CurrencyIdOf, + ) -> Result, DispatchError> { + Self::ensure_native_rewards_enabled(liquidity_asset_id)?; + + let calc = RewardsCalculator::mining_rewards::(user.clone(), liquidity_asset_id)?; + let (rewards_info, total_available_rewards) = + calc.claim_rewards().map_err(|err| Into::>::into(err))?; + + ::Currency::transfer( + Self::native_token_id().into(), + &::LiquidityMiningIssuanceVault::get(), + &user, + total_available_rewards, + ExistenceRequirement::KeepAlive, + )?; + + RewardsInfo::::insert(user.clone(), liquidity_asset_id, rewards_info); + + Pallet::::deposit_event(Event::RewardsClaimed( + user, + liquidity_asset_id, + total_available_rewards, + )); + + Ok(total_available_rewards) + } + + fn activate_liquidity( + user: T::AccountId, + liquidity_asset_id: CurrencyIdOf, + amount: BalanceOf, + use_balance_from: Option, + ) -> DispatchResult { + Self::activate_liquidity_for_native_rewards_impl( + user, + liquidity_asset_id, + amount, + use_balance_from, + ) + } + + fn deactivate_liquidity( + user: T::AccountId, + liquidity_asset_id: CurrencyIdOf, + amount: BalanceOf, + ) -> DispatchResult { + Self::deactivate_liquidity_for_native_rewards_impl(user, liquidity_asset_id, amount) + } + + fn calculate_rewards_amount( + user: AccountIdOf, + liquidity_asset_id: CurrencyIdOf, + ) -> Result, DispatchError> { + Self::calculate_native_rewards_amount(user, liquidity_asset_id) + } + + fn rewards_period() -> u32 { + Self::rewards_period() + } +} + +impl LiquidityMiningApi> for Pallet { + /// Distributs liquidity mining rewards between all the activated tokens based on their weight + fn distribute_rewards(liquidity_mining_rewards: BalanceOf) { + let _ = PromotedPoolRewards::::try_mutate(|promoted_pools| -> DispatchResult { + // benchmark with max of X prom pools + let activated_pools: Vec<_> = promoted_pools + .clone() + .into_iter() + .filter_map(|(token_id, info)| { + let activated_amount = Self::total_activated_amount(token_id); + if activated_amount > BalanceOf::::zero() && info.weight > 0 { + Some((token_id, info.weight, info.rewards, activated_amount)) + } else { + None + } + }) + .collect(); + + let maybe_total_weight = activated_pools.iter().try_fold( + 0u64, + |acc, &(_token_id, weight, _rewards, _activated_amount)| { + acc.checked_add(weight.into()) + }, + ); + + for (token_id, weight, rewards, activated_amount) in activated_pools { + let liquidity_mining_issuance_for_pool = match maybe_total_weight { + Some(total_weight) if !total_weight.is_zero() => + Perbill::from_rational(weight.into(), total_weight) + .mul_floor(liquidity_mining_rewards), + _ => BalanceOf::::zero(), + }; + + let rewards_for_liquidity: U256 = + U256::from(liquidity_mining_issuance_for_pool.into()) + .checked_mul(U256::from(u128::MAX)) + .and_then(|x| x.checked_div(activated_amount.into().into())) + .and_then(|x| x.checked_add(rewards)) + .ok_or(Error::::MathError)?; + + promoted_pools + .entry(token_id.clone()) + .and_modify(|info| info.rewards = rewards_for_liquidity); + } + Ok(()) + }); + } +} diff --git a/gasp-node/pallets/proof-of-stake/src/mock.rs b/gasp-node/pallets/proof-of-stake/src/mock.rs new file mode 100644 index 000000000..00b16d375 --- /dev/null +++ b/gasp-node/pallets/proof-of-stake/src/mock.rs @@ -0,0 +1,494 @@ +// Copyright (C) 2020 Mangata team + +use super::*; +use mangata_support::traits::GetMaintenanceStatusTrait; +use mangata_types::assets::CustomMetadata; + +use crate as pos; +use core::convert::TryFrom; +use frame_support::{ + construct_runtime, derive_impl, parameter_types, + traits::{ + tokens::currency::MultiTokenCurrency, ConstU128, ConstU32, Contains, Everything, Nothing, + WithdrawReasons, + }, + PalletId, +}; +use frame_system as system; +pub use mangata_support::pools::{PoolInfo, Valuate}; +use orml_tokens::{MultiTokenCurrencyAdapter, MultiTokenCurrencyExtended}; +use orml_traits::{asset_registry::AssetMetadata, parameter_type_with_key}; +use pallet_xyk::AssetMetadataMutationTrait; +use sp_runtime::{traits::AccountIdConversion, BuildStorage, Perbill, Percent, Saturating}; +use std::{collections::hash_map::HashMap, sync::Mutex}; + +pub const NATIVE_CURRENCY_ID: u32 = 0; + +pub(crate) type AccountId = u64; +pub(crate) type Amount = i128; +pub(crate) type Balance = u128; +pub(crate) type TokenId = u32; + +type Block = frame_system::mocking::MockBlock; + +construct_runtime!( + pub enum Test { + System: frame_system, + Tokens: orml_tokens, + ProofOfStake: pos, + Vesting: pallet_vesting_mangata, + Issuance: pallet_issuance, + Xyk: pallet_xyk, + } +); + +#[derive_impl(frame_system::config_preludes::TestDefaultConfig as frame_system::DefaultConfig)] +impl frame_system::Config for Test { + type Block = Block; +} + +parameter_type_with_key! { + pub ExistentialDeposits: |currency_id: TokenId| -> Balance { + match currency_id { + _ => 0, + } + }; +} + +pub struct DustRemovalWhitelist; +impl Contains for DustRemovalWhitelist { + fn contains(a: &AccountId) -> bool { + *a == TreasuryAccount::get() + } +} + +parameter_types! { + pub TreasuryAccount: AccountId = TreasuryPalletId::get().into_account_truncating(); + pub const MaxLocks: u32 = 50; +} + +impl pallet_issuance::Config for Test { + type RuntimeEvent = RuntimeEvent; + type NativeCurrencyId = MgaTokenId; + type Tokens = orml_tokens::MultiTokenCurrencyAdapter; + type BlocksPerRound = BlocksPerRound; + type HistoryLimit = HistoryLimit; + type LiquidityMiningIssuanceVault = LiquidityMiningIssuanceVault; + type StakingIssuanceVault = StakingIssuanceVault; + type SequencersIssuanceVault = SequencerIssuanceVault; + type TotalCrowdloanAllocation = TotalCrowdloanAllocation; + type LinearIssuanceAmount = LinearIssuanceAmount; + type LinearIssuanceBlocks = LinearIssuanceBlocks; + type LiquidityMiningSplit = LiquidityMiningSplit; + type StakingSplit = StakingSplit; + type SequencersSplit = SequencerSplit; + type ImmediateTGEReleasePercent = ImmediateTGEReleasePercent; + type TGEReleasePeriod = TGEReleasePeriod; + type TGEReleaseBegin = TGEReleaseBegin; + type VestingProvider = Vesting; + type WeightInfo = (); + type LiquidityMiningApi = ProofOfStake; +} + +impl orml_tokens::Config for Test { + type RuntimeEvent = RuntimeEvent; + type Balance = Balance; + type Amount = Amount; + type CurrencyId = TokenId; + type WeightInfo = (); + type ExistentialDeposits = ExistentialDeposits; + type MaxLocks = MaxLocks; + type DustRemovalWhitelist = DustRemovalWhitelist; + type MaxReserves = (); + type ReserveIdentifier = [u8; 8]; + type CurrencyHooks = (); + type NontransferableTokens = Nothing; + type NontransferableTokensAllowList = Nothing; +} + +parameter_types! { + pub const NativeCurrencyId: u32 = NATIVE_CURRENCY_ID; + pub const TreasuryPalletId: PalletId = PalletId(*b"py/trsry"); + pub const BnbTreasurySubAccDerive: [u8; 4] = *b"bnbt"; +} + +parameter_types! { + pub const MinVestedTransfer: Balance = 0; + pub UnvestedFundsAllowedWithdrawReasons: WithdrawReasons = + WithdrawReasons::except(WithdrawReasons::TRANSFER | WithdrawReasons::RESERVE); +} + +impl pallet_vesting_mangata::Config for Test { + type RuntimeEvent = RuntimeEvent; + type Tokens = MultiTokenCurrencyAdapter; + type BlockNumberToBalance = sp_runtime::traits::ConvertInto; + type MinVestedTransfer = MinVestedTransfer; + type WeightInfo = pallet_vesting_mangata::weights::SubstrateWeight; + type UnvestedFundsAllowedWithdrawReasons = UnvestedFundsAllowedWithdrawReasons; + type BlockNumberProvider = System; + // `VestingInfo` encode length is 36bytes. 28 schedules gets encoded as 1009 bytes, which is the + // highest number of schedules that encodes less than 2^10. + const MAX_VESTING_SCHEDULES: u32 = 28; +} + +parameter_types! { + pub LiquidityMiningIssuanceVault: AccountId = LiquidityMiningIssuanceVaultId::get().into_account_truncating(); + pub const StakingIssuanceVaultId: PalletId = PalletId(*b"py/stkiv"); + pub StakingIssuanceVault: AccountId = StakingIssuanceVaultId::get().into_account_truncating(); + pub const SequencerIssuanceVaultId: PalletId = PalletId(*b"py/seqiv"); + pub SequencerIssuanceVault: AccountId = SequencerIssuanceVaultId::get().into_account_truncating(); + pub const MgaTokenId: TokenId = 0u32; + + + pub const TotalCrowdloanAllocation: Balance = 200_000_000; + pub const LinearIssuanceAmount: Balance = 4_000_000_000; + pub const LinearIssuanceBlocks: u32 = 22_222u32; + pub const LiquidityMiningSplit: Perbill = Perbill::from_parts(555555556); + pub const StakingSplit: Perbill = Perbill::from_parts(344444444); + pub const SequencerSplit: Perbill = Perbill::from_parts(100000000); + pub const ImmediateTGEReleasePercent: Percent = Percent::from_percent(20); + pub const TGEReleasePeriod: u32 = 100u32; // 2 years + pub const TGEReleaseBegin: u32 = 10u32; // Two weeks into chain start + pub const BlocksPerRound: u32 = 10u32; + pub const HistoryLimit: u32 = 10u32; +} + +parameter_types! { + pub const LiquidityMiningIssuanceVaultId: PalletId = PalletId(*b"py/lqmiv"); + pub FakeLiquidityMiningIssuanceVault: AccountId = LiquidityMiningIssuanceVaultId::get().into_account_truncating(); +} + +pub struct DummyBlacklistedPool; + +impl Contains<(TokenId, TokenId)> for DummyBlacklistedPool { + fn contains(pair: &(TokenId, TokenId)) -> bool { + pair == &(1_u32, 9_u32) || pair == &(9_u32, 1_u32) + } +} + +pub struct MockAssetRegister; + +lazy_static::lazy_static! { + static ref ASSET_REGISTER: Mutex>>> = { + let m = HashMap::new(); + Mutex::new(m) + }; +} + +#[cfg(not(feature = "runtime-benchmarks"))] +mockall::mock! { + pub ValuationApi {} + + impl Valuate for ValuationApi { + type CurrencyId = TokenId; + type Balance = Balance; + + fn find_paired_pool(base_id: TokenId, asset_id: TokenId) -> Result, DispatchError>; + + fn check_can_valuate(base_id: TokenId, pool_id: TokenId) -> Result<(), DispatchError>; + + fn check_pool_exist(pool_id: TokenId) -> Result<(), DispatchError>; + + fn get_reserve_and_lp_supply(base_id: TokenId, pool_id: TokenId) -> Option<(Balance, Balance)>; + + fn get_valuation_for_paired(base_id: TokenId, pool_id: TokenId, amount: Balance) -> Balance; + + fn find_valuation(base_id: TokenId, asset_id: TokenId, amount: Balance) -> Result; + } +} + +#[cfg(feature = "runtime-benchmarks")] +pub struct MockValuationApi; +#[cfg(feature = "runtime-benchmarks")] +impl Valuate for MockValuationApi { + type CurrencyId = TokenId; + type Balance = Balance; + + fn find_paired_pool( + base_id: TokenId, + asset_id: TokenId, + ) -> Result, DispatchError> { + unimplemented!() + } + + fn check_can_valuate(base_id: TokenId, pool_id: TokenId) -> Result<(), DispatchError> { + unimplemented!() + } + + fn check_pool_exist(pool_id: TokenId) -> Result<(), DispatchError> { + Ok(()) + } + + fn get_reserve_and_lp_supply(base_id: TokenId, pool_id: TokenId) -> Option<(Balance, Balance)> { + let volume = + <::Min3rdPartyRewardVolume as Get>::get() * 1_000_000u128; + Some((volume, volume / 2)) + } + + fn get_valuation_for_paired(base_id: TokenId, pool_id: TokenId, amount: Balance) -> Balance { + min_req_volume() + } + + fn find_valuation( + base_id: TokenId, + asset_id: TokenId, + amount: Balance, + ) -> Result { + unimplemented!() + } +} + +impl ValuateFor for MockValuationApi {} + +pub struct AssetMetadataMutation; +impl AssetMetadataMutationTrait for AssetMetadataMutation { + fn set_asset_info( + _asset: TokenId, + _name: Vec, + _symbol: Vec, + _decimals: u32, + ) -> DispatchResult { + Ok(()) + } +} + +pub struct MockMaintenanceStatusProvider; +impl GetMaintenanceStatusTrait for MockMaintenanceStatusProvider { + fn is_maintenance() -> bool { + false + } + + fn is_upgradable() -> bool { + true + } +} + +impl pallet_xyk::XykBenchmarkingConfig for Test {} + +impl pallet_xyk::Config for Test { + type RuntimeEvent = RuntimeEvent; + type MaintenanceStatusProvider = MockMaintenanceStatusProvider; + type ActivationReservesProvider = TokensActivationPassthrough; + type Currency = MultiTokenCurrencyAdapter; + type NativeCurrencyId = NativeCurrencyId; + type TreasuryPalletId = TreasuryPalletId; + type BnbTreasurySubAccDerive = BnbTreasurySubAccDerive; + type LiquidityMiningRewards = ProofOfStake; + type PoolFeePercentage = ConstU128<20>; + type TreasuryFeePercentage = ConstU128<5>; + type BuyAndBurnFeePercentage = ConstU128<5>; + type WeightInfo = (); + type DisallowedPools = (); + type DisabledTokens = Nothing; + type VestingProvider = Vesting; + type AssetMetadataMutation = AssetMetadataMutation; + type FeeLockWeight = (); +} + +#[cfg(not(feature = "runtime-benchmarks"))] +impl pos::Config for Test { + type RuntimeEvent = RuntimeEvent; + type ActivationReservesProvider = TokensActivationPassthrough; + type NativeCurrencyId = NativeCurrencyId; + type Currency = MultiTokenCurrencyAdapter; + type LiquidityMiningIssuanceVault = FakeLiquidityMiningIssuanceVault; + type RewardsDistributionPeriod = ConstU32<10>; + type RewardsSchedulesLimit = ConstU32<10>; + type Min3rdPartyRewardValutationPerSession = ConstU128<10>; + type Min3rdPartyRewardVolume = ConstU128<10>; + type WeightInfo = (); + type ValuationApi = MockValuationApi; + type SchedulesPerBlock = ConstU32<5>; + type NontransferableTokens = Nothing; +} + +#[cfg(feature = "runtime-benchmarks")] +impl pos::Config for Test { + type RuntimeEvent = RuntimeEvent; + type ActivationReservesProvider = TokensActivationPassthrough; + type NativeCurrencyId = NativeCurrencyId; + type Currency = MultiTokenCurrencyAdapter; + type LiquidityMiningIssuanceVault = FakeLiquidityMiningIssuanceVault; + type RewardsDistributionPeriod = ConstU32<10>; + type RewardsSchedulesLimit = ConstU32<10>; + type Min3rdPartyRewardValutationPerSession = ConstU128<100_000>; + type Min3rdPartyRewardVolume = ConstU128<10>; + type WeightInfo = (); + type ValuationApi = MockValuationApi; + type SchedulesPerBlock = ConstU32<5>; + type NontransferableTokens = Nothing; + type Xyk = Xyk; +} + +pub struct TokensActivationPassthrough(PhantomData); + +impl ActivationReservesProviderTrait + for TokensActivationPassthrough +where + T::Currency: MultiTokenReservableCurrency, +{ + fn get_max_instant_unreserve_amount(token_id: TokenId, account_id: &AccountId) -> Balance { + ProofOfStake::get_rewards_info(account_id, token_id).activated_amount + } + + fn can_activate( + token_id: TokenId, + account_id: &AccountId, + amount: Balance, + _use_balance_from: Option, + ) -> bool { + ::Currency::can_reserve(token_id, account_id, amount) + } + + fn activate( + token_id: TokenId, + account_id: &AccountId, + amount: Balance, + _use_balance_from: Option, + ) -> DispatchResult { + ::Currency::reserve(token_id, account_id, amount) + } + + fn deactivate(token_id: TokenId, account_id: &AccountId, amount: Balance) -> Balance { + ::Currency::unreserve(token_id, account_id, amount) + } +} + +impl Pallet +where + T::Currency: MultiTokenReservableCurrency + + MultiTokenCurrencyExtended, +{ + pub fn balance(id: TokenId, who: AccountId) -> Balance { + ::Currency::free_balance(id, &who) + } + pub fn reserved(id: TokenId, who: AccountId) -> Balance { + ::Currency::reserved_balance(id, &who) + } + pub fn total_supply(id: TokenId) -> Balance { + ::Currency::total_issuance(id) + } + pub fn transfer( + currency_id: TokenId, + source: AccountId, + dest: AccountId, + value: Balance, + ) -> DispatchResult { + ::Currency::transfer( + currency_id, + &source, + &dest, + value, + ExistenceRequirement::KeepAlive, + ) + } + pub fn create_new_token(who: &AccountId, amount: Balance) -> TokenId { + ::Currency::create(who, amount).expect("Token creation failed") + } + + pub fn mint_token(token_id: TokenId, who: &AccountId, amount: Balance) { + ::Currency::mint(token_id, who, amount).expect("Token minting failed") + } +} + +// This function basically just builds a genesis storage key/value store according to +// our desired mockup. +pub fn new_test_ext() -> sp_io::TestExternalities { + let mut ext: sp_io::TestExternalities = + system::GenesisConfig::::default().build_storage().unwrap().into(); + ext.execute_with(|| { + System::set_block_number(1); + }); + ext +} + +pub struct ExtBuilder { + ext: sp_io::TestExternalities, +} + +fn min_req_volume() -> u128 { + <::Min3rdPartyRewardValutationPerSession as sp_core::Get>::get() +} + +impl ExtBuilder { + pub fn new() -> Self { + let t = frame_system::GenesisConfig::::default() + .build_storage() + .expect("Frame system builds valid default genesis config"); + + let mut ext = sp_io::TestExternalities::new(t); + Self { ext } + } + + fn create_if_does_not_exists(&mut self, token_id: TokenId) { + self.ext.execute_with(|| { + while token_id >= Tokens::next_asset_id() { + Tokens::create(RuntimeOrigin::root(), 0, 0).unwrap(); + } + }); + } + + pub fn issue(mut self, who: AccountId, token_id: TokenId, balance: Balance) -> Self { + self.create_if_does_not_exists(token_id); + self.ext + .execute_with(|| Tokens::mint(RuntimeOrigin::root(), token_id, who, balance).unwrap()); + return self + } + + pub fn build(self) -> sp_io::TestExternalities { + self.ext + } + + #[cfg(not(feature = "runtime-benchmarks"))] + pub fn execute_with_default_mocks(mut self, f: impl FnOnce() -> R) -> R { + self.ext.execute_with(|| { + let is_liquidity_token_mock = MockValuationApi::check_pool_exist_context(); + is_liquidity_token_mock.expect().return_const(Ok(())); + let get_liquidity_asset_mock = MockValuationApi::find_paired_pool_context(); + get_liquidity_asset_mock.expect().return_const(Ok((10u32, (0, 0), (0, 0)))); + let valuate_liquidity_token_mock = MockValuationApi::get_valuation_for_paired_context(); + valuate_liquidity_token_mock.expect().return_const(11u128); + let get_pool_state_mock = MockValuationApi::get_reserve_and_lp_supply_context(); + get_pool_state_mock + .expect() + .return_const(Some((min_req_volume(), min_req_volume()))); + f() + }) + } +} + +pub(crate) fn events() -> Vec> { + System::events() + .into_iter() + .map(|r| r.event) + .filter_map(|e| if let RuntimeEvent::ProofOfStake(inner) = e { Some(inner) } else { None }) + .collect::>() +} + +/// Compares the system events with passed in events +/// Prints highlighted diff iff assert_eq fails +#[macro_export] +macro_rules! assert_eq_events { + ($events:expr) => { + match &$events { + e => similar_asserts::assert_eq!(*e, $crate::mock::events()), + } + }; +} + +/// Panics if an event is not found in the system log of events +#[macro_export] +macro_rules! assert_event_emitted { + ($event:expr) => { + match &$event { + e => { + assert!( + $crate::mock::events().iter().find(|x| *x == e).is_some(), + "Event {:?} was not found in events: \n {:?}", + e, + crate::mock::events() + ); + }, + } + }; +} diff --git a/gasp-node/pallets/proof-of-stake/src/reward_info.rs b/gasp-node/pallets/proof-of-stake/src/reward_info.rs new file mode 100644 index 000000000..91380da98 --- /dev/null +++ b/gasp-node/pallets/proof-of-stake/src/reward_info.rs @@ -0,0 +1,392 @@ +use codec::FullCodec; +use frame_support::{dispatch::DispatchResult, traits::MultiTokenCurrency}; +use sp_arithmetic::traits::{AtLeast32BitUnsigned, Zero}; + +use crate::{ + schedule_rewards_calculator::ScheduleRewardsCalculator, BalanceOf, Config, Error, Pallet, +}; +use frame_support::pallet_prelude::*; +use sp_core::U256; +use sp_std::{ + convert::{TryFrom, TryInto}, + prelude::*, +}; + +// Quocient ratio in which liquidity minting curve is rising +const Q: f64 = 1.03; +// Precision used in rewards calculation rounding +const REWARDS_PRECISION: u32 = 10000; + +fn calculate_q_pow(q: f64, pow: u32) -> u128 { + libm::floor(libm::pow(q, pow as f64) * REWARDS_PRECISION as f64) as u128 +} + +/// Stores all the information required for non iterative rewards calculation between +/// last_checkpoint and particular subsequent block ('now' in most cases) +#[derive(Encode, Decode, Clone, Default, RuntimeDebug, PartialEq, Eq, TypeInfo, MaxEncodedLen)] +pub struct RewardInfo { + // amount of activated token + pub activated_amount: Balance, + // when doing checkpoint we need to store rewards up to this point + pub rewards_not_yet_claimed: Balance, + // there is no checkpoint during for claim_rewards + pub rewards_already_claimed: Balance, + // block number of last checkpoint + pub last_checkpoint: u32, + // ration betwen rewards : liquidity (as in table) + pub pool_ratio_at_last_checkpoint: U256, + // related to the table in the doc + pub missing_at_last_checkpoint: U256, +} + +pub struct RewardsContext { + pub current_time: u32, + pub pool_ratio_current: U256, +} + +pub struct RewardsCalculator { + rewards_context: RewardsContext, + rewards_info: RewardInfo, + _curve: sp_std::marker::PhantomData, +} + +impl RewardsCalculator, Balance> +where + Balance: 'static + CurrencyBalance, +{ + pub fn mining_rewards( + user: T::AccountId, + asset_id: crate::CurrencyIdOf, + ) -> sp_std::result::Result + where + T: Config, + T::Currency: MultiTokenCurrency, + { + let current_time: u32 = Pallet::::get_current_rewards_time()?; + let pool_ratio_current = Pallet::::get_pool_rewards(asset_id)?; + let default_rewards = RewardInfo::> { + last_checkpoint: current_time, + pool_ratio_at_last_checkpoint: pool_ratio_current, + missing_at_last_checkpoint: U256::from(0u128), + ..Default::default() + }; + + let rewards_info = + crate::RewardsInfo::::try_get(user.clone(), asset_id).unwrap_or(default_rewards); + + Ok(Self { + rewards_context: RewardsContext { + current_time: Pallet::::get_current_rewards_time()?, + pool_ratio_current: Pallet::::get_pool_rewards(asset_id)?, + }, + rewards_info, + _curve: PhantomData::>, + }) + } +} + +impl RewardsCalculator, Balance> +where + Balance: 'static + CurrencyBalance, +{ + pub fn schedule_rewards( + user: T::AccountId, + asset_id: crate::CurrencyIdOf, + reward_asset_id: crate::CurrencyIdOf, + ) -> sp_std::result::Result + where + T: Config, + T::Currency: MultiTokenCurrency, + { + let current_time: u32 = Pallet::::get_current_rewards_time()?; + ensure!( + crate::RewardTokensPerPool::::try_get(asset_id, reward_asset_id).is_ok(), + crate::Error::::NotAPromotedPool + ); + let pool_ratio_current = + ScheduleRewardsCalculator::::total_rewards_for_liquidity(asset_id, reward_asset_id); + + let default_rewards = RewardInfo { + last_checkpoint: current_time, + pool_ratio_at_last_checkpoint: pool_ratio_current.into(), + missing_at_last_checkpoint: U256::from(0u128), + ..Default::default() + }; + + let rewards_info = crate::RewardsInfoForScheduleRewards::::try_get( + user.clone(), + (asset_id, reward_asset_id), + ) + .unwrap_or(default_rewards); + + Ok(Self { + rewards_context: RewardsContext { + current_time: Pallet::::get_current_rewards_time()?, + pool_ratio_current: pool_ratio_current.into(), + }, + rewards_info, + _curve: PhantomData::>, + }) + } +} + +pub trait CurveRewards { + type Balance: CurrencyBalance; + fn calculate_curve_position( + ctx: &RewardsContext, + user_info: &RewardInfo, + ) -> Option; + fn calculate_curve_rewards( + ctx: &RewardsContext, + user_info: &RewardInfo, + ) -> Option; +} + +pub struct ConstCurveRewards(RewardsContext, RewardInfo); +pub struct AsymptoticCurveRewards(RewardsContext, RewardInfo); + +impl CurveRewards for AsymptoticCurveRewards +where + Balance: 'static + CurrencyBalance, +{ + type Balance = Balance; + fn calculate_curve_position( + ctx: &RewardsContext, + user_info: &RewardInfo, + ) -> Option { + let time_passed = ctx.current_time.checked_sub(user_info.last_checkpoint)?; + let q_pow = calculate_q_pow(Q, time_passed); + Some(user_info.missing_at_last_checkpoint * U256::from(REWARDS_PRECISION) / q_pow) + } + + fn calculate_curve_rewards( + ctx: &RewardsContext, + user_info: &RewardInfo, + ) -> Option { + let pool_rewards_ratio_new = + ctx.pool_ratio_current.checked_sub(user_info.pool_ratio_at_last_checkpoint)?; + + let rewards_base: U256 = U256::from(user_info.activated_amount.clone().into()) + .checked_mul(pool_rewards_ratio_new)? + .checked_div(U256::from(u128::MAX))?; // always fit into u128 + + let time_passed = ctx.current_time.checked_sub(user_info.last_checkpoint)?; + let mut cummulative_work = U256::from(0); + let mut cummulative_work_max_possible_for_ratio = U256::from(1); + + if time_passed != 0 && user_info.activated_amount != Balance::zero() { + let liquidity_assets_amount_u256: U256 = + U256::from(user_info.activated_amount.clone().into()); + + // whole formula: missing_at_last_checkpoint*106/6 - missing_at_last_checkpoint*106*precision/6/q_pow + // q_pow is multiplied by precision, thus there needs to be *precision in numenator as well + + cummulative_work_max_possible_for_ratio = + liquidity_assets_amount_u256.checked_mul(U256::from(time_passed))?; + + // whole formula: missing_at_last_checkpoint*Q*100/(Q*100-100) - missing_at_last_checkpoint*Q*100/(Q*100-100)*REWARDS_PRECISION/q_pow + // q_pow is multiplied by precision, thus there needs to be *precision in numenator as well + let base = user_info + .missing_at_last_checkpoint + .checked_mul(U256::from(libm::floor(Q * 100_f64) as u128))? + .checked_div(U256::from(libm::floor(Q * 100_f64 - 100_f64) as u128))?; + + let q_pow = calculate_q_pow(Q, time_passed.checked_add(1)?); + + let cummulative_missing_new = base - + base * U256::from(REWARDS_PRECISION) / q_pow - + user_info.missing_at_last_checkpoint; + + cummulative_work = + cummulative_work_max_possible_for_ratio.checked_sub(cummulative_missing_new)? + } + + TryInto::::try_into( + rewards_base + .checked_mul(cummulative_work)? + .checked_div(cummulative_work_max_possible_for_ratio)?, + ) + .ok() + .and_then(|v| TryInto::try_into(v).ok()) + } +} + +impl CurveRewards for ConstCurveRewards +where + Balance: 'static + CurrencyBalance, +{ + type Balance = Balance; + fn calculate_curve_position( + _ctx: &RewardsContext, + _user_info: &RewardInfo, + ) -> Option { + Some(U256::from(0)) + } + + fn calculate_curve_rewards( + ctx: &RewardsContext, + user_info: &RewardInfo, + ) -> Option { + let pool_rewards_ratio_new = + ctx.pool_ratio_current.checked_sub(user_info.pool_ratio_at_last_checkpoint)?; + + let rewards_base: U256 = U256::from(user_info.activated_amount.clone().into()) + .checked_mul(pool_rewards_ratio_new)? + .checked_div(U256::from(u128::MAX))?; // always fit into u128 + + TryInto::::try_into(rewards_base) + .ok() + .and_then(|v| TryInto::try_into(v).ok()) + } +} + +#[derive(Debug)] +pub enum RewardsCalcError { + CheckpointMathError, + NotEnoughAssets, +} + +impl Into> for RewardsCalcError { + fn into(self) -> Error { + match self { + RewardsCalcError::CheckpointMathError => Error::::LiquidityCheckpointMathError, + RewardsCalcError::NotEnoughAssets => Error::::NotEnoughAssets, + } + } +} + +/// Balance of MultiToken currency is quite complex and cannot be reexported so lets recreate it +/// here to simplify trait bounds +pub trait CurrencyBalance: + TryFrom + + Into + + AtLeast32BitUnsigned + + FullCodec + + Copy + + Default + + sp_std::fmt::Debug + + scale_info::TypeInfo + + MaxEncodedLen +{ +} + +impl CurrencyBalance for T where + T: TryFrom + + Into + + AtLeast32BitUnsigned + + FullCodec + + Copy + + Default + + sp_std::fmt::Debug + + scale_info::TypeInfo + + MaxEncodedLen +{ +} + +impl RewardsCalculator +where + T: CurveRewards, + Balance: CurrencyBalance, +{ + pub fn activate_more( + self, + liquidity_assets_added: Balance, + ) -> sp_std::result::Result, RewardsCalcError> { + let activated_amount = self + .rewards_info + .activated_amount + .checked_add(&liquidity_assets_added) + .ok_or(RewardsCalcError::CheckpointMathError)?; + + let missing_at_last_checkpoint = + T::calculate_curve_position(&self.rewards_context, &self.rewards_info) + .and_then(|v| v.checked_add(U256::from(liquidity_assets_added.into()))) + .ok_or(RewardsCalcError::CheckpointMathError)?; + + let user_current_rewards = Into::::into(self.calculate_rewards_impl()?); + + let rewards_not_yet_claimed = user_current_rewards + .checked_add(self.rewards_info.rewards_not_yet_claimed.into()) + .and_then(|v| v.checked_sub(self.rewards_info.rewards_already_claimed.into())) + .ok_or(RewardsCalcError::CheckpointMathError)? + .try_into() + .or(Err(RewardsCalcError::CheckpointMathError))?; + + Ok(RewardInfo { + activated_amount, + pool_ratio_at_last_checkpoint: self.rewards_context.pool_ratio_current, + rewards_already_claimed: Balance::zero(), + missing_at_last_checkpoint, + rewards_not_yet_claimed, + last_checkpoint: self.rewards_context.current_time, + }) + } + + pub fn activate_less( + self, + liquidity_assets_removed: Balance, + ) -> sp_std::result::Result, RewardsCalcError> { + let activated_amount = self + .rewards_info + .activated_amount + .checked_sub(&liquidity_assets_removed) + .ok_or(RewardsCalcError::NotEnoughAssets)?; + + let missing_at_checkpoint_new = + T::calculate_curve_position(&self.rewards_context, &self.rewards_info) + .ok_or(RewardsCalcError::CheckpointMathError)?; + + let activated_amount_new = self + .rewards_info + .activated_amount + .checked_sub(&liquidity_assets_removed) + .ok_or(RewardsCalcError::CheckpointMathError)?; + + let missing_at_checkpoint_after_burn = U256::from(activated_amount_new.clone().into()) + .checked_mul(missing_at_checkpoint_new) + .and_then(|v| v.checked_div(self.rewards_info.activated_amount.clone().into().into())) + .ok_or(RewardsCalcError::CheckpointMathError)?; + + let user_current_rewards = self.calculate_rewards_impl()?; + + let total_available_rewards = user_current_rewards + .checked_add(&self.rewards_info.rewards_not_yet_claimed) + .and_then(|v| v.checked_sub(&self.rewards_info.rewards_already_claimed)) + .ok_or(RewardsCalcError::CheckpointMathError)?; + + Ok(RewardInfo { + activated_amount, + pool_ratio_at_last_checkpoint: self.rewards_context.pool_ratio_current, + rewards_already_claimed: Balance::zero(), + missing_at_last_checkpoint: missing_at_checkpoint_after_burn, + rewards_not_yet_claimed: total_available_rewards, + last_checkpoint: self.rewards_context.current_time, + }) + } + + pub fn claim_rewards( + self, + ) -> sp_std::result::Result<(RewardInfo, Balance), RewardsCalcError> { + let current_rewards = self.calculate_rewards_impl()?; + + let total_available_rewards = current_rewards + .checked_add(&self.rewards_info.rewards_not_yet_claimed) + .and_then(|v| v.checked_sub(&self.rewards_info.rewards_already_claimed)) + .ok_or(RewardsCalcError::CheckpointMathError)?; + + let mut info = self.rewards_info.clone(); + + info.rewards_not_yet_claimed = Balance::zero(); + info.rewards_already_claimed = current_rewards; + Ok((info, total_available_rewards)) + } + + pub fn calculate_rewards(self) -> sp_std::result::Result { + self.calculate_rewards_impl() + } + + fn calculate_rewards_impl(&self) -> sp_std::result::Result { + T::calculate_curve_rewards(&self.rewards_context, &self.rewards_info) + .ok_or(RewardsCalcError::CheckpointMathError) + } +} diff --git a/gasp-node/pallets/proof-of-stake/src/schedule_rewards_calculator.rs b/gasp-node/pallets/proof-of-stake/src/schedule_rewards_calculator.rs new file mode 100644 index 000000000..e46982902 --- /dev/null +++ b/gasp-node/pallets/proof-of-stake/src/schedule_rewards_calculator.rs @@ -0,0 +1,201 @@ +use crate::{ + BalanceOf, Config, CurrencyIdOf, Pallet, ScheduleRewardsPerLiquidity, ScheduleRewardsTotal, + SessionId, TotalActivatedLiquidityForSchedules, +}; +use core::marker::PhantomData; +use frame_support::pallet_prelude::*; +use sp_arithmetic::traits::{AtLeast32BitUnsigned, Zero}; +use sp_core::U256; + +#[derive(Encode, Decode, Clone, Default, RuntimeDebug, PartialEq, Eq, TypeInfo)] +pub struct ActivatedLiquidityPerSchedule { + pending_positive: Balance, + pending_negative: Balance, + pending_session_id: SessionId, + total: Balance, +} + +#[derive(Encode, Decode, Clone, RuntimeDebug, PartialEq, Eq, TypeInfo)] +pub enum LiquidityModification { + Increase, + Decrease, +} + +impl ActivatedLiquidityPerSchedule { + fn total(&self, now: SessionId) -> Balance { + if now <= self.pending_session_id { + self.total.clone() + } else { + self.total.clone() + self.pending_positive.clone() - self.pending_negative.clone() + } + } + + fn update(&mut self, now: SessionId, amount: Balance, kind: LiquidityModification) { + if now <= self.pending_session_id { + if kind == LiquidityModification::Increase { + self.pending_positive += amount; + } else { + self.pending_negative += amount; + } + } else { + self.total = + self.total.clone() + self.pending_positive.clone() - self.pending_negative.clone(); + if kind == LiquidityModification::Increase { + self.pending_positive = amount; + self.pending_negative = Balance::zero(); + } else { + self.pending_positive = Balance::zero(); + self.pending_negative = amount; + }; + self.pending_session_id = now; + } + } +} + +/// Information about single token rewards. Automatically accumulates new rewards into `pending` +/// and once `pending_session_id < current_session` they are moved to `total` and become ready for +/// distribution to end users +#[derive(Encode, Decode, Clone, Default, RuntimeDebug, PartialEq, Eq, TypeInfo)] +pub struct ScheduleRewards { + // Accumulated rewards in current or past session. Once `now > pending_session_id` they + // should be moved to total + pending: Balance, + + // id of the session when pending_rewards were recently updated + pending_session_id: SessionId, + + // total amount of rewards ready for distribution + total: Balance, +} + +impl ScheduleRewards { + pub fn provide_rewards(&mut self, now: SessionId, amount: Balance) { + if now > self.pending_session_id { + self.total += self.pending.clone(); + self.pending = amount; + self.pending_session_id = now; + } else { + self.pending += amount; + } + } + + pub fn total_rewards(&self, now: SessionId) -> Balance { + if now > self.pending_session_id { + self.total.clone() + self.pending.clone() + } else { + self.total.clone() + } + } + + pub fn clear(&mut self, now: SessionId) { + if now > self.pending_session_id { + self.total = Balance::zero(); + self.pending = Balance::zero(); + self.pending_session_id = now; + } else { + self.total = Balance::zero(); + self.pending_session_id = now; + } + } +} + +pub struct ScheduleRewardsCalculator { + data: PhantomData, +} + +/// Class responsible for maintaining and periodically updating cumulative +/// calculations required for 3rdparty rewards +impl ScheduleRewardsCalculator { + /// updates cumulative number of rewards per 1 liquidity (mulipliedd by u128::MAX) because of + /// precision. + pub fn update_cumulative_rewards( + liquidity_asset_id: CurrencyIdOf, + liquidity_assets_reward: CurrencyIdOf, + ) { + let session_id = Pallet::::session_index(); + + let (cumulative, idx) = + ScheduleRewardsPerLiquidity::::get((liquidity_asset_id, liquidity_assets_reward)); + if (idx + 1) < (Pallet::::session_index() as u64) { + let total_activated_liquidity = + Self::total_activated_liquidity(liquidity_asset_id, liquidity_assets_reward); + let total_schedule_rewards = + Self::total_schedule_rewards(liquidity_asset_id, liquidity_assets_reward); + if total_activated_liquidity > BalanceOf::::zero() { + ScheduleRewardsTotal::::mutate( + (liquidity_asset_id, liquidity_assets_reward), + |schedule| { + schedule.clear(session_id); + }, + ); + let pending = (U256::from(total_schedule_rewards.into()) * U256::from(u128::MAX)) + .checked_div(U256::from(total_activated_liquidity.into())) + .unwrap_or_default(); + ScheduleRewardsPerLiquidity::::insert( + (liquidity_asset_id, liquidity_assets_reward), + (cumulative + pending, ((Pallet::::session_index() - 1) as u64)), + ); + } + } + } + + /// returns cumulative number of rewards per 1 liquidity (mulipliedd by u128::MAX) because of + /// precision. + pub fn total_rewards_for_liquidity( + liquidity_asset_id: CurrencyIdOf, + liquidity_assets_reward: CurrencyIdOf, + ) -> U256 { + let (cumulative, idx) = + ScheduleRewardsPerLiquidity::::get((liquidity_asset_id, liquidity_assets_reward)); + if idx == (Pallet::::session_index() as u64) { + cumulative + } else { + let total_activated_liquidity = + Self::total_activated_liquidity(liquidity_asset_id, liquidity_assets_reward); + let total_schedule_rewards = + Self::total_schedule_rewards(liquidity_asset_id, liquidity_assets_reward); + let pending = (U256::from(total_schedule_rewards.into()) * U256::from(u128::MAX)) + .checked_div(U256::from(total_activated_liquidity.into())) + .unwrap_or_default(); + cumulative + pending + } + } + + /// returns amount of schedule rewards that has been accumulated since last update of `ScheduleRewardsPerLiquidity` + /// its beeing tracked only for purpose of `ScheduleRewardsPerLiquidity` calculations + pub fn total_schedule_rewards( + liquidity_asset_id: CurrencyIdOf, + liquidity_assets_reward: CurrencyIdOf, + ) -> BalanceOf { + ScheduleRewardsTotal::::get((liquidity_asset_id, liquidity_assets_reward)) + .total_rewards(Pallet::::session_index()) + } + + /// returns amount of schedule rewards that has been accumulated since last update of `ScheduleRewardsPerLiquidity` + /// its beeing tracked only for purpose of `ScheduleRewardsPerLiquidity` calculations + pub fn update_total_activated_liqudity( + liquidity_asset_id: CurrencyIdOf, + liquidity_assets_reward: CurrencyIdOf, + diff: BalanceOf, + change: bool, + ) { + let session_id = Pallet::::session_index(); + let kind = + if change { LiquidityModification::Increase } else { LiquidityModification::Decrease }; + TotalActivatedLiquidityForSchedules::::mutate( + liquidity_asset_id, + liquidity_assets_reward, + |s| s.update(session_id, diff, kind), + ); + } + + /// returns info about total activated liquidity per schedule + pub fn total_activated_liquidity( + liquidity_asset_id: CurrencyIdOf, + liquidity_assets_reward: CurrencyIdOf, + ) -> BalanceOf { + let session_id = Pallet::::session_index(); + TotalActivatedLiquidityForSchedules::::get(liquidity_asset_id, liquidity_assets_reward) + .total(session_id) + } +} diff --git a/gasp-node/pallets/proof-of-stake/src/tests.rs b/gasp-node/pallets/proof-of-stake/src/tests.rs new file mode 100644 index 000000000..f66c8062f --- /dev/null +++ b/gasp-node/pallets/proof-of-stake/src/tests.rs @@ -0,0 +1,4527 @@ +// Copyright (C) 2020 Mangata team +#![allow(non_snake_case)] + +use super::*; +use crate::{mock::*, utils::*}; +use frame_support::{assert_err, assert_err_ignore_postinfo, assert_ok}; +use mockall::predicate::eq; +use serial_test::serial; + +fn mint_and_activate_tokens(who: AccountId, token_id: TokenId, amount: Balance) { + TokensOf::::mint(token_id, &who, amount).unwrap(); + ProofOfStake::activate_liquidity_for_native_rewards( + RuntimeOrigin::signed(who), + token_id, + amount, + None, + ) + .unwrap(); +} + +fn initialize_liquidity_rewards() { + System::set_block_number(1); + let acc_id: AccountId = 2; + let amount: u128 = std::u128::MAX; + PromotedPoolRewards::::get(); + TokensOf::::create(&acc_id, amount).unwrap(); + TokensOf::::create(&acc_id, amount).unwrap(); + TokensOf::::create(&acc_id, amount).unwrap(); + TokensOf::::create(&acc_id, amount).unwrap(); + TokensOf::::create(&acc_id, 10000).unwrap(); + + let check_pool_exist_mock = MockValuationApi::check_pool_exist_context(); + check_pool_exist_mock.expect().return_const(Ok(())); + + ProofOfStake::update_pool_promotion(RuntimeOrigin::root(), 4, 2u8).unwrap(); + PromotedPoolRewards::::mutate(|pools| { + pools.get_mut(&4).unwrap().rewards = U256::from(0); + }); + + ProofOfStake::activate_liquidity_for_native_rewards(RuntimeOrigin::signed(2), 4, 10000, None) + .unwrap(); +} + +fn process_all_schedules_in_current_session() { + let session = ProofOfStake::session_index(); + loop { + if ProofOfStake::session_index() > session { + panic!("couldnt process all schedules within the session"); + } + + if !ProofOfStake::is_new_session() && + ProofOfStake::list_metadata().pos == ProofOfStake::list_metadata().tail + { + break + } + roll_to_next_block::(); + } +} + +#[test] +#[serial] +fn liquidity_rewards_single_user_mint_W() { + ExtBuilder::new().execute_with_default_mocks(|| { + let max = std::u128::MAX; + System::set_block_number(1); + let acc_id: AccountId = 2; + let amount: u128 = max / 2u128; + + TokensOf::::create(&acc_id, amount).unwrap(); + TokensOf::::create(&acc_id, amount).unwrap(); + TokensOf::::create(&acc_id, amount).unwrap(); + TokensOf::::create(&acc_id, amount).unwrap(); + + TokensOf::::create(&acc_id, 10000).unwrap(); + + let liquidity_tokens_owned = TokensOf::::free_balance(4, &2); + + ProofOfStake::update_pool_promotion(RuntimeOrigin::root(), 4, 2u8).unwrap(); + ProofOfStake::activate_liquidity_for_native_rewards( + RuntimeOrigin::signed(2), + 4, + liquidity_tokens_owned, + None, + ) + .unwrap(); + + let rewards_info = ProofOfStake::get_rewards_info(2, 4); + + assert_eq!(rewards_info.activated_amount, 10000); + assert_eq!(rewards_info.rewards_not_yet_claimed, 0); + assert_eq!(rewards_info.rewards_already_claimed, 0); + assert_eq!(rewards_info.last_checkpoint, 0); + assert_eq!(rewards_info.pool_ratio_at_last_checkpoint, U256::from(0)); + assert_eq!(rewards_info.missing_at_last_checkpoint, U256::from(10000)); + + assert_eq!(ProofOfStake::calculate_rewards_amount(2, 4).unwrap(), 0); + + System::set_block_number(10); + ProofOfStake::distribute_rewards(10000 * 1); + assert_eq!(ProofOfStake::calculate_rewards_amount(2, 4).unwrap(), 291); + System::set_block_number(20); + ProofOfStake::distribute_rewards(10000 * 1); + assert_eq!(ProofOfStake::calculate_rewards_amount(2, 4).unwrap(), 873); + System::set_block_number(30); + ProofOfStake::distribute_rewards(10000 * 1); + assert_eq!(ProofOfStake::calculate_rewards_amount(2, 4).unwrap(), 1716); + System::set_block_number(40); + ProofOfStake::distribute_rewards(10000 * 1); + assert_eq!(ProofOfStake::calculate_rewards_amount(2, 4).unwrap(), 2847); + System::set_block_number(50); + ProofOfStake::distribute_rewards(10000 * 1); + assert_eq!(ProofOfStake::calculate_rewards_amount(2, 4).unwrap(), 4215); + System::set_block_number(60); + ProofOfStake::distribute_rewards(10000 * 1); + assert_eq!(ProofOfStake::calculate_rewards_amount(2, 4).unwrap(), 5844); + System::set_block_number(70); + ProofOfStake::distribute_rewards(10000 * 1); + assert_eq!(ProofOfStake::calculate_rewards_amount(2, 4).unwrap(), 7712); + System::set_block_number(80); + ProofOfStake::distribute_rewards(10000 * 1); + assert_eq!(ProofOfStake::calculate_rewards_amount(2, 4).unwrap(), 9817); + System::set_block_number(90); + ProofOfStake::distribute_rewards(10000 * 1); + assert_eq!(ProofOfStake::calculate_rewards_amount(2, 4).unwrap(), 12142); + System::set_block_number(100); + ProofOfStake::distribute_rewards(10000 * 1); + assert_eq!(ProofOfStake::calculate_rewards_amount(2, 4).unwrap(), 14704); + }); +} + +#[test] +#[serial] +fn liquidity_rewards_three_users_burn_W() { + ExtBuilder::new().execute_with_default_mocks(|| { + let max = std::u128::MAX; + System::set_block_number(1); + let acc_id: AccountId = 2; + let amount: u128 = max / 2u128; + + TokensOf::::create(&acc_id, amount).unwrap(); + TokensOf::::create(&acc_id, amount).unwrap(); + TokensOf::::create(&acc_id, amount).unwrap(); + TokensOf::::create(&acc_id, amount).unwrap(); + TokensOf::::create(&acc_id, 10000).unwrap(); + + ProofOfStake::update_pool_promotion(RuntimeOrigin::root(), 4, 1u8).unwrap(); + + TokensOf::::transfer(0, &2, &3, 1000000, ExistenceRequirement::AllowDeath).unwrap(); + TokensOf::::transfer(1, &2, &3, 1000000, ExistenceRequirement::AllowDeath).unwrap(); + TokensOf::::transfer(0, &2, &4, 1000000, ExistenceRequirement::AllowDeath).unwrap(); + TokensOf::::transfer(1, &2, &4, 1000000, ExistenceRequirement::AllowDeath).unwrap(); + + let liquidity_tokens_owned = TokensOf::::free_balance(4, &2); + ProofOfStake::activate_liquidity_for_native_rewards( + RuntimeOrigin::signed(2), + 4, + liquidity_tokens_owned, + None, + ) + .unwrap(); + + forward_to_block::(100); + + mint_and_activate_tokens(3, 4, 10000); + + forward_to_block::(200); + + mint_and_activate_tokens(4, 4, 10000); + forward_to_block::(240); + + ProofOfStake::deactivate_liquidity_for_native_rewards(RuntimeOrigin::signed(4), 4, 5000) + .unwrap(); + forward_to_block::(400); + + assert_eq!(ProofOfStake::calculate_rewards_amount(2, 4).unwrap(), 95965); + assert_eq!(ProofOfStake::calculate_rewards_amount(3, 4).unwrap(), 44142); + assert_eq!(ProofOfStake::calculate_rewards_amount(4, 4).unwrap(), 10630); + }); +} + +#[test] +#[serial] +fn liquidity_rewards_claim_W() { + ExtBuilder::new().execute_with_default_mocks(|| { + let max = std::u128::MAX; + System::set_block_number(1); + let acc_id: AccountId = 2; + let amount: u128 = max / 2u128; + + TokensOf::::create(&acc_id, amount).unwrap(); + TokensOf::::create(&acc_id, amount).unwrap(); + TokensOf::::create(&acc_id, amount).unwrap(); + TokensOf::::create(&acc_id, amount).unwrap(); + TokensOf::::transfer( + 0, + &2, + &::LiquidityMiningIssuanceVault::get(), + 10000000000, + ExistenceRequirement::AllowDeath, + ) + .unwrap(); + + TokensOf::::create(&acc_id, 10000).unwrap(); + ProofOfStake::update_pool_promotion(RuntimeOrigin::root(), 4, 1u8).unwrap(); + let liquidity_tokens_owned = TokensOf::::free_balance(4, &2); + ProofOfStake::activate_liquidity_for_native_rewards( + RuntimeOrigin::signed(2), + 4, + liquidity_tokens_owned, + None, + ) + .unwrap(); + + forward_to_block::(10); + forward_to_block::(90); + + assert_eq!(ProofOfStake::calculate_rewards_amount(2, 4).unwrap(), 12142); + ProofOfStake::claim_native_rewards(RuntimeOrigin::signed(2), 4).unwrap(); + + forward_to_block::(100); + assert_eq!(ProofOfStake::calculate_rewards_amount(2, 4).unwrap(), 2562); + }); +} + +#[test] +#[serial] +fn liquidity_rewards_promote_pool_W() { + ExtBuilder::new().execute_with_default_mocks(|| { + let max = std::u128::MAX; + System::set_block_number(1); + let acc_id: AccountId = 2; + let amount: u128 = max / 2u128; + TokensOf::::create(&acc_id, amount).unwrap(); + TokensOf::::create(&acc_id, amount).unwrap(); + TokensOf::::create(&acc_id, amount).unwrap(); + TokensOf::::create(&acc_id, amount).unwrap(); + TokensOf::::create(&acc_id, amount).unwrap(); + + ProofOfStake::update_pool_promotion(RuntimeOrigin::root(), 4, 1u8).unwrap(); + }); +} + +#[test] +#[serial] +fn liquidity_rewards_promote_pool_already_promoted_NW() { + ExtBuilder::new().execute_with_default_mocks(|| { + let max = std::u128::MAX; + System::set_block_number(1); + let acc_id: AccountId = 2; + let amount: u128 = max / 2u128; + TokensOf::::create(&acc_id, amount).unwrap(); + TokensOf::::create(&acc_id, amount).unwrap(); + TokensOf::::create(&acc_id, amount).unwrap(); + TokensOf::::create(&acc_id, amount).unwrap(); + TokensOf::::create(&acc_id, amount).unwrap(); + + ProofOfStake::update_pool_promotion(RuntimeOrigin::root(), 4, 1u8).unwrap(); + + // assert!(Test::is_enabled(4)); + assert!(ProofOfStake::is_enabled(4)); + }); +} + +#[test] +#[serial] +fn liquidity_rewards_work_after_burn_W() { + ExtBuilder::new().execute_with_default_mocks(|| { + let max = std::u128::MAX; + System::set_block_number(1); + let acc_id: AccountId = 2; + let amount: u128 = max / 2u128; + + TokensOf::::create(&acc_id, amount).unwrap(); + TokensOf::::create(&acc_id, amount).unwrap(); + TokensOf::::create(&acc_id, amount).unwrap(); + TokensOf::::create(&acc_id, amount).unwrap(); + TokensOf::::create(&acc_id, 10000).unwrap(); + + ProofOfStake::update_pool_promotion(RuntimeOrigin::root(), 4, 1u8).unwrap(); + + TokensOf::::transfer(0, &2, &3, 1000000, ExistenceRequirement::AllowDeath).unwrap(); + TokensOf::::transfer(1, &2, &3, 1000000, ExistenceRequirement::AllowDeath).unwrap(); + TokensOf::::transfer(0, &2, &4, 1000000, ExistenceRequirement::AllowDeath).unwrap(); + TokensOf::::transfer(1, &2, &4, 1000000, ExistenceRequirement::AllowDeath).unwrap(); + + let liquidity_tokens_owned = TokensOf::::free_balance(4, &2); + ProofOfStake::activate_liquidity_for_native_rewards( + RuntimeOrigin::signed(2), + 4, + liquidity_tokens_owned, + None, + ) + .unwrap(); + + forward_to_block::(100); + assert_eq!(ProofOfStake::calculate_rewards_amount(2, 4).unwrap(), 14704); + + mint_and_activate_tokens(3, 4, 10000); + forward_to_block::(200); + + mint_and_activate_tokens(4, 4, 10000); + forward_to_block::(240); + + ProofOfStake::deactivate_liquidity_for_native_rewards(RuntimeOrigin::signed(4), 4, 10000) + .unwrap(); + forward_to_block::(400); + + assert_eq!(ProofOfStake::calculate_rewards_amount(4, 4).unwrap(), 948); + + mint_and_activate_tokens(4, 4, 20000); + forward_to_block::(500); + assert_eq!(ProofOfStake::calculate_rewards_amount(4, 4).unwrap(), 8299); + }); +} + +#[test] +#[serial] +fn liquidity_rewards_deactivate_transfer_controled_W() { + ExtBuilder::new().execute_with_default_mocks(|| { + let max = std::u128::MAX; + System::set_block_number(1); + let acc_id: AccountId = 2; + let amount: u128 = max / 2u128; + + TokensOf::::create(&acc_id, amount).unwrap(); + TokensOf::::create(&acc_id, amount).unwrap(); + TokensOf::::create(&acc_id, amount).unwrap(); + TokensOf::::create(&acc_id, amount).unwrap(); + + TokensOf::::create(&acc_id, 10000).unwrap(); + ProofOfStake::update_pool_promotion(RuntimeOrigin::root(), 4, 1u8).unwrap(); + + let liquidity_tokens_owned = TokensOf::::free_balance(4, &2); + + ProofOfStake::activate_liquidity_for_native_rewards( + RuntimeOrigin::signed(2), + 4, + liquidity_tokens_owned, + None, + ) + .unwrap(); + assert_err!( + TokensOf::::transfer(4, &2, &3, 10, ExistenceRequirement::AllowDeath), + orml_tokens::Error::::BalanceTooLow, + ); + + forward_to_block::(100); + assert_eq!(ProofOfStake::calculate_rewards_amount(2, 4).unwrap(), 14704); + + ProofOfStake::deactivate_liquidity_for_native_rewards( + RuntimeOrigin::signed(2), + 4, + liquidity_tokens_owned, + ) + .unwrap(); + TokensOf::::transfer(4, &2, &3, 10, ExistenceRequirement::AllowDeath).unwrap(); + assert_eq!(ProofOfStake::calculate_rewards_amount(2, 4).unwrap(), 14704); + }); +} + +#[test] +#[serial] +fn liquidity_rewards_deactivate_more_NW() { + ExtBuilder::new().execute_with_default_mocks(|| { + let max = std::u128::MAX; + System::set_block_number(1); + let acc_id: AccountId = 2; + let amount: u128 = max / 2u128; + + TokensOf::::create(&acc_id, amount).unwrap(); + TokensOf::::create(&acc_id, amount).unwrap(); + TokensOf::::create(&acc_id, amount).unwrap(); + TokensOf::::create(&acc_id, amount).unwrap(); + + TokensOf::::create(&acc_id, 10000).unwrap(); + ProofOfStake::update_pool_promotion(RuntimeOrigin::root(), 4, 1u8).unwrap(); + + let liquidity_tokens_owned = TokensOf::::free_balance(4, &2); + ProofOfStake::activate_liquidity_for_native_rewards( + RuntimeOrigin::signed(2), + 4, + liquidity_tokens_owned, + None, + ) + .unwrap(); + assert_err!( + ProofOfStake::deactivate_liquidity_for_native_rewards( + RuntimeOrigin::signed(2), + 4, + liquidity_tokens_owned + 1 + ), + Error::::NotEnoughAssets + ); + }); +} + +#[test] +#[serial] +fn liquidity_rewards_activate_more_NW() { + ExtBuilder::new().execute_with_default_mocks(|| { + let max = std::u128::MAX; + System::set_block_number(1); + let acc_id: AccountId = 2; + let amount: u128 = max / 2u128; + + TokensOf::::create(&acc_id, amount).unwrap(); + TokensOf::::create(&acc_id, amount).unwrap(); + TokensOf::::create(&acc_id, amount).unwrap(); + TokensOf::::create(&acc_id, amount).unwrap(); + + TokensOf::::create(&acc_id, 10000).unwrap(); + ProofOfStake::update_pool_promotion(RuntimeOrigin::root(), 4, 1u8).unwrap(); + + let liquidity_tokens_owned = TokensOf::::free_balance(4, &2); + assert_err!( + ProofOfStake::activate_liquidity_for_native_rewards( + RuntimeOrigin::signed(2), + 4, + liquidity_tokens_owned + 1, + None + ), + Error::::NotEnoughAssets + ); + }); +} + +#[test] +#[serial] +fn liquidity_rewards_calculate_rewards_pool_not_promoted() { + ExtBuilder::new().execute_with_default_mocks(|| { + let max = std::u128::MAX; + System::set_block_number(1); + let acc_id: AccountId = 2; + let amount: u128 = max / 2u128; + + TokensOf::::create(&acc_id, amount).unwrap(); + TokensOf::::create(&acc_id, amount).unwrap(); + TokensOf::::create(&acc_id, amount).unwrap(); + TokensOf::::create(&acc_id, amount).unwrap(); + + TokensOf::::create(&acc_id, 10000).unwrap(); + assert_err!(ProofOfStake::calculate_rewards_amount(2, 4), Error::::NotAPromotedPool); + }); +} + +#[test] +#[serial] +fn liquidity_rewards_claim_pool_not_promoted() { + ExtBuilder::new().execute_with_default_mocks(|| { + let max = std::u128::MAX; + System::set_block_number(1); + let acc_id: AccountId = 2; + let amount: u128 = max / 2u128; + + TokensOf::::create(&acc_id, amount).unwrap(); + TokensOf::::create(&acc_id, amount).unwrap(); + TokensOf::::create(&acc_id, amount).unwrap(); + TokensOf::::create(&acc_id, amount).unwrap(); + + assert_err!( + ProofOfStake::claim_native_rewards(RuntimeOrigin::signed(2), 7), + Error::::NotAPromotedPool, + ); + }); +} + +#[test] +#[serial] +fn liquidity_rewards_transfer_not_working() { + ExtBuilder::new().execute_with_default_mocks(|| { + initialize_liquidity_rewards(); + + assert_err!( + TokensOf::::transfer(4, &2, &3, 10, ExistenceRequirement::AllowDeath), + orml_tokens::Error::::BalanceTooLow, + ); + }); +} + +#[test] +#[serial] +fn liquidity_rewards_not_yet_claimed_already_claimed_W() { + ExtBuilder::new().execute_with_default_mocks(|| { + let max = std::u128::MAX; + System::set_block_number(1); + let acc_id: AccountId = 2; + let amount: u128 = max / 2u128; + TokensOf::::create(&acc_id, amount).unwrap(); + TokensOf::::create(&acc_id, amount).unwrap(); + TokensOf::::create(&acc_id, amount).unwrap(); + TokensOf::::create(&acc_id, amount).unwrap(); + + TokensOf::::transfer( + 0, + &2, + &::LiquidityMiningIssuanceVault::get(), + 10000000000, + ExistenceRequirement::AllowDeath, + ) + .unwrap(); + + TokensOf::::create(&2, 10000).unwrap(); + ProofOfStake::update_pool_promotion(RuntimeOrigin::root(), 4, 1u8).unwrap(); + + let liquidity_tokens_owned = TokensOf::::free_balance(4, &2); + ProofOfStake::activate_liquidity_for_native_rewards( + RuntimeOrigin::signed(2), + 4, + liquidity_tokens_owned, + None, + ) + .unwrap(); + + forward_to_block::(10); + + assert_eq!(ProofOfStake::calculate_rewards_amount(2, 4).unwrap(), 291); + ProofOfStake::deactivate_liquidity_for_native_rewards( + RuntimeOrigin::signed(2), + 4, + liquidity_tokens_owned, + ) + .unwrap(); + + let rewards_info = ProofOfStake::get_rewards_info(2, 4); + assert_eq!(rewards_info.rewards_not_yet_claimed, 291); + + ProofOfStake::activate_liquidity_for_native_rewards( + RuntimeOrigin::signed(2), + 4, + liquidity_tokens_owned, + None, + ) + .unwrap(); + + forward_to_block::(100); + + assert_eq!(ProofOfStake::calculate_rewards_amount(2, 4).unwrap(), 12433); + ProofOfStake::claim_native_rewards(RuntimeOrigin::signed(2), 4).unwrap(); + + let rewards_info = ProofOfStake::get_rewards_info(2, 4); + assert_eq!(rewards_info.rewards_already_claimed, 12142); + }); +} + +#[test] +#[serial] +fn extreme_case_pool_ratio() { + ExtBuilder::new().execute_with_default_mocks(|| { + let max = std::u128::MAX; + System::set_block_number(1); + let acc_id: AccountId = 2; + let amount: u128 = max / 2u128; + + TokensOf::::create(&acc_id, amount).unwrap(); + TokensOf::::create(&acc_id, amount).unwrap(); + TokensOf::::create(&acc_id, amount).unwrap(); + TokensOf::::create(&acc_id, amount).unwrap(); + + TokensOf::::create(&acc_id, max).unwrap(); + ProofOfStake::update_pool_promotion(RuntimeOrigin::root(), 4, 1u8).unwrap(); + + ProofOfStake::activate_liquidity_for_native_rewards(RuntimeOrigin::signed(2), 4, 1, None) + .unwrap(); + + PromotedPoolRewards::::mutate(|pools| { + pools.get_mut(&4).unwrap().rewards = U256::from(u128::MAX) * U256::from(u128::MAX); + }); + + System::set_block_number(10000); + assert_eq!( + ProofOfStake::calculate_rewards_amount(2, 4).unwrap(), + 329053048812547494169083245386519860476 + ); + }); +} + +#[test] +#[serial] +fn rewards_rounding_during_often_mint() { + ExtBuilder::new().execute_with_default_mocks(|| { + let max = std::u128::MAX; + System::set_block_number(1); + let acc_id: AccountId = 2; + let amount: u128 = max / 2u128; + + TokensOf::::create(&acc_id, amount).unwrap(); + TokensOf::::create(&acc_id, amount).unwrap(); + TokensOf::::create(&acc_id, amount).unwrap(); + TokensOf::::create(&acc_id, amount).unwrap(); + + TokensOf::::transfer( + 0, + &2, + &::LiquidityMiningIssuanceVault::get(), + 10000000000, + ExistenceRequirement::AllowDeath, + ) + .unwrap(); + + let calculate_liq_tokens_amount = |first_amount: u128, second_amount: u128| -> u128 { + ((first_amount / 2) + (second_amount / 2)).try_into().unwrap() + }; + TokensOf::::create( + &acc_id, + calculate_liq_tokens_amount(250000000000000000000000000, 10000000000000000), + ) + .unwrap(); + ProofOfStake::update_pool_promotion(RuntimeOrigin::root(), 4, 1u8).unwrap(); + TokensOf::::transfer( + 0, + &2, + &3, + 10000000000000000000000000, + ExistenceRequirement::AllowDeath, + ) + .unwrap(); + TokensOf::::transfer( + 1, + &2, + &3, + 10000000000000000000000000, + ExistenceRequirement::AllowDeath, + ) + .unwrap(); + mint_and_activate_tokens( + 2, + 4, + calculate_liq_tokens_amount(25000000000000000000000, 2000000000000), + ); + mint_and_activate_tokens( + 3, + 4, + calculate_liq_tokens_amount(25000000000000000000000, 2000000000000), + ); + + let mut non_minter_higher_rewards_counter = 0; + let mut higher_rewards_cumulative = 0; + for n in 1..10000 { + System::set_block_number(n); + if ProofOfStake::is_new_session() { + ProofOfStake::distribute_rewards(10000); + + mint_and_activate_tokens( + 3, + 4, + calculate_liq_tokens_amount(34000000000000000000, 68000000000000000000), + ); + log::info!("----------------------------"); + let rew_non_minter = ProofOfStake::calculate_rewards_amount(2, 4).unwrap(); + let rew_minter = ProofOfStake::calculate_rewards_amount(3, 4).unwrap(); + log::info!("rew {} {}", n, rew_non_minter); + log::info!("rew minter {} {}", n, rew_minter); + + if rew_non_minter > rew_minter { + non_minter_higher_rewards_counter += 1; + higher_rewards_cumulative += rew_minter * 10000 / rew_non_minter; + } + } + } + log::info!( + "times minting user had lower rewards {} avg minter/nonminter * 10000 {}", + non_minter_higher_rewards_counter, + higher_rewards_cumulative / non_minter_higher_rewards_counter + ); + }); +} + +#[test] +#[serial] +fn rewards_storage_right_amounts_start1() { + ExtBuilder::new().execute_with_default_mocks(|| { + let max = std::u128::MAX; + System::set_block_number(1); + let acc_id: AccountId = 2; + let amount: u128 = max / 2u128; + + TokensOf::::create(&acc_id, amount).unwrap(); + TokensOf::::create(&acc_id, amount).unwrap(); + TokensOf::::create(&acc_id, amount).unwrap(); + TokensOf::::create(&acc_id, amount).unwrap(); + + TokensOf::::transfer( + 0, + &2, + &::LiquidityMiningIssuanceVault::get(), + 10000000000, + ExistenceRequirement::AllowDeath, + ) + .unwrap(); + + TokensOf::::create(&acc_id, 10000).unwrap(); + ProofOfStake::update_pool_promotion(RuntimeOrigin::root(), 4, 1u8).unwrap(); + + TokensOf::::transfer(1, &2, &3, 20010, ExistenceRequirement::AllowDeath).unwrap(); + TokensOf::::transfer(2, &2, &3, 20010, ExistenceRequirement::AllowDeath).unwrap(); + TokensOf::::transfer(1, &2, &4, 20010, ExistenceRequirement::AllowDeath).unwrap(); + TokensOf::::transfer(2, &2, &4, 20010, ExistenceRequirement::AllowDeath).unwrap(); + TokensOf::::transfer(1, &2, &5, 20010, ExistenceRequirement::AllowDeath).unwrap(); + TokensOf::::transfer(2, &2, &5, 20010, ExistenceRequirement::AllowDeath).unwrap(); + TokensOf::::transfer(1, &2, &6, 20010, ExistenceRequirement::AllowDeath).unwrap(); + TokensOf::::transfer(2, &2, &6, 20010, ExistenceRequirement::AllowDeath).unwrap(); + ProofOfStake::activate_liquidity_for_native_rewards( + RuntimeOrigin::signed(2), + 4, + 10000, + None, + ) + .unwrap(); + mint_and_activate_tokens(3, 4, 10000); + mint_and_activate_tokens(4, 4, 10000); + mint_and_activate_tokens(5, 4, 10000); + mint_and_activate_tokens(6, 4, 10000); + + forward_to_block_with_custom_rewards::(100, 50000); // No clue why we considr 50k rewards per + assert_eq!( + U256::from(u128::MAX) * U256::from(10), + PromotedPoolRewards::::get().get(&4).unwrap().rewards + ); + + ProofOfStake::claim_native_rewards(RuntimeOrigin::signed(2), 4).unwrap(); + ProofOfStake::claim_native_rewards(RuntimeOrigin::signed(3), 4).unwrap(); + ProofOfStake::claim_native_rewards(RuntimeOrigin::signed(4), 4).unwrap(); + ProofOfStake::claim_native_rewards(RuntimeOrigin::signed(5), 4).unwrap(); + ProofOfStake::claim_native_rewards(RuntimeOrigin::signed(6), 4).unwrap(); + + let mut rewards_info = ProofOfStake::get_rewards_info(2, 4); + assert_eq!(rewards_info.rewards_not_yet_claimed, 0); + assert_eq!(rewards_info.rewards_already_claimed, 14704); + rewards_info = ProofOfStake::get_rewards_info(3, 4); + assert_eq!(rewards_info.rewards_not_yet_claimed, 0); + assert_eq!(rewards_info.rewards_already_claimed, 14704); + rewards_info = ProofOfStake::get_rewards_info(4, 4); + assert_eq!(rewards_info.rewards_not_yet_claimed, 0); + assert_eq!(rewards_info.rewards_already_claimed, 14704); + rewards_info = ProofOfStake::get_rewards_info(5, 4); + assert_eq!(rewards_info.rewards_not_yet_claimed, 0); + assert_eq!(rewards_info.rewards_already_claimed, 14704); + rewards_info = ProofOfStake::get_rewards_info(6, 4); + assert_eq!(rewards_info.rewards_not_yet_claimed, 0); + assert_eq!(rewards_info.rewards_already_claimed, 14704); + + forward_to_block_with_custom_rewards::(200, 50000); + + assert_eq!(ProofOfStake::calculate_rewards_amount(2, 4).unwrap(), 36530); + assert_eq!(ProofOfStake::calculate_rewards_amount(3, 4).unwrap(), 36530); + assert_eq!(ProofOfStake::calculate_rewards_amount(4, 4).unwrap(), 36530); + assert_eq!(ProofOfStake::calculate_rewards_amount(5, 4).unwrap(), 36530); + assert_eq!(ProofOfStake::calculate_rewards_amount(6, 4).unwrap(), 36530); + + // starting point for blue cases + + // usecase 3 claim (all) + let mut user_balance_before = TokensOf::::free_balance(0, &2); + ProofOfStake::claim_native_rewards(RuntimeOrigin::signed(2), 4).unwrap(); + let mut user_balance_after = TokensOf::::free_balance(0, &2); + rewards_info = ProofOfStake::get_rewards_info(2, 4); + + assert_eq!(rewards_info.rewards_not_yet_claimed, 0); + assert_eq!(rewards_info.rewards_already_claimed, 51234); + assert_eq!(ProofOfStake::calculate_rewards_amount(2, 4).unwrap(), 0); + assert_eq!(user_balance_after - user_balance_before, 36530); + + // usecase 6 burn some + user_balance_before = TokensOf::::free_balance(0, &3); + ProofOfStake::deactivate_liquidity_for_native_rewards(RuntimeOrigin::signed(3), 4, 5000) + .unwrap(); + + user_balance_after = TokensOf::::free_balance(0, &3); + rewards_info = ProofOfStake::get_rewards_info(3, 4); + + assert_eq!(rewards_info.rewards_not_yet_claimed, 36530); // total rewards 51234, while 14704 were already claimed. Burning puts all rewards to not_yet_claimed, but zeroes the already_claimed. 51234 - 14704 = 36530 + assert_eq!(rewards_info.rewards_already_claimed, 0); + assert_eq!(ProofOfStake::calculate_rewards_amount(3, 4).unwrap(), 36530); + assert_eq!(user_balance_after - user_balance_before, 0); + + // usecase 7 mint some + user_balance_before = TokensOf::::free_balance(0, &4); + mint_and_activate_tokens(4, 4, 5000); + user_balance_after = TokensOf::::free_balance(0, &4); + rewards_info = ProofOfStake::get_rewards_info(4, 4); + + assert_eq!(rewards_info.rewards_not_yet_claimed, 36530); + assert_eq!(rewards_info.rewards_already_claimed, 0); + assert_eq!(ProofOfStake::calculate_rewards_amount(4, 4).unwrap(), 36530); + assert_eq!(user_balance_after - user_balance_before, 0); + + // usecase 8 deactivate some + user_balance_before = TokensOf::::free_balance(0, &5); + ProofOfStake::deactivate_liquidity_for_native_rewards(RuntimeOrigin::signed(5), 4, 5000) + .unwrap(); + user_balance_after = TokensOf::::free_balance(0, &5); + rewards_info = ProofOfStake::get_rewards_info(5, 4); + + assert_eq!(rewards_info.rewards_not_yet_claimed, 36530); + assert_eq!(rewards_info.rewards_already_claimed, 0); + assert_eq!(ProofOfStake::calculate_rewards_amount(5, 4).unwrap(), 36530); + assert_eq!(user_balance_after - user_balance_before, 0); + + // usecase 16 claim some + user_balance_before = TokensOf::::free_balance(0, &6); + ProofOfStake::claim_native_rewards(RuntimeOrigin::signed(6), 4).unwrap(); + user_balance_after = TokensOf::::free_balance(0, &6); + rewards_info = ProofOfStake::get_rewards_info(6, 4); + + assert_eq!(rewards_info.rewards_not_yet_claimed, 0); + assert_eq!(rewards_info.rewards_already_claimed, 14704 + 36530); + assert_eq!(ProofOfStake::calculate_rewards_amount(6, 4).unwrap(), 0); + assert_eq!(user_balance_after - user_balance_before, 36530); + }); +} + +// starting point, user burned some rewards, then new rewards were generated (yellow) +#[test] +#[serial] +fn rewards_storage_right_amounts_start2() { + ExtBuilder::new().execute_with_default_mocks(|| { + let max = std::u128::MAX; + System::set_block_number(1); + let acc_id: AccountId = 2; + let amount: u128 = max / 2u128; + + TokensOf::::create(&acc_id, amount).unwrap(); + TokensOf::::create(&acc_id, amount).unwrap(); + TokensOf::::create(&acc_id, amount).unwrap(); + TokensOf::::create(&acc_id, amount).unwrap(); + + TokensOf::::transfer( + 0, + &2, + &::LiquidityMiningIssuanceVault::get(), + 10000000000, + ExistenceRequirement::AllowDeath, + ) + .unwrap(); + + // XykStorage::create_pool(RuntimeOrigin::signed(2), 1, 10000, 2, 10000).unwrap(); + TokensOf::::create(&acc_id, 10000).unwrap(); + ProofOfStake::update_pool_promotion(RuntimeOrigin::root(), 4, 1u8).unwrap(); + + TokensOf::::transfer(1, &2, &3, 20010, ExistenceRequirement::AllowDeath).unwrap(); + TokensOf::::transfer(2, &2, &3, 20010, ExistenceRequirement::AllowDeath).unwrap(); + TokensOf::::transfer(1, &2, &4, 20010, ExistenceRequirement::AllowDeath).unwrap(); + TokensOf::::transfer(2, &2, &4, 20010, ExistenceRequirement::AllowDeath).unwrap(); + TokensOf::::transfer(1, &2, &5, 20010, ExistenceRequirement::AllowDeath).unwrap(); + TokensOf::::transfer(2, &2, &5, 20010, ExistenceRequirement::AllowDeath).unwrap(); + TokensOf::::transfer(1, &2, &6, 20010, ExistenceRequirement::AllowDeath).unwrap(); + TokensOf::::transfer(2, &2, &6, 20010, ExistenceRequirement::AllowDeath).unwrap(); + ProofOfStake::activate_liquidity_for_native_rewards( + RuntimeOrigin::signed(2), + 4, + 10000, + None, + ) + .unwrap(); + mint_and_activate_tokens(3, 4, 10000); + mint_and_activate_tokens(4, 4, 10000); + mint_and_activate_tokens(5, 4, 10000); + + forward_to_block_with_custom_rewards::(100, 40000); + assert_eq!( + U256::from(u128::MAX) * U256::from(10), + PromotedPoolRewards::::get().get(&4).unwrap().rewards + ); + + ProofOfStake::deactivate_liquidity_for_native_rewards(RuntimeOrigin::signed(2), 4, 5000) + .unwrap(); + ProofOfStake::deactivate_liquidity_for_native_rewards(RuntimeOrigin::signed(3), 4, 5000) + .unwrap(); + ProofOfStake::deactivate_liquidity_for_native_rewards(RuntimeOrigin::signed(4), 4, 5000) + .unwrap(); + ProofOfStake::deactivate_liquidity_for_native_rewards(RuntimeOrigin::signed(5), 4, 5000) + .unwrap(); + + forward_to_block_with_custom_rewards::(200, 20000); //its really weird that rewards are + //decreased from 40k to 20k in single + assert_eq!( + U256::from(u128::MAX) * U256::from(20), + PromotedPoolRewards::::get().get(&4).unwrap().rewards + ); + + let mut rewards_info = ProofOfStake::get_rewards_info(2, 4); + assert_eq!(rewards_info.rewards_not_yet_claimed, 14704); + assert_eq!(rewards_info.rewards_already_claimed, 0); + rewards_info = ProofOfStake::get_rewards_info(3, 4); + assert_eq!(rewards_info.rewards_not_yet_claimed, 14704); + assert_eq!(rewards_info.rewards_already_claimed, 0); + rewards_info = ProofOfStake::get_rewards_info(4, 4); + assert_eq!(rewards_info.rewards_not_yet_claimed, 14704); + assert_eq!(rewards_info.rewards_already_claimed, 0); + rewards_info = ProofOfStake::get_rewards_info(5, 4); + assert_eq!(rewards_info.rewards_not_yet_claimed, 14704); + assert_eq!(rewards_info.rewards_already_claimed, 0); + + assert_eq!(ProofOfStake::calculate_rewards_amount(2, 4).unwrap(), 32973); + assert_eq!(ProofOfStake::calculate_rewards_amount(3, 4).unwrap(), 32973); + assert_eq!(ProofOfStake::calculate_rewards_amount(4, 4).unwrap(), 32973); + assert_eq!(ProofOfStake::calculate_rewards_amount(5, 4).unwrap(), 32973); + + // starting point for blue cases + + // usecase 2 claim_all + let mut user_balance_before = TokensOf::::free_balance(0, &2); + ProofOfStake::claim_native_rewards(RuntimeOrigin::signed(2), 4).unwrap(); + let mut user_balance_after = TokensOf::::free_balance(0, &2); + rewards_info = ProofOfStake::get_rewards_info(2, 4); + + assert_eq!(rewards_info.rewards_not_yet_claimed, 0); + assert_eq!(rewards_info.rewards_already_claimed, 18269); + assert_eq!(ProofOfStake::calculate_rewards_amount(2, 4).unwrap(), 0); + assert_eq!(user_balance_after - user_balance_before, 32973); + + // usecase 9 burn some + user_balance_before = TokensOf::::free_balance(0, &3); + ProofOfStake::deactivate_liquidity_for_native_rewards(RuntimeOrigin::signed(3), 4, 5000) + .unwrap(); + user_balance_after = TokensOf::::free_balance(0, &3); + rewards_info = ProofOfStake::get_rewards_info(3, 4); + + assert_eq!(rewards_info.rewards_not_yet_claimed, 32973); + assert_eq!(rewards_info.rewards_already_claimed, 0); + assert_eq!(ProofOfStake::calculate_rewards_amount(3, 4).unwrap(), 32973); + assert_eq!(user_balance_after - user_balance_before, 0); + + // usecase 10 mint some + user_balance_before = TokensOf::::free_balance(0, &4); + mint_and_activate_tokens(4, 4, 5000); + user_balance_after = TokensOf::::free_balance(0, &4); + rewards_info = ProofOfStake::get_rewards_info(4, 4); + + assert_eq!(rewards_info.rewards_not_yet_claimed, 32973); + assert_eq!(rewards_info.rewards_already_claimed, 0); + assert_eq!(ProofOfStake::calculate_rewards_amount(4, 4).unwrap(), 32973); + assert_eq!(user_balance_after - user_balance_before, 0); + + // usecase 11 deactivate some + user_balance_before = TokensOf::::free_balance(0, &5); + ProofOfStake::deactivate_liquidity_for_native_rewards(RuntimeOrigin::signed(5), 4, 5000) + .unwrap(); + user_balance_after = TokensOf::::free_balance(0, &5); + rewards_info = ProofOfStake::get_rewards_info(5, 4); + + assert_eq!(rewards_info.rewards_not_yet_claimed, 32973); + assert_eq!(rewards_info.rewards_already_claimed, 0); + assert_eq!(ProofOfStake::calculate_rewards_amount(5, 4).unwrap(), 32973); + assert_eq!(user_balance_after - user_balance_before, 0); + }); +} + +// starting point, just new rewards were generated (green) +#[test] +#[serial] +fn rewards_storage_right_amounts_start3() { + ExtBuilder::new().execute_with_default_mocks(|| { + let max = std::u128::MAX; + System::set_block_number(1); + let acc_id: AccountId = 2; + let amount: u128 = 100_000_000_000_000u128; + + TokensOf::::create(&acc_id, amount).unwrap(); + TokensOf::::create(&acc_id, amount).unwrap(); + TokensOf::::create(&acc_id, amount).unwrap(); + TokensOf::::create(&acc_id, amount).unwrap(); + + TokensOf::::transfer( + 0, + &2, + &::LiquidityMiningIssuanceVault::get(), + 10000000000, + ExistenceRequirement::AllowDeath, + ) + .unwrap(); + + TokensOf::::create(&acc_id, 10000).unwrap(); + ProofOfStake::update_pool_promotion(RuntimeOrigin::root(), 4, 1u8).unwrap(); + + TokensOf::::transfer(1, &2, &3, 20010, ExistenceRequirement::AllowDeath).unwrap(); + TokensOf::::transfer(2, &2, &3, 20010, ExistenceRequirement::AllowDeath).unwrap(); + TokensOf::::transfer(1, &2, &4, 20010, ExistenceRequirement::AllowDeath).unwrap(); + TokensOf::::transfer(2, &2, &4, 20010, ExistenceRequirement::AllowDeath).unwrap(); + + ProofOfStake::activate_liquidity_for_native_rewards( + RuntimeOrigin::signed(2), + 4, + 10000, + None, + ) + .unwrap(); + mint_and_activate_tokens(3, 4, 10000); + + forward_to_block_with_custom_rewards::(100, 20000); + + let mut rewards_info = ProofOfStake::get_rewards_info(2, 4); + assert_eq!(rewards_info.rewards_not_yet_claimed, 0); + assert_eq!(rewards_info.rewards_already_claimed, 0); + rewards_info = ProofOfStake::get_rewards_info(3, 4); + assert_eq!(rewards_info.rewards_not_yet_claimed, 0); + assert_eq!(rewards_info.rewards_already_claimed, 0); + + assert_eq!(ProofOfStake::calculate_rewards_amount(2, 4).unwrap(), 14704); + assert_eq!(ProofOfStake::calculate_rewards_amount(3, 4).unwrap(), 14704); + + // starting point for blue cases + + // usecase 1 claim (all) + let mut user_balance_before = TokensOf::::free_balance(0, &2); + ProofOfStake::claim_native_rewards(RuntimeOrigin::signed(2), 4).unwrap(); + let mut user_balance_after = TokensOf::::free_balance(0, &2); + rewards_info = ProofOfStake::get_rewards_info(2, 4); + + assert_eq!(rewards_info.rewards_not_yet_claimed, 0); + assert_eq!(rewards_info.rewards_already_claimed, 14704); + assert_eq!(ProofOfStake::calculate_rewards_amount(2, 4).unwrap(), 0); + assert_eq!(user_balance_after - user_balance_before, 14704); + + // usecase 17 claim some + user_balance_before = TokensOf::::free_balance(0, &3); + ProofOfStake::claim_native_rewards(RuntimeOrigin::signed(3), 4).unwrap(); + user_balance_after = TokensOf::::free_balance(0, &3); + rewards_info = ProofOfStake::get_rewards_info(3, 4); + + assert_eq!(rewards_info.rewards_not_yet_claimed, 0); + assert_eq!(rewards_info.rewards_already_claimed, 10000 + 4704); + assert_eq!(ProofOfStake::calculate_rewards_amount(3, 4).unwrap(), 0); + assert_eq!(user_balance_after - user_balance_before, 10000 + 4704); + }); +} + +#[test] +#[serial] +fn liquidity_rewards_transfered_liq_tokens_produce_rewards_W() { + ExtBuilder::new().execute_with_default_mocks(|| { + let max = std::u128::MAX; + System::set_block_number(1); + let acc_id: AccountId = 2; + let amount: u128 = max / 2u128; + + TokensOf::::create(&acc_id, amount).unwrap(); + TokensOf::::create(&acc_id, amount).unwrap(); + TokensOf::::create(&acc_id, amount).unwrap(); + TokensOf::::create(&acc_id, amount).unwrap(); + + TokensOf::::transfer( + 0, + &2, + &::LiquidityMiningIssuanceVault::get(), + 10000000000, + ExistenceRequirement::AllowDeath, + ) + .unwrap(); + + TokensOf::::create(&acc_id, 10000).unwrap(); + ProofOfStake::update_pool_promotion(RuntimeOrigin::root(), 4, 2u8).unwrap(); + + let liquidity_tokens_owned = TokensOf::::free_balance(4, &2); + + TokensOf::::transfer( + 4, + &2, + &3, + liquidity_tokens_owned, + ExistenceRequirement::AllowDeath, + ) + .unwrap(); + + ProofOfStake::activate_liquidity_for_native_rewards( + RuntimeOrigin::signed(3), + 4, + liquidity_tokens_owned, + None, + ) + .unwrap(); + + forward_to_block::(100); + + assert_eq!(ProofOfStake::calculate_rewards_amount(3, 4).unwrap(), 14704); + ProofOfStake::claim_native_rewards(RuntimeOrigin::signed(3), 4).unwrap(); + }); +} + +#[test] +#[serial] +fn test_migrated_from_pallet_issuance() { + ExtBuilder::new().execute_with_default_mocks(|| { + let _ = env_logger::try_init(); + System::set_block_number(1); + const LIQUIDITY_ISSUANCE: Balance = 450045; + + let token_id = TokensOf::::create(&99999, 2000_000_000).unwrap(); + assert_eq!(token_id, 0); + + let current_issuance = TokensOf::::total_issuance(token_id); + let target_tge = 2_000_000_000u128; + assert!(current_issuance <= target_tge); + + assert_ok!(TokensOf::::mint(token_id, &99999, target_tge - current_issuance)); + + assert_ok!(Issuance::finalize_tge(RuntimeOrigin::root())); + assert_ok!(Issuance::init_issuance_config(RuntimeOrigin::root())); + assert_ok!(Issuance::calculate_and_store_round_issuance(0u32)); + + assert_eq!(1, TokensOf::::create(&99999, 1_000_000u128).unwrap()); + ProofOfStake::enable(1, 1u8); + ProofOfStake::activate_liquidity_for_native_rewards( + RuntimeOrigin::signed(99999), + 1, + 1, + None, + ) + .unwrap(); + + forward_to_block_with_custom_rewards::(9, LIQUIDITY_ISSUANCE); + assert_eq!( + U256::from_dec_str("153142377820933750789374425201630124724265475").unwrap(), + ProofOfStake::get_pool_rewards(1).unwrap() + ); + forward_to_block_with_custom_rewards::(19, LIQUIDITY_ISSUANCE); + assert_eq!( + U256::from_dec_str("306284755641867501578748850403260249448530950").unwrap(), + ProofOfStake::get_pool_rewards(1).unwrap() + ); + + assert_eq!(2, TokensOf::::create(&99999, 1_000_000u128).unwrap()); + ProofOfStake::enable(2, 1u8); + ProofOfStake::activate_liquidity_for_native_rewards( + RuntimeOrigin::signed(99999), + 2, + 1, + None, + ) + .unwrap(); + forward_to_block_with_custom_rewards::(29, LIQUIDITY_ISSUANCE); + assert_eq!( + U256::from_dec_str("382855774411150916504204331316771595926557960").unwrap(), + ProofOfStake::get_pool_rewards(1).unwrap() + ); + assert_eq!( + U256::from_dec_str("76571018769283414925455480913511346478027010").unwrap(), + ProofOfStake::get_pool_rewards(2).unwrap() + ); + + forward_to_block_with_custom_rewards::(39, LIQUIDITY_ISSUANCE); + assert_eq!( + U256::from_dec_str("459426793180434331429659812230282942404584970").unwrap(), + ProofOfStake::get_pool_rewards(1).unwrap() + ); + assert_eq!( + U256::from_dec_str("153142037538566829850910961827022692956054020").unwrap(), + ProofOfStake::get_pool_rewards(2).unwrap() + ); + }); +} + +#[test] +#[serial] +fn claim_rewards_from_pool_that_has_been_disabled() { + ExtBuilder::new().execute_with_default_mocks(|| { + let max = std::u128::MAX; + System::set_block_number(1); + let acc_id: AccountId = 2; + let amount: u128 = max / 2u128; + TokensOf::::create(&acc_id, amount).unwrap(); + TokensOf::::create(&acc_id, amount).unwrap(); + TokensOf::::create(&acc_id, amount).unwrap(); + TokensOf::::create(&acc_id, amount).unwrap(); + + TokensOf::::transfer( + 0, + &2, + &::LiquidityMiningIssuanceVault::get(), + 10000000000, + ExistenceRequirement::AllowDeath, + ) + .unwrap(); + + TokensOf::::create(&2, 10000).unwrap(); + ProofOfStake::update_pool_promotion(RuntimeOrigin::root(), 4, 1u8).unwrap(); + + let liquidity_tokens_owned = TokensOf::::free_balance(4, &2); + ProofOfStake::activate_liquidity_for_native_rewards( + RuntimeOrigin::signed(2), + 4, + liquidity_tokens_owned, + None, + ) + .unwrap(); + + forward_to_block::(10); + + assert_eq!(ProofOfStake::calculate_rewards_amount(2, 4).unwrap(), 291); + + ProofOfStake::update_pool_promotion(RuntimeOrigin::root(), 4, 0u8).unwrap(); + + ProofOfStake::claim_native_rewards(RuntimeOrigin::signed(2), 4).unwrap(); + + assert_eq!(ProofOfStake::calculate_rewards_amount(2, 4).unwrap(), 0); + }); +} + +const MILLION: u128 = 1_000_000; +const REWARDED_PAIR: TokenId = 10_u32; +const FIRST_REWARDED_PAIR: (TokenId, TokenId) = (0u32, 4u32); +const SECOND_REWARDED_PAIR: (TokenId, TokenId) = (0u32, 5u32); +const REWARD_AMOUNT: u128 = 10_000u128; +const REWARD_TOKEN: u32 = 5u32; +const FIRST_REWARD_TOKEN: u32 = REWARD_TOKEN; +const SECOND_REWARD_TOKEN: u32 = 6u32; +const LIQUIDITY_TOKEN: u32 = 10; +const FIRST_LIQUIDITY_TOKEN: u32 = 10; +const SECOND_LIQUIDITY_TOKEN: u32 = 11; +const TOKEN_PAIRED_WITH_MGX: u32 = 15; +const ALICE: u64 = 2; +const BOB: u64 = 3; +const CHARLIE: u64 = 4; +const EVE: u64 = 5; + +fn min_req_volume() -> u128 { + <::Min3rdPartyRewardValutationPerSession as sp_core::Get>::get() +} + +#[test] +#[serial] +fn user_can_provide_3rdparty_rewards() { + ExtBuilder::new() + .issue(ALICE, REWARD_TOKEN, REWARD_AMOUNT) + .execute_with_default_mocks(|| { + System::set_block_number(1); + ProofOfStake::reward_pool( + RuntimeOrigin::signed(ALICE), + REWARDED_PAIR, + REWARD_TOKEN, + REWARD_AMOUNT / 2, + 10u32.into(), + ) + .unwrap(); + + roll_to_session::(5); + ProofOfStake::reward_pool( + RuntimeOrigin::signed(ALICE), + REWARDED_PAIR, + REWARD_TOKEN, + REWARD_AMOUNT / 2, + 6u32.into(), + ) + .unwrap(); + }); +} + +#[test] +#[serial] +fn cant_schedule_rewards_in_past() { + ExtBuilder::new() + .issue(ALICE, REWARD_TOKEN, REWARD_AMOUNT) + .execute_with_default_mocks(|| { + System::set_block_number(1); + + roll_to_session::(5); + assert_err!( + ProofOfStake::reward_pool( + RuntimeOrigin::signed(ALICE), + REWARDED_PAIR, + REWARD_TOKEN, + REWARD_AMOUNT, + 1u32.into() + ), + Error::::CannotScheduleRewardsInPast + ); + assert_err!( + ProofOfStake::reward_pool( + RuntimeOrigin::signed(ALICE), + REWARDED_PAIR, + REWARD_TOKEN, + REWARD_AMOUNT, + 4u32.into() + ), + Error::::CannotScheduleRewardsInPast + ); + assert_err!( + ProofOfStake::reward_pool( + RuntimeOrigin::signed(ALICE), + REWARDED_PAIR, + REWARD_TOKEN, + REWARD_AMOUNT, + 5u32.into() + ), + Error::::CannotScheduleRewardsInPast + ); + }); +} + +#[test] +#[serial] +fn cannot_reward_unexisting_pool() { + ExtBuilder::new() + .issue(ALICE, REWARD_TOKEN, REWARD_AMOUNT) + .build() + .execute_with(|| { + let check_pool_exist_mock = MockValuationApi::check_pool_exist_context(); + check_pool_exist_mock + .expect() + .return_const(Err(Error::::PoolDoesNotExist.into())); + let get_valuation_for_paired_mock = + MockValuationApi::get_valuation_for_paired_context(); + get_valuation_for_paired_mock.expect().return_const(11u128); + + assert_err!( + ProofOfStake::reward_pool( + RuntimeOrigin::signed(ALICE), + REWARDED_PAIR, + REWARD_TOKEN, + REWARD_AMOUNT, + 5u32.into() + ), + Error::::PoolDoesNotExist + ); + }); +} + +#[test] +#[serial] +fn rewards_are_stored_in_pallet_account() { + ExtBuilder::new() + .issue(ALICE, REWARD_TOKEN, REWARD_AMOUNT) + .execute_with_default_mocks(|| { + System::set_block_number(1); + + assert_eq!( + TokensOf::::free_balance(REWARD_TOKEN, &Pallet::::pallet_account()), + 0 + ); + assert_eq!(TokensOf::::free_balance(REWARD_TOKEN, &ALICE), REWARD_AMOUNT); + + assert_ok!(ProofOfStake::reward_pool( + RuntimeOrigin::signed(ALICE), + REWARDED_PAIR, + REWARD_TOKEN, + REWARD_AMOUNT, + 5u32.into() + ),); + + assert_eq!(TokensOf::::free_balance(REWARD_TOKEN, &ALICE), 0); + assert_eq!( + TokensOf::::free_balance(REWARD_TOKEN, &Pallet::::pallet_account()), + REWARD_AMOUNT + ); + }); +} + +#[test] +#[serial] +fn rewards_schedule_is_stored() { + ExtBuilder::new() + .issue(ALICE, REWARD_TOKEN, REWARD_AMOUNT) + .execute_with_default_mocks(|| { + System::set_block_number(1); + + assert_ok!(ProofOfStake::reward_pool( + RuntimeOrigin::signed(ALICE), + REWARDED_PAIR, + REWARD_TOKEN, + REWARD_AMOUNT, + 5u32.into() + ),); + + let rewards_per_session = REWARD_AMOUNT / 5; + assert_eq!( + RewardsSchedulesList::::get(0).unwrap(), + ( + Schedule { + scheduled_at: 0u32, + last_session: 5u32, + liq_token: LIQUIDITY_TOKEN, + reward_token: REWARD_TOKEN, + amount_per_session: rewards_per_session, + }, + None + ) + ); + assert_eq!(ProofOfStake::list_metadata().tail, Some(0u64)); + assert_eq!(ProofOfStake::list_metadata().head, Some(0u64)); + }); +} + +#[test] +#[serial] +fn rewards_linked_list_insert_multiple_schedules() { + ExtBuilder::new() + .issue(ALICE, REWARD_TOKEN, 2 * REWARD_AMOUNT) + .execute_with_default_mocks(|| { + System::set_block_number(1); + + assert_ok!(ProofOfStake::reward_pool( + RuntimeOrigin::signed(ALICE), + REWARDED_PAIR, + REWARD_TOKEN, + REWARD_AMOUNT, + 1u32.into() + ),); + assert_eq!(ProofOfStake::list_metadata().count, 1); + + assert_ok!(ProofOfStake::reward_pool( + RuntimeOrigin::signed(ALICE), + REWARDED_PAIR, + REWARD_TOKEN, + REWARD_AMOUNT, + 2u32.into() + ),); + assert_eq!(ProofOfStake::list_metadata().count, 2); + + assert_eq!( + RewardsSchedulesList::::get(0).unwrap(), + ( + Schedule { + scheduled_at: 0u32, + last_session: 1u32, + liq_token: LIQUIDITY_TOKEN, + reward_token: REWARD_TOKEN, + amount_per_session: REWARD_AMOUNT / 1, + }, + Some(1) + ) + ); + + assert_eq!( + RewardsSchedulesList::::get(1).unwrap(), + ( + Schedule { + scheduled_at: 0u32, + last_session: 2u32, + liq_token: LIQUIDITY_TOKEN, + reward_token: REWARD_TOKEN, + amount_per_session: REWARD_AMOUNT / 2, + }, + None + ) + ); + + assert_eq!(ProofOfStake::list_metadata().head, Some(0u64)); + assert_eq!(ProofOfStake::list_metadata().tail, Some(1u64)); + assert_eq!(ProofOfStake::list_metadata().count, 2); + }); +} + +#[test] +#[serial] +fn rewards_linked_list_removes_outdated_schedule_automatically() { + ExtBuilder::new() + .issue(ALICE, REWARD_TOKEN, 2 * REWARD_AMOUNT) + .execute_with_default_mocks(|| { + System::set_block_number(1); + + assert_ok!(ProofOfStake::reward_pool( + RuntimeOrigin::signed(ALICE), + REWARDED_PAIR, + REWARD_TOKEN, + REWARD_AMOUNT, + 1u32.into() + ),); + + assert_ok!(ProofOfStake::reward_pool( + RuntimeOrigin::signed(ALICE), + REWARDED_PAIR, + REWARD_TOKEN, + REWARD_AMOUNT, + 2u32.into() + ),); + assert_eq!(ProofOfStake::list_metadata().count, 2); + + assert_ok!(RewardsSchedulesList::::get(0).ok_or(())); + assert_ok!(RewardsSchedulesList::::get(1).ok_or(())); + + assert_eq!(ProofOfStake::list_metadata().head, Some(0u64)); + assert_eq!(ProofOfStake::list_metadata().pos, None); + assert_eq!(ProofOfStake::list_metadata().tail, Some(1u64)); + + forward_to_block::(2); + + assert_eq!(ProofOfStake::list_metadata().head, Some(0u64)); + assert_eq!(ProofOfStake::list_metadata().tail, Some(1u64)); + assert_eq!(ProofOfStake::list_metadata().pos, Some(1u64)); + + forward_to_block::(5); + + assert_eq!(ProofOfStake::list_metadata().head, Some(0u64)); + assert_eq!(ProofOfStake::list_metadata().tail, Some(1u64)); + assert_eq!(ProofOfStake::list_metadata().pos, Some(1u64)); + + forward_to_block::(11); + assert_eq!(ProofOfStake::list_metadata().head, Some(0u64)); + assert_eq!(ProofOfStake::list_metadata().tail, Some(1u64)); + assert_eq!(ProofOfStake::list_metadata().pos, Some(1u64)); + + assert_eq!(ProofOfStake::list_metadata().count, 2); + forward_to_block::(21); + assert_eq!(ProofOfStake::list_metadata().count, 1); + assert_eq!(ProofOfStake::list_metadata().head, Some(1u64)); + assert_eq!(ProofOfStake::list_metadata().tail, Some(1u64)); + assert_eq!(ProofOfStake::list_metadata().pos, Some(1u64)); + + forward_to_block::(25); + assert_eq!(ProofOfStake::list_metadata().head, Some(1u64)); + assert_eq!(ProofOfStake::list_metadata().tail, Some(1u64)); + assert_eq!(ProofOfStake::list_metadata().pos, Some(1u64)); + + forward_to_block::(29); + assert_eq!(ProofOfStake::list_metadata().head, Some(1u64)); + assert_eq!(ProofOfStake::list_metadata().tail, Some(1u64)); + + forward_to_block::(30); + assert_eq!(ProofOfStake::list_metadata().head, None); + assert_eq!(ProofOfStake::list_metadata().tail, None); + assert_eq!(ProofOfStake::list_metadata().pos, None); + assert_eq!(ProofOfStake::list_metadata().count, 0); + }); +} + +fn insert_schedule_ending_at_session(n: u32) { + assert_ok!(ProofOfStake::reward_pool( + RuntimeOrigin::signed(ALICE), + REWARDED_PAIR, + REWARD_TOKEN, + REWARD_AMOUNT, + n.into(), + ),); +} + +#[test] +#[serial] +fn rewards_first_schedule_from_linked_list_of_four() { + ExtBuilder::new() + .issue(ALICE, REWARD_TOKEN, 4 * REWARD_AMOUNT) + .execute_with_default_mocks(|| { + System::set_block_number(1); + + insert_schedule_ending_at_session(1); + insert_schedule_ending_at_session(2); + insert_schedule_ending_at_session(2); + insert_schedule_ending_at_session(2); + + assert_ok!(RewardsSchedulesList::::get(0).ok_or(())); + assert_ok!(RewardsSchedulesList::::get(1).ok_or(())); + assert_ok!(RewardsSchedulesList::::get(2).ok_or(())); + assert_ok!(RewardsSchedulesList::::get(3).ok_or(())); + assert_eq!(ProofOfStake::list_metadata().head, Some(0u64)); + assert_eq!(ProofOfStake::list_metadata().pos, None); + assert_eq!(ProofOfStake::list_metadata().tail, Some(3u64)); + + assert_eq!(ProofOfStake::list_metadata().count, 4); + forward_to_block::(21); + + assert_eq!(ProofOfStake::list_metadata().head, Some(1u64)); + assert_eq!(ProofOfStake::list_metadata().tail, Some(3u64)); + assert_eq!(RewardsSchedulesList::::get(1u64).unwrap().1, Some(2u64)); + assert_eq!(RewardsSchedulesList::::get(2u64).unwrap().1, Some(3u64)); + assert_eq!(RewardsSchedulesList::::get(3u64).unwrap().1, None); + assert_eq!(ProofOfStake::list_metadata().count, 3); + }); +} + +#[test] +#[serial] +fn remove_last_schedule_from_linked_list() { + ExtBuilder::new() + .issue(ALICE, REWARD_TOKEN, 4 * REWARD_AMOUNT) + .execute_with_default_mocks(|| { + System::set_block_number(1); + + insert_schedule_ending_at_session(2); + insert_schedule_ending_at_session(2); + insert_schedule_ending_at_session(2); + insert_schedule_ending_at_session(1); + + assert_ok!(RewardsSchedulesList::::get(0).ok_or(())); + assert_ok!(RewardsSchedulesList::::get(1).ok_or(())); + assert_ok!(RewardsSchedulesList::::get(2).ok_or(())); + assert_ok!(RewardsSchedulesList::::get(3).ok_or(())); + assert_eq!(ProofOfStake::list_metadata().head, Some(0u64)); + assert_eq!(ProofOfStake::list_metadata().pos, None); + assert_eq!(ProofOfStake::list_metadata().tail, Some(3u64)); + assert_eq!(ProofOfStake::list_metadata().count, 4); + + forward_to_block::(21); + + assert_eq!(ProofOfStake::list_metadata().head, Some(0u64)); + assert_eq!(ProofOfStake::list_metadata().tail, Some(2u64)); + assert_eq!(RewardsSchedulesList::::get(0u64).unwrap().1, Some(1u64)); + assert_eq!(RewardsSchedulesList::::get(1u64).unwrap().1, Some(2u64)); + assert_eq!(RewardsSchedulesList::::get(2u64).unwrap().1, None); + assert_eq!(ProofOfStake::list_metadata().count, 3); + }); +} + +#[test] +#[serial] +fn remove_middle_schedule_from_linked_list() { + ExtBuilder::new() + .issue(ALICE, REWARD_TOKEN, 4 * REWARD_AMOUNT) + .execute_with_default_mocks(|| { + System::set_block_number(1); + + insert_schedule_ending_at_session(2); + insert_schedule_ending_at_session(1); + insert_schedule_ending_at_session(2); + insert_schedule_ending_at_session(2); + + assert_ok!(RewardsSchedulesList::::get(0).ok_or(())); + assert_ok!(RewardsSchedulesList::::get(1).ok_or(())); + assert_ok!(RewardsSchedulesList::::get(2).ok_or(())); + assert_ok!(RewardsSchedulesList::::get(3).ok_or(())); + assert_eq!(ProofOfStake::list_metadata().head, Some(0u64)); + assert_eq!(ProofOfStake::list_metadata().pos, None); + assert_eq!(ProofOfStake::list_metadata().tail, Some(3u64)); + assert_eq!(ProofOfStake::list_metadata().count, 4); + + forward_to_block::(21); + + assert_eq!(ProofOfStake::list_metadata().head, Some(0u64)); + assert_eq!(ProofOfStake::list_metadata().tail, Some(3u64)); + assert_eq!(RewardsSchedulesList::::get(0u64).unwrap().1, Some(2u64)); + assert_eq!(RewardsSchedulesList::::get(2u64).unwrap().1, Some(3u64)); + assert_eq!(RewardsSchedulesList::::get(3u64).unwrap().1, None); + assert_eq!(ProofOfStake::list_metadata().count, 3); + }); +} + +#[test] +#[serial] +fn remove_first_few_elems_at_once_from_linked_list() { + ExtBuilder::new() + .issue(ALICE, REWARD_TOKEN, 4 * REWARD_AMOUNT) + .execute_with_default_mocks(|| { + System::set_block_number(1); + + insert_schedule_ending_at_session(1); + insert_schedule_ending_at_session(1); + insert_schedule_ending_at_session(2); + insert_schedule_ending_at_session(2); + + assert_ok!(RewardsSchedulesList::::get(0).ok_or(())); + assert_ok!(RewardsSchedulesList::::get(1).ok_or(())); + assert_ok!(RewardsSchedulesList::::get(2).ok_or(())); + assert_ok!(RewardsSchedulesList::::get(3).ok_or(())); + assert_eq!(ProofOfStake::list_metadata().head, Some(0u64)); + assert_eq!(ProofOfStake::list_metadata().pos, None); + assert_eq!(ProofOfStake::list_metadata().tail, Some(3u64)); + assert_eq!(ProofOfStake::list_metadata().count, 4); + + forward_to_block::(20); + + assert_eq!(ProofOfStake::list_metadata().head, Some(2u64)); + assert_eq!(ProofOfStake::list_metadata().tail, Some(3u64)); + assert_eq!(RewardsSchedulesList::::get(2u64).unwrap().1, Some(3u64)); + assert_eq!(RewardsSchedulesList::::get(3u64).unwrap().1, None); + assert_eq!(ProofOfStake::list_metadata().count, 2); + }); +} + +#[test] +#[serial] +fn remove_few_last_elems_at_once_from_linked_list() { + ExtBuilder::new() + .issue(ALICE, REWARD_TOKEN, 4 * REWARD_AMOUNT) + .execute_with_default_mocks(|| { + System::set_block_number(1); + + insert_schedule_ending_at_session(2); + insert_schedule_ending_at_session(2); + insert_schedule_ending_at_session(1); + insert_schedule_ending_at_session(1); + + assert_ok!(RewardsSchedulesList::::get(0).ok_or(())); + assert_ok!(RewardsSchedulesList::::get(1).ok_or(())); + assert_ok!(RewardsSchedulesList::::get(2).ok_or(())); + assert_ok!(RewardsSchedulesList::::get(3).ok_or(())); + assert_eq!(ProofOfStake::list_metadata().head, Some(0u64)); + assert_eq!(ProofOfStake::list_metadata().pos, None); + assert_eq!(ProofOfStake::list_metadata().tail, Some(3u64)); + assert_eq!(ProofOfStake::list_metadata().count, 4); + + forward_to_block::(21); + + assert_eq!(ProofOfStake::list_metadata().head, Some(0u64)); + assert_eq!(ProofOfStake::list_metadata().tail, Some(1u64)); + assert_eq!(RewardsSchedulesList::::get(0u64).unwrap().1, Some(1u64)); + assert_eq!(RewardsSchedulesList::::get(1u64).unwrap().1, None); + assert_eq!(ProofOfStake::list_metadata().count, 2); + }); +} + +#[test] +#[serial] +fn remove_few_middle_elements_from_linkedd_list() { + ExtBuilder::new() + .issue(ALICE, REWARD_TOKEN, 4 * REWARD_AMOUNT) + .execute_with_default_mocks(|| { + System::set_block_number(1); + + insert_schedule_ending_at_session(2); + insert_schedule_ending_at_session(1); + insert_schedule_ending_at_session(1); + insert_schedule_ending_at_session(2); + + assert_ok!(RewardsSchedulesList::::get(0).ok_or(())); + assert_ok!(RewardsSchedulesList::::get(1).ok_or(())); + assert_ok!(RewardsSchedulesList::::get(2).ok_or(())); + assert_ok!(RewardsSchedulesList::::get(3).ok_or(())); + assert_eq!(ProofOfStake::list_metadata().head, Some(0u64)); + assert_eq!(ProofOfStake::list_metadata().pos, None); + assert_eq!(ProofOfStake::list_metadata().tail, Some(3u64)); + assert_eq!(ProofOfStake::list_metadata().count, 4); + + forward_to_block::(21); + + assert_eq!(ProofOfStake::list_metadata().head, Some(0u64)); + assert_eq!(ProofOfStake::list_metadata().tail, Some(3u64)); + assert_eq!(RewardsSchedulesList::::get(0u64).unwrap().1, Some(3u64)); + assert_eq!(RewardsSchedulesList::::get(3u64).unwrap().1, None); + assert_eq!(ProofOfStake::list_metadata().count, 2); + }); +} + +#[test] +#[serial] +fn remove_random_elements_from_linked_list() { + ExtBuilder::new() + .issue(ALICE, REWARD_TOKEN, 5 * REWARD_AMOUNT) + .execute_with_default_mocks(|| { + System::set_block_number(1); + + insert_schedule_ending_at_session(2); + insert_schedule_ending_at_session(1); + insert_schedule_ending_at_session(2); + insert_schedule_ending_at_session(1); + insert_schedule_ending_at_session(2); + + assert_ok!(RewardsSchedulesList::::get(0).ok_or(())); + assert_ok!(RewardsSchedulesList::::get(1).ok_or(())); + assert_ok!(RewardsSchedulesList::::get(2).ok_or(())); + assert_ok!(RewardsSchedulesList::::get(3).ok_or(())); + assert_ok!(RewardsSchedulesList::::get(4).ok_or(())); + assert_eq!(ProofOfStake::list_metadata().head, Some(0u64)); + assert_eq!(ProofOfStake::list_metadata().pos, None); + assert_eq!(ProofOfStake::list_metadata().tail, Some(4u64)); + assert_eq!(ProofOfStake::list_metadata().count, 5); + + forward_to_block::(21); + + assert_eq!(ProofOfStake::list_metadata().head, Some(0u64)); + assert_eq!(ProofOfStake::list_metadata().tail, Some(4u64)); + assert_eq!(RewardsSchedulesList::::get(0u64).unwrap().1, Some(2u64)); + assert_eq!(RewardsSchedulesList::::get(2u64).unwrap().1, Some(4u64)); + assert_eq!(RewardsSchedulesList::::get(4u64).unwrap().1, None); + assert_eq!(ProofOfStake::list_metadata().count, 3); + }); +} + +#[test] +#[serial] +fn remove_random_elements_from_linked_list_over_time() { + ExtBuilder::new() + .issue(ALICE, REWARD_TOKEN, 7 * REWARD_AMOUNT) + .execute_with_default_mocks(|| { + System::set_block_number(1); + + insert_schedule_ending_at_session(3); // 0 + insert_schedule_ending_at_session(2); // 1 + insert_schedule_ending_at_session(1); // 2 + insert_schedule_ending_at_session(2); // 3 + insert_schedule_ending_at_session(2); // 4 + insert_schedule_ending_at_session(1); // 5 + insert_schedule_ending_at_session(3); // 6 + + assert_eq!(ProofOfStake::list_metadata().head, Some(0u64)); + assert_eq!(ProofOfStake::list_metadata().pos, None); + assert_eq!(ProofOfStake::list_metadata().tail, Some(6u64)); + assert_eq!(ProofOfStake::list_metadata().count, 7); + + roll_to_session::(2); + process_all_schedules_in_current_session(); + assert_eq!(ProofOfStake::list_metadata().head, Some(0u64)); + assert_eq!(ProofOfStake::list_metadata().tail, Some(6u64)); + assert_eq!(RewardsSchedulesList::::get(0u64).unwrap().1, Some(1u64)); + assert_eq!(RewardsSchedulesList::::get(1u64).unwrap().1, Some(3u64)); + assert_eq!(RewardsSchedulesList::::get(3u64).unwrap().1, Some(4u64)); + assert_eq!(RewardsSchedulesList::::get(4u64).unwrap().1, Some(6u64)); + assert_eq!(RewardsSchedulesList::::get(6u64).unwrap().1, None); + assert_eq!(ProofOfStake::list_metadata().count, 5); + + roll_to_session::(3); + process_all_schedules_in_current_session(); + + assert_eq!(ProofOfStake::list_metadata().head, Some(0u64)); + assert_eq!(ProofOfStake::list_metadata().tail, Some(6u64)); + assert_eq!(RewardsSchedulesList::::get(0u64).unwrap().1, Some(6u64)); + assert_eq!(RewardsSchedulesList::::get(6u64).unwrap().1, None); + assert_eq!(ProofOfStake::list_metadata().count, 2); + }); +} + +#[test] +#[serial] +fn remove_lot_of_schedules_from_linked_list_in_single_iteration() { + ExtBuilder::new() + .issue(ALICE, REWARD_TOKEN, 10 * REWARD_AMOUNT) + .execute_with_default_mocks(|| { + System::set_block_number(1); + + insert_schedule_ending_at_session(3); // 0 + insert_schedule_ending_at_session(1); // 1 + insert_schedule_ending_at_session(1); // 1 + insert_schedule_ending_at_session(1); // 1 + insert_schedule_ending_at_session(1); // 2 + insert_schedule_ending_at_session(1); // 3 + insert_schedule_ending_at_session(1); // 4 + insert_schedule_ending_at_session(1); // 5 + insert_schedule_ending_at_session(3); // 6 + + assert_eq!(ProofOfStake::list_metadata().head, Some(0u64)); + assert_eq!(ProofOfStake::list_metadata().pos, None); + assert_eq!(ProofOfStake::list_metadata().tail, Some(8u64)); + assert_eq!(ProofOfStake::list_metadata().count, 9); + + forward_to_block::(24); + + assert_eq!(ProofOfStake::list_metadata().head, Some(0u64)); + assert_eq!(ProofOfStake::list_metadata().tail, Some(8u64)); + assert_eq!(RewardsSchedulesList::::get(0u64).unwrap().1, Some(8u64)); + assert_eq!(RewardsSchedulesList::::get(8u64).unwrap().1, None); + assert_eq!(ProofOfStake::list_metadata().count, 2); + + forward_to_block::(100); + assert_eq!(ProofOfStake::list_metadata().head, None); + assert_eq!(ProofOfStake::list_metadata().tail, None); + assert_eq!(ProofOfStake::list_metadata().count, 0); + }); +} + +#[test] +#[serial] +fn number_of_active_schedules_is_limited() { + ExtBuilder::new() + .issue(ALICE, REWARD_TOKEN, MILLION) + .execute_with_default_mocks(|| { + System::set_block_number(1); + + let max_schedules: u32 = + <::RewardsSchedulesLimit as sp_core::Get<_>>::get(); + for i in 0..(max_schedules) { + assert_ok!(ProofOfStake::reward_pool( + RuntimeOrigin::signed(ALICE), + REWARDED_PAIR, + REWARD_TOKEN, + REWARD_AMOUNT, + (5u32 + i).into() + )); + } + + assert_err!( + ProofOfStake::reward_pool( + RuntimeOrigin::signed(ALICE), + REWARDED_PAIR, + REWARD_TOKEN, + REWARD_AMOUNT, + 100u32.into() + ), + Error::::TooManySchedules + ); + + roll_to_session::(10); + + assert_ok!(ProofOfStake::reward_pool( + RuntimeOrigin::signed(ALICE), + REWARDED_PAIR, + REWARD_TOKEN, + REWARD_AMOUNT, + 100u32.into() + )); + }); +} + +#[test] +#[serial] +fn duplicated_schedules_works() { + ExtBuilder::new() + .issue(ALICE, REWARD_TOKEN, REWARD_AMOUNT) + .execute_with_default_mocks(|| { + System::set_block_number(1); + + assert_eq!(ProofOfStake::list_metadata().head, None); + assert_eq!(ProofOfStake::list_metadata().tail, None); + + assert_ok!(ProofOfStake::reward_pool( + RuntimeOrigin::signed(ALICE), + REWARDED_PAIR, + REWARD_TOKEN, + REWARD_AMOUNT / 2, + 5u32.into() + )); + + assert_eq!(ProofOfStake::list_metadata().head, Some(0u64)); + assert_eq!(ProofOfStake::list_metadata().tail, Some(0u64)); + + assert_ok!(ProofOfStake::reward_pool( + RuntimeOrigin::signed(ALICE), + REWARDED_PAIR, + REWARD_TOKEN, + REWARD_AMOUNT / 2, + 5u32.into() + )); + + assert_eq!(ProofOfStake::list_metadata().head, Some(0u64)); + assert_eq!(ProofOfStake::list_metadata().tail, Some(1u64)); + }); +} + +#[test] +#[serial] +fn reject_schedule_with_too_little_rewards_per_session() { + ExtBuilder::new() + .issue(ALICE, REWARD_TOKEN, REWARD_AMOUNT) + .build() + .execute_with(|| { + System::set_block_number(1); + let check_pool_exist_mock = MockValuationApi::check_pool_exist_context(); + check_pool_exist_mock.expect().return_const(Ok(())); + let get_valuation_for_paired_mock = + MockValuationApi::get_valuation_for_paired_context(); + get_valuation_for_paired_mock.expect().return_const(1u128); + + let find_valuation_mock = MockValuationApi::find_valuation_context(); + find_valuation_mock.expect().return_const(Ok(0u128)); + + roll_to_session::(4); + + assert_err!( + ProofOfStake::reward_pool( + RuntimeOrigin::signed(ALICE), + REWARDED_PAIR, + REWARD_TOKEN, + 1, + 5u32.into() + ), + Error::::TooLittleRewards + ); + }); +} + +#[test] +#[serial] +fn accept_schedule_valuated_in_native_token() { + ExtBuilder::new() + .issue(ALICE, ProofOfStake::native_token_id(), REWARD_AMOUNT) + .build() + .execute_with(|| { + System::set_block_number(1); + let check_pool_exist_mock = MockValuationApi::check_pool_exist_context(); + check_pool_exist_mock.expect().return_const(Ok(())); + let get_valuation_for_paired_mock = + MockValuationApi::get_valuation_for_paired_context(); + get_valuation_for_paired_mock.expect().return_const(0u128); + let get_reserve_and_lp_supply_mock = + MockValuationApi::get_reserve_and_lp_supply_context(); + get_reserve_and_lp_supply_mock + .expect() + .return_const(Some((min_req_volume(), min_req_volume()))); + + roll_to_session::(4); + + assert_ok!(ProofOfStake::reward_pool( + RuntimeOrigin::signed(ALICE), + REWARDED_PAIR, + ProofOfStake::native_token_id(), + 10, + 5u32.into() + ),); + }); +} + +#[test] +#[serial] +fn accept_schedule_valuated_in_token_paired_with_native_token() { + ExtBuilder::new() + .issue(ALICE, ProofOfStake::native_token_id(), REWARD_AMOUNT) + .issue(ALICE, TOKEN_PAIRED_WITH_MGX, REWARD_AMOUNT) + .build() + .execute_with(|| { + System::set_block_number(1); + let check_pool_exist_mock = MockValuationApi::check_pool_exist_context(); + check_pool_exist_mock.expect().return_const(Ok(())); + + let get_valuation_for_paired_mock = + MockValuationApi::get_valuation_for_paired_context(); + get_valuation_for_paired_mock.expect().return_const(0u128); + + let find_valuation_mock = MockValuationApi::find_valuation_context(); + find_valuation_mock.expect().return_const(Ok(10u128)); + let get_reserve_and_lp_supply_mock = + MockValuationApi::get_reserve_and_lp_supply_context(); + get_reserve_and_lp_supply_mock + .expect() + .return_const(Some((min_req_volume(), min_req_volume()))); + + roll_to_session::(4); + + assert_ok!(ProofOfStake::reward_pool( + RuntimeOrigin::signed(ALICE), + REWARDED_PAIR, + TOKEN_PAIRED_WITH_MGX, + REWARD_AMOUNT, + 5u32.into() + ),); + }); +} + +#[test] +#[serial] +fn user_can_claim_3rdparty_rewards() { + ExtBuilder::new() + .issue(ALICE, REWARD_TOKEN, REWARD_AMOUNT) + .issue(BOB, LIQUIDITY_TOKEN, 100) + .issue(CHARLIE, LIQUIDITY_TOKEN, 100) + .issue(EVE, LIQUIDITY_TOKEN, 100) + .execute_with_default_mocks(|| { + System::set_block_number(1); + + TokensOf::::mint(LIQUIDITY_TOKEN, &BOB, 100).unwrap(); + TokensOf::::mint(LIQUIDITY_TOKEN, &CHARLIE, 100).unwrap(); + TokensOf::::mint(LIQUIDITY_TOKEN, &EVE, 100).unwrap(); + + let amount = 10_000u128; + + ProofOfStake::reward_pool( + RuntimeOrigin::signed(ALICE), + REWARDED_PAIR, + REWARD_TOKEN, + REWARD_AMOUNT, + 10u32.into(), + ) + .unwrap(); + + roll_to_session::(1); + ProofOfStake::activate_liquidity_for_3rdparty_rewards( + RuntimeOrigin::signed(BOB), + LIQUIDITY_TOKEN, + 100, + REWARD_TOKEN, + None, + ) + .unwrap(); + assert_eq!( + ProofOfStake::calculate_3rdparty_rewards_amount(BOB, LIQUIDITY_TOKEN, REWARD_TOKEN), + Ok(0) + ); + + roll_to_session::(2); + ProofOfStake::activate_liquidity_for_3rdparty_rewards( + RuntimeOrigin::signed(CHARLIE), + LIQUIDITY_TOKEN, + 100, + REWARD_TOKEN, + None, + ) + .unwrap(); + assert_eq!( + ProofOfStake::calculate_3rdparty_rewards_amount( + CHARLIE, + LIQUIDITY_TOKEN, + REWARD_TOKEN + ), + Ok(0) + ); + assert_eq!( + ProofOfStake::calculate_3rdparty_rewards_amount(BOB, LIQUIDITY_TOKEN, REWARD_TOKEN), + Ok(1000) + ); + + roll_to_session::(3); + assert_eq!( + ProofOfStake::calculate_3rdparty_rewards_amount(BOB, LIQUIDITY_TOKEN, REWARD_TOKEN), + Ok(1500) + ); + assert_eq!( + ProofOfStake::calculate_3rdparty_rewards_amount( + CHARLIE, + LIQUIDITY_TOKEN, + REWARD_TOKEN + ), + Ok(500) + ); + }); +} + +#[test] +#[serial] +fn overlapping_3rdparty_rewards_works() { + ExtBuilder::new() + .issue(ALICE, REWARD_TOKEN, REWARD_AMOUNT) + .build() + .execute_with(|| { + System::set_block_number(1); + const LIQUIDITY_TOKEN: u32 = 5; + let check_pool_exist_mock = MockValuationApi::check_pool_exist_context(); + check_pool_exist_mock.expect().return_const(Ok(())); + let get_valuation_for_paired_mock = + MockValuationApi::get_valuation_for_paired_context(); + get_valuation_for_paired_mock.expect().return_const(11u128); + let get_reserve_and_lp_supply_mock = + MockValuationApi::get_reserve_and_lp_supply_context(); + get_reserve_and_lp_supply_mock + .expect() + .return_const(Some((min_req_volume(), min_req_volume()))); + + let first_reward_token = TokensOf::::create(&ALICE, MILLION).unwrap(); + TokensOf::::mint(LIQUIDITY_TOKEN, &BOB, 200).unwrap(); + + let amount = 10_000u128; + + ProofOfStake::reward_pool( + RuntimeOrigin::signed(ALICE), + LIQUIDITY_TOKEN, + first_reward_token, + amount, + 10u32.into(), + ) + .unwrap(); + + roll_to_session::(1); + ProofOfStake::activate_liquidity_for_3rdparty_rewards( + RuntimeOrigin::signed(BOB), + LIQUIDITY_TOKEN, + 100, + first_reward_token, + None, + ) + .unwrap(); + + roll_to_session::(5); + let second_reward_token_id = TokensOf::::create(&ALICE, MILLION).unwrap(); + ProofOfStake::reward_pool( + RuntimeOrigin::signed(ALICE), + LIQUIDITY_TOKEN, + second_reward_token_id, + 100_000, + 15u32.into(), + ) + .unwrap(); + ProofOfStake::activate_liquidity_for_3rdparty_rewards( + RuntimeOrigin::signed(BOB), + LIQUIDITY_TOKEN, + 100, + second_reward_token_id, + None, + ) + .unwrap(); + + roll_to_session::(7); + + assert_eq!( + ProofOfStake::calculate_3rdparty_rewards_amount( + BOB, + LIQUIDITY_TOKEN, + second_reward_token_id + ), + Ok(10000) + ); + + assert_eq!( + ProofOfStake::calculate_3rdparty_rewards_amount( + BOB, + LIQUIDITY_TOKEN, + first_reward_token + ), + Ok(6000) + ); + }); +} + +#[test] +#[serial] +fn reuse_activated_liquiddity_tokens_for_multiple_3rdparty_schedules() { + ExtBuilder::new() + .issue(ALICE, FIRST_REWARD_TOKEN, REWARD_AMOUNT) + .issue(ALICE, SECOND_REWARD_TOKEN, 100_000u128) + .issue(BOB, LIQUIDITY_TOKEN, 200) + .execute_with_default_mocks(|| { + System::set_block_number(1); + + ProofOfStake::reward_pool( + RuntimeOrigin::signed(ALICE), + REWARDED_PAIR, + FIRST_REWARD_TOKEN, + REWARD_AMOUNT, + 10u32.into(), + ) + .unwrap(); + + roll_to_session::(1); + ProofOfStake::activate_liquidity_for_3rdparty_rewards( + RuntimeOrigin::signed(BOB), + LIQUIDITY_TOKEN, + 100, + FIRST_REWARD_TOKEN, + None, + ) + .unwrap(); + + roll_to_session::(5); + ProofOfStake::reward_pool( + RuntimeOrigin::signed(ALICE), + REWARDED_PAIR, + SECOND_REWARD_TOKEN, + 100_000, + 15u32.into(), + ) + .unwrap(); + + ProofOfStake::activate_liquidity_for_3rdparty_rewards( + RuntimeOrigin::signed(BOB), + LIQUIDITY_TOKEN, + 100, + SECOND_REWARD_TOKEN, + Some(ThirdPartyActivationKind::ActivatedLiquidity(FIRST_REWARD_TOKEN)), + ) + .unwrap(); + + roll_to_session::(7); + + assert_eq!( + ProofOfStake::calculate_3rdparty_rewards_amount( + BOB, + LIQUIDITY_TOKEN, + SECOND_REWARD_TOKEN + ), + Ok(10000) + ); + + assert_eq!( + ProofOfStake::calculate_3rdparty_rewards_amount( + BOB, + LIQUIDITY_TOKEN, + FIRST_REWARD_TOKEN + ), + Ok(6000) + ); + }); +} + +#[test] +#[serial] +fn deactivate_3rdparty_rewards() { + ExtBuilder::new() + .issue(ALICE, REWARD_TOKEN, REWARD_AMOUNT) + .issue(BOB, LIQUIDITY_TOKEN, 100) + .issue(CHARLIE, LIQUIDITY_TOKEN, 100) + .execute_with_default_mocks(|| { + System::set_block_number(1); + + ProofOfStake::reward_pool( + RuntimeOrigin::signed(ALICE), + REWARDED_PAIR, + REWARD_TOKEN, + REWARD_AMOUNT, + 10u32.into(), + ) + .unwrap(); + + roll_to_session::(1); + ProofOfStake::activate_liquidity_for_3rdparty_rewards( + RuntimeOrigin::signed(BOB), + LIQUIDITY_TOKEN, + 100, + REWARD_TOKEN, + None, + ) + .unwrap(); + + ProofOfStake::activate_liquidity_for_3rdparty_rewards( + RuntimeOrigin::signed(CHARLIE), + LIQUIDITY_TOKEN, + 100, + REWARD_TOKEN, + None, + ) + .unwrap(); + + roll_to_session::(2); + + assert_eq!( + ProofOfStake::calculate_3rdparty_rewards_amount(BOB, LIQUIDITY_TOKEN, REWARD_TOKEN), + Ok(500) + ); + + assert_eq!( + ProofOfStake::calculate_3rdparty_rewards_amount( + CHARLIE, + LIQUIDITY_TOKEN, + REWARD_TOKEN + ), + Ok(500) + ); + ProofOfStake::deactivate_liquidity_for_3rdparty_rewards( + RuntimeOrigin::signed(CHARLIE), + LIQUIDITY_TOKEN, + 100, + REWARD_TOKEN, + ) + .unwrap(); + + roll_to_session::(3); + + assert_eq!( + ProofOfStake::calculate_3rdparty_rewards_amount(BOB, LIQUIDITY_TOKEN, REWARD_TOKEN), + Ok(1500) + ); + + assert_eq!( + ProofOfStake::calculate_3rdparty_rewards_amount( + CHARLIE, + LIQUIDITY_TOKEN, + REWARD_TOKEN + ), + Ok(500) + ); + }); +} + +#[test] +#[serial] +fn calculate_and_claim_rewards_from_multiple_schedules_using_single_liquidity() { + ExtBuilder::new() + .issue(ALICE, FIRST_REWARD_TOKEN, 2 * REWARD_AMOUNT) + .issue(ALICE, SECOND_REWARD_TOKEN, 4 * REWARD_AMOUNT) + .issue(BOB, FIRST_LIQUIDITY_TOKEN, 100) + .issue(BOB, SECOND_LIQUIDITY_TOKEN, 100) + .build() + .execute_with(|| { + let check_pool_exist_mock = MockValuationApi::check_pool_exist_context(); + check_pool_exist_mock.expect().return_const(Ok(())); + + let get_valuation_for_paired_mock = + MockValuationApi::get_valuation_for_paired_context(); + get_valuation_for_paired_mock.expect().return_const(11u128); + + let get_reserve_and_lp_supply_mock = + MockValuationApi::get_reserve_and_lp_supply_context(); + get_reserve_and_lp_supply_mock + .expect() + .return_const(Some((min_req_volume(), min_req_volume()))); + + System::set_block_number(1); + ProofOfStake::reward_pool( + RuntimeOrigin::signed(ALICE), + FIRST_LIQUIDITY_TOKEN, + FIRST_REWARD_TOKEN, + REWARD_AMOUNT, + 10u32.into(), + ) + .unwrap(); + ProofOfStake::activate_liquidity_for_3rdparty_rewards( + RuntimeOrigin::signed(BOB), + FIRST_LIQUIDITY_TOKEN, + 100u128, + FIRST_REWARD_TOKEN, + None, + ) + .unwrap(); + + roll_to_session::(2); + assert_eq!( + ProofOfStake::calculate_3rdparty_rewards_all(BOB), + vec![(FIRST_LIQUIDITY_TOKEN, FIRST_REWARD_TOKEN, 1 * 1000u128),] + ); + ProofOfStake::reward_pool( + RuntimeOrigin::signed(ALICE), + FIRST_LIQUIDITY_TOKEN, + SECOND_REWARD_TOKEN, + 2 * REWARD_AMOUNT, + 12u32.into(), + ) + .unwrap(); + ProofOfStake::activate_liquidity_for_3rdparty_rewards( + RuntimeOrigin::signed(BOB), + FIRST_LIQUIDITY_TOKEN, + 100u128, + SECOND_REWARD_TOKEN, + Some(ThirdPartyActivationKind::ActivatedLiquidity(FIRST_REWARD_TOKEN)), + ) + .unwrap(); + + roll_to_session::(3); + assert_eq!( + ProofOfStake::calculate_3rdparty_rewards_all(BOB), + vec![ + (FIRST_LIQUIDITY_TOKEN, FIRST_REWARD_TOKEN, 2 * 1000u128), + (FIRST_LIQUIDITY_TOKEN, SECOND_REWARD_TOKEN, 0 * 2000u128), + ] + ); + + roll_to_session::(4); + + assert_eq!( + ProofOfStake::calculate_3rdparty_rewards_all(BOB), + vec![ + (FIRST_LIQUIDITY_TOKEN, FIRST_REWARD_TOKEN, 3 * 1000u128), + (FIRST_LIQUIDITY_TOKEN, SECOND_REWARD_TOKEN, 1 * 2000u128), + ] + ); + ProofOfStake::reward_pool( + RuntimeOrigin::signed(ALICE), + SECOND_LIQUIDITY_TOKEN, + FIRST_REWARD_TOKEN, + REWARD_AMOUNT, + 14u32.into(), + ) + .unwrap(); + ProofOfStake::activate_liquidity_for_3rdparty_rewards( + RuntimeOrigin::signed(BOB), + SECOND_LIQUIDITY_TOKEN, + 100u128, + FIRST_REWARD_TOKEN, + None, + ) + .unwrap(); + + roll_to_session::(5); + assert_eq!( + ProofOfStake::calculate_3rdparty_rewards_all(BOB), + vec![ + (FIRST_LIQUIDITY_TOKEN, FIRST_REWARD_TOKEN, 4 * 1000u128), + (FIRST_LIQUIDITY_TOKEN, SECOND_REWARD_TOKEN, 2 * 2000u128), + (SECOND_LIQUIDITY_TOKEN, FIRST_REWARD_TOKEN, 0 * 1000u128), + ] + ); + ProofOfStake::reward_pool( + RuntimeOrigin::signed(ALICE), + SECOND_LIQUIDITY_TOKEN, + SECOND_REWARD_TOKEN, + 2 * REWARD_AMOUNT, + 15u32.into(), + ) + .unwrap(); + ProofOfStake::activate_liquidity_for_3rdparty_rewards( + RuntimeOrigin::signed(BOB), + SECOND_LIQUIDITY_TOKEN, + 100u128, + SECOND_REWARD_TOKEN, + Some(ThirdPartyActivationKind::ActivatedLiquidity(FIRST_REWARD_TOKEN)), + ) + .unwrap(); + + roll_to_session::(7); + assert_eq!( + ProofOfStake::calculate_3rdparty_rewards_all(BOB), + vec![ + (FIRST_LIQUIDITY_TOKEN, FIRST_REWARD_TOKEN, 6 * 1000u128), + (FIRST_LIQUIDITY_TOKEN, SECOND_REWARD_TOKEN, 4 * 2000u128), + (SECOND_LIQUIDITY_TOKEN, FIRST_REWARD_TOKEN, 2 * 1000u128), + (SECOND_LIQUIDITY_TOKEN, SECOND_REWARD_TOKEN, 1 * 2000u128), + ] + ); + + ProofOfStake::claim_3rdparty_rewards( + RuntimeOrigin::signed(BOB), + FIRST_LIQUIDITY_TOKEN, + FIRST_REWARD_TOKEN, + ) + .unwrap(); + + assert_eq!( + ProofOfStake::calculate_3rdparty_rewards_all(BOB), + vec![ + (FIRST_LIQUIDITY_TOKEN, FIRST_REWARD_TOKEN, 0u128), + (FIRST_LIQUIDITY_TOKEN, SECOND_REWARD_TOKEN, 4 * 2000u128), + (SECOND_LIQUIDITY_TOKEN, FIRST_REWARD_TOKEN, 2 * 1000u128), + (SECOND_LIQUIDITY_TOKEN, SECOND_REWARD_TOKEN, 1 * 2000u128), + ] + ); + + ProofOfStake::claim_3rdparty_rewards( + RuntimeOrigin::signed(BOB), + FIRST_LIQUIDITY_TOKEN, + SECOND_REWARD_TOKEN, + ) + .unwrap(); + assert_eq!( + ProofOfStake::calculate_3rdparty_rewards_all(BOB), + vec![ + (FIRST_LIQUIDITY_TOKEN, FIRST_REWARD_TOKEN, 0u128), + (FIRST_LIQUIDITY_TOKEN, SECOND_REWARD_TOKEN, 0u128), + (SECOND_LIQUIDITY_TOKEN, FIRST_REWARD_TOKEN, 2 * 1000u128), + (SECOND_LIQUIDITY_TOKEN, SECOND_REWARD_TOKEN, 1 * 2000u128), + ] + ); + + ProofOfStake::claim_3rdparty_rewards( + RuntimeOrigin::signed(BOB), + SECOND_LIQUIDITY_TOKEN, + FIRST_REWARD_TOKEN, + ) + .unwrap(); + assert_eq!( + ProofOfStake::calculate_3rdparty_rewards_all(BOB), + vec![ + (FIRST_LIQUIDITY_TOKEN, FIRST_REWARD_TOKEN, 0u128), + (FIRST_LIQUIDITY_TOKEN, SECOND_REWARD_TOKEN, 0u128), + (SECOND_LIQUIDITY_TOKEN, FIRST_REWARD_TOKEN, 0u128), + (SECOND_LIQUIDITY_TOKEN, SECOND_REWARD_TOKEN, 1 * 2000u128), + ] + ); + + ProofOfStake::claim_3rdparty_rewards( + RuntimeOrigin::signed(BOB), + SECOND_LIQUIDITY_TOKEN, + SECOND_REWARD_TOKEN, + ) + .unwrap(); + + assert_eq!( + ProofOfStake::calculate_3rdparty_rewards_all(BOB), + vec![ + (FIRST_LIQUIDITY_TOKEN, FIRST_REWARD_TOKEN, 0u128), + (FIRST_LIQUIDITY_TOKEN, SECOND_REWARD_TOKEN, 0u128), + (SECOND_LIQUIDITY_TOKEN, FIRST_REWARD_TOKEN, 0u128), + (SECOND_LIQUIDITY_TOKEN, SECOND_REWARD_TOKEN, 0u128), + ] + ); + + ProofOfStake::deactivate_liquidity_for_3rdparty_rewards( + RuntimeOrigin::signed(BOB), + FIRST_LIQUIDITY_TOKEN, + 100, + FIRST_REWARD_TOKEN, + ) + .unwrap(); + ProofOfStake::deactivate_liquidity_for_3rdparty_rewards( + RuntimeOrigin::signed(BOB), + FIRST_LIQUIDITY_TOKEN, + 100, + SECOND_REWARD_TOKEN, + ) + .unwrap(); + + ProofOfStake::deactivate_liquidity_for_3rdparty_rewards( + RuntimeOrigin::signed(BOB), + SECOND_LIQUIDITY_TOKEN, + 100, + FIRST_REWARD_TOKEN, + ) + .unwrap(); + ProofOfStake::deactivate_liquidity_for_3rdparty_rewards( + RuntimeOrigin::signed(BOB), + SECOND_LIQUIDITY_TOKEN, + 100, + SECOND_REWARD_TOKEN, + ) + .unwrap(); + assert_eq!( + ProofOfStake::calculate_3rdparty_rewards_all(BOB), + vec![ + (FIRST_LIQUIDITY_TOKEN, FIRST_REWARD_TOKEN, 0u128), + (FIRST_LIQUIDITY_TOKEN, SECOND_REWARD_TOKEN, 0u128), + (SECOND_LIQUIDITY_TOKEN, FIRST_REWARD_TOKEN, 0u128), + (SECOND_LIQUIDITY_TOKEN, SECOND_REWARD_TOKEN, 0u128), + ] + ); + }); +} + +#[test] +#[serial] +fn liquidity_minting_liquidity_can_be_resused() { + ExtBuilder::new() + .issue(ALICE, FIRST_REWARD_TOKEN, REWARD_AMOUNT) + .issue(ALICE, SECOND_REWARD_TOKEN, 100_000u128) + .issue(BOB, LIQUIDITY_TOKEN, 100) + .execute_with_default_mocks(|| { + System::set_block_number(1); + + ProofOfStake::update_pool_promotion(RuntimeOrigin::root(), LIQUIDITY_TOKEN, 1u8) + .unwrap(); + ProofOfStake::reward_pool( + RuntimeOrigin::signed(ALICE), + REWARDED_PAIR, + FIRST_REWARD_TOKEN, + REWARD_AMOUNT, + 10u32.into(), + ) + .unwrap(); + + ProofOfStake::activate_liquidity_for_native_rewards( + RuntimeOrigin::signed(BOB), + LIQUIDITY_TOKEN, + 100, + None, + ) + .unwrap(); + ProofOfStake::activate_liquidity_for_3rdparty_rewards( + RuntimeOrigin::signed(BOB), + LIQUIDITY_TOKEN, + 100, + FIRST_REWARD_TOKEN, + Some(ThirdPartyActivationKind::NativeRewardsLiquidity), + ) + .unwrap(); + + roll_to_session::(2); + + assert_eq!( + ProofOfStake::calculate_3rdparty_rewards_amount( + BOB, + LIQUIDITY_TOKEN, + FIRST_REWARD_TOKEN + ), + Ok(1000) + ); + }); +} + +#[test] +#[serial] +fn fail_to_transfer_tokens_that_has_been_partially_deactivated() { + // 1. activate tokens for native rewards + // 2. re-activate tokens for 3rdparty rewards + // 4. deactivate tokens for 3rdparty rewards + // 5. fail to transfer assets as they are still locked + // 6. deactivate tokens for native rewards + // 7. successfully transfer unlocked tokens + ExtBuilder::new() + .issue(ALICE, FIRST_REWARD_TOKEN, REWARD_AMOUNT) + .issue(ALICE, SECOND_REWARD_TOKEN, 100_000u128) + .issue(BOB, LIQUIDITY_TOKEN, 100) + .execute_with_default_mocks(|| { + System::set_block_number(1); + + ProofOfStake::update_pool_promotion(RuntimeOrigin::root(), LIQUIDITY_TOKEN, 1u8) + .unwrap(); + ProofOfStake::reward_pool( + RuntimeOrigin::signed(ALICE), + REWARDED_PAIR, + FIRST_REWARD_TOKEN, + REWARD_AMOUNT, + 10u32.into(), + ) + .unwrap(); + ProofOfStake::activate_liquidity_for_native_rewards( + RuntimeOrigin::signed(BOB), + LIQUIDITY_TOKEN, + 100, + None, + ) + .unwrap(); + ProofOfStake::activate_liquidity_for_3rdparty_rewards( + RuntimeOrigin::signed(BOB), + LIQUIDITY_TOKEN, + 100, + FIRST_REWARD_TOKEN, + Some(ThirdPartyActivationKind::NativeRewardsLiquidity), + ) + .unwrap(); + + assert_err!( + ProofOfStake::deactivate_liquidity_for_native_rewards( + RuntimeOrigin::signed(BOB), + LIQUIDITY_TOKEN, + 100, + ), + Error::::LiquidityLockedIn3rdpartyRewards + ); + + assert_err!( + TokensOf::::transfer( + LIQUIDITY_TOKEN, + &BOB, + &CHARLIE, + 100, + ExistenceRequirement::AllowDeath + ), + orml_tokens::Error::::BalanceTooLow + ); + + ProofOfStake::deactivate_liquidity_for_3rdparty_rewards( + RuntimeOrigin::signed(BOB), + LIQUIDITY_TOKEN, + 100, + FIRST_REWARD_TOKEN, + ) + .unwrap(); + ProofOfStake::deactivate_liquidity_for_native_rewards( + RuntimeOrigin::signed(BOB), + LIQUIDITY_TOKEN, + 100, + ) + .unwrap(); + + assert_ok!(TokensOf::::transfer( + LIQUIDITY_TOKEN, + &BOB, + &CHARLIE, + 100, + ExistenceRequirement::AllowDeath + )); + }); +} + +#[test] +#[serial] +fn allow_to_deactive_native_liq_when_only_part_of_it_is_used_for_3rdpaty_rewards() { + // 1. activate tokens for native rewards + // 2. re-activate tokens for 3rdparty rewards + // 4. deactivate tokens for 3rdparty rewards + // 5. fail to transfer assets as they are still locked + // 6. deactivate tokens for native rewards + // 7. successfully transfer unlocked tokens + ExtBuilder::new() + .issue(ALICE, FIRST_REWARD_TOKEN, REWARD_AMOUNT) + .issue(ALICE, SECOND_REWARD_TOKEN, 100_000u128) + .issue(BOB, LIQUIDITY_TOKEN, 100) + .execute_with_default_mocks(|| { + System::set_block_number(1); + + ProofOfStake::update_pool_promotion(RuntimeOrigin::root(), LIQUIDITY_TOKEN, 1u8) + .unwrap(); + ProofOfStake::reward_pool( + RuntimeOrigin::signed(ALICE), + REWARDED_PAIR, + FIRST_REWARD_TOKEN, + REWARD_AMOUNT, + 10u32.into(), + ) + .unwrap(); + ProofOfStake::activate_liquidity_for_native_rewards( + RuntimeOrigin::signed(BOB), + LIQUIDITY_TOKEN, + 100, + None, + ) + .unwrap(); + ProofOfStake::activate_liquidity_for_3rdparty_rewards( + RuntimeOrigin::signed(BOB), + LIQUIDITY_TOKEN, + 80, + FIRST_REWARD_TOKEN, + Some(ThirdPartyActivationKind::NativeRewardsLiquidity), + ) + .unwrap(); + + assert_err!( + ProofOfStake::deactivate_liquidity_for_native_rewards( + RuntimeOrigin::signed(BOB), + LIQUIDITY_TOKEN, + 100, + ), + Error::::LiquidityLockedIn3rdpartyRewards + ); + + assert_ok!(ProofOfStake::deactivate_liquidity_for_native_rewards( + RuntimeOrigin::signed(BOB), + LIQUIDITY_TOKEN, + 20, + )); + + assert_err!( + TokensOf::::transfer( + LIQUIDITY_TOKEN, + &BOB, + &CHARLIE, + 100, + ExistenceRequirement::AllowDeath + ), + orml_tokens::Error::::BalanceTooLow + ); + + assert_ok!(TokensOf::::transfer( + LIQUIDITY_TOKEN, + &BOB, + &CHARLIE, + 20, + ExistenceRequirement::AllowDeath + )); + }); +} + +#[test] +#[serial] +fn when_liquidity_mining_is_reused_it_is_unlocked_properly() { + ExtBuilder::new() + .issue(ALICE, FIRST_REWARD_TOKEN, REWARD_AMOUNT) + .issue(ALICE, SECOND_REWARD_TOKEN, 100_000u128) + .issue(BOB, LIQUIDITY_TOKEN, 100) + .execute_with_default_mocks(|| { + System::set_block_number(1); + + ProofOfStake::update_pool_promotion(RuntimeOrigin::root(), LIQUIDITY_TOKEN, 1u8) + .unwrap(); + ProofOfStake::reward_pool( + RuntimeOrigin::signed(ALICE), + REWARDED_PAIR, + FIRST_REWARD_TOKEN, + REWARD_AMOUNT, + 10u32.into(), + ) + .unwrap(); + ProofOfStake::reward_pool( + RuntimeOrigin::signed(ALICE), + REWARDED_PAIR, + SECOND_REWARD_TOKEN, + 2 * REWARD_AMOUNT, + 10u32.into(), + ) + .unwrap(); + + ProofOfStake::activate_liquidity_for_native_rewards( + RuntimeOrigin::signed(BOB), + LIQUIDITY_TOKEN, + 100, + None, + ) + .unwrap(); + assert_err!( + TokensOf::::transfer( + LIQUIDITY_TOKEN, + &BOB, + &CHARLIE, + 100, + ExistenceRequirement::AllowDeath + ), + orml_tokens::Error::::BalanceTooLow + ); + + ProofOfStake::activate_liquidity_for_3rdparty_rewards( + RuntimeOrigin::signed(BOB), + LIQUIDITY_TOKEN, + 100, + FIRST_REWARD_TOKEN, + Some(ThirdPartyActivationKind::NativeRewardsLiquidity), + ) + .unwrap(); + + TokensOf::::mint(LIQUIDITY_TOKEN, &BOB, 100).unwrap(); + ProofOfStake::activate_liquidity_for_3rdparty_rewards( + RuntimeOrigin::signed(BOB), + LIQUIDITY_TOKEN, + 100, + FIRST_REWARD_TOKEN, + None, + ) + .unwrap(); + + ProofOfStake::deactivate_liquidity_for_3rdparty_rewards( + RuntimeOrigin::signed(BOB), + LIQUIDITY_TOKEN, + 200, + FIRST_REWARD_TOKEN, + ) + .unwrap(); + assert_err!( + TokensOf::::transfer( + LIQUIDITY_TOKEN, + &BOB, + &CHARLIE, + 101, + ExistenceRequirement::AllowDeath + ), + orml_tokens::Error::::BalanceTooLow + ); + + assert_ok!(TokensOf::::transfer( + LIQUIDITY_TOKEN, + &BOB, + &CHARLIE, + 100, + ExistenceRequirement::AllowDeath + )); + }); +} + +#[test] +#[serial] +fn liquidity_can_be_deactivated_when_all_reward_participation_were_deactivated() { + ExtBuilder::new() + .issue(ALICE, FIRST_REWARD_TOKEN, REWARD_AMOUNT) + .issue(ALICE, SECOND_REWARD_TOKEN, 100_000u128) + .issue(BOB, LIQUIDITY_TOKEN, 100) + .execute_with_default_mocks(|| { + System::set_block_number(1); + + ProofOfStake::update_pool_promotion(RuntimeOrigin::root(), LIQUIDITY_TOKEN, 1u8) + .unwrap(); + ProofOfStake::reward_pool( + RuntimeOrigin::signed(ALICE), + REWARDED_PAIR, + FIRST_REWARD_TOKEN, + REWARD_AMOUNT, + 10u32.into(), + ) + .unwrap(); + + ProofOfStake::reward_pool( + RuntimeOrigin::signed(ALICE), + REWARDED_PAIR, + SECOND_REWARD_TOKEN, + 2 * REWARD_AMOUNT, + 10u32.into(), + ) + .unwrap(); + + ProofOfStake::activate_liquidity_for_3rdparty_rewards( + RuntimeOrigin::signed(BOB), + LIQUIDITY_TOKEN, + 100, + FIRST_REWARD_TOKEN, + None, + ) + .unwrap(); + + ProofOfStake::activate_liquidity_for_3rdparty_rewards( + RuntimeOrigin::signed(BOB), + LIQUIDITY_TOKEN, + 100, + SECOND_REWARD_TOKEN, + Some(ThirdPartyActivationKind::ActivatedLiquidity(FIRST_REWARD_TOKEN)), + ) + .unwrap(); + + assert_err!( + TokensOf::::transfer( + 0, + &BOB, + &CHARLIE, + 100, + ExistenceRequirement::AllowDeath + ), + orml_tokens::Error::::BalanceTooLow + ); + ProofOfStake::deactivate_liquidity_for_3rdparty_rewards( + RuntimeOrigin::signed(BOB), + LIQUIDITY_TOKEN, + 100, + FIRST_REWARD_TOKEN, + ) + .unwrap(); + assert_err!( + TokensOf::::transfer( + LIQUIDITY_TOKEN, + &BOB, + &CHARLIE, + 100, + ExistenceRequirement::AllowDeath + ), + orml_tokens::Error::::BalanceTooLow + ); + ProofOfStake::deactivate_liquidity_for_3rdparty_rewards( + RuntimeOrigin::signed(BOB), + LIQUIDITY_TOKEN, + 100, + SECOND_REWARD_TOKEN, + ) + .unwrap(); + + assert_ok!(TokensOf::::transfer( + LIQUIDITY_TOKEN, + &BOB, + &CHARLIE, + 100, + ExistenceRequirement::AllowDeath + ),); + }); +} + +#[test] +#[serial] +fn can_claim_schedule_rewards() { + ExtBuilder::new() + .issue(ALICE, FIRST_REWARD_TOKEN, REWARD_AMOUNT) + .issue(ALICE, SECOND_REWARD_TOKEN, 100_000u128) + .issue(BOB, LIQUIDITY_TOKEN, 100) + .execute_with_default_mocks(|| { + System::set_block_number(1); + + ProofOfStake::update_pool_promotion(RuntimeOrigin::root(), LIQUIDITY_TOKEN, 1u8) + .unwrap(); + ProofOfStake::reward_pool( + RuntimeOrigin::signed(ALICE), + REWARDED_PAIR, + FIRST_REWARD_TOKEN, + REWARD_AMOUNT, + 10u32.into(), + ) + .unwrap(); + + ProofOfStake::reward_pool( + RuntimeOrigin::signed(ALICE), + REWARDED_PAIR, + SECOND_REWARD_TOKEN, + REWARD_AMOUNT, + 10u32.into(), + ) + .unwrap(); + + ProofOfStake::activate_liquidity_for_3rdparty_rewards( + RuntimeOrigin::signed(BOB), + LIQUIDITY_TOKEN, + 100, + FIRST_REWARD_TOKEN, + None, + ) + .unwrap(); + ProofOfStake::activate_liquidity_for_3rdparty_rewards( + RuntimeOrigin::signed(BOB), + LIQUIDITY_TOKEN, + 100, + SECOND_REWARD_TOKEN, + Some(ThirdPartyActivationKind::ActivatedLiquidity(FIRST_REWARD_TOKEN)), + ) + .unwrap(); + + forward_to_block::(20); + + assert_eq!(TokensOf::::free_balance(FIRST_REWARD_TOKEN, &BOB), 0,); + assert_eq!(TokensOf::::free_balance(SECOND_REWARD_TOKEN, &BOB), 0,); + + assert_eq!( + ProofOfStake::calculate_3rdparty_rewards_amount( + BOB, + LIQUIDITY_TOKEN, + FIRST_REWARD_TOKEN, + ) + .unwrap(), + 1000 + ); + assert_eq!( + ProofOfStake::calculate_3rdparty_rewards_amount( + BOB, + LIQUIDITY_TOKEN, + SECOND_REWARD_TOKEN, + ) + .unwrap(), + 1000 + ); + + ProofOfStake::claim_3rdparty_rewards( + RuntimeOrigin::signed(BOB), + LIQUIDITY_TOKEN, + FIRST_REWARD_TOKEN, + ) + .unwrap(); + + assert_eq!( + ProofOfStake::calculate_3rdparty_rewards_amount( + BOB, + LIQUIDITY_TOKEN, + FIRST_REWARD_TOKEN, + ) + .unwrap(), + 0 + ); + assert_eq!( + ProofOfStake::calculate_3rdparty_rewards_amount( + BOB, + LIQUIDITY_TOKEN, + SECOND_REWARD_TOKEN, + ) + .unwrap(), + 1000 + ); + + ProofOfStake::claim_3rdparty_rewards( + RuntimeOrigin::signed(BOB), + LIQUIDITY_TOKEN, + SECOND_REWARD_TOKEN, + ) + .unwrap(); + + assert_eq!( + ProofOfStake::calculate_3rdparty_rewards_amount( + BOB, + LIQUIDITY_TOKEN, + FIRST_REWARD_TOKEN, + ) + .unwrap(), + 0 + ); + assert_eq!( + ProofOfStake::calculate_3rdparty_rewards_amount( + BOB, + LIQUIDITY_TOKEN, + SECOND_REWARD_TOKEN, + ) + .unwrap(), + 0 + ); + + assert_eq!(TokensOf::::free_balance(FIRST_REWARD_TOKEN, &BOB), 1000); + assert_eq!(TokensOf::::free_balance(SECOND_REWARD_TOKEN, &BOB), 1000); + + assert_eq!( + ProofOfStake::calculate_3rdparty_rewards_amount( + BOB, + LIQUIDITY_TOKEN, + FIRST_REWARD_TOKEN + ), + Ok(0) + ); + assert_eq!( + ProofOfStake::calculate_3rdparty_rewards_amount( + BOB, + LIQUIDITY_TOKEN, + SECOND_REWARD_TOKEN + ), + Ok(0) + ); + }); +} + +#[test] +#[serial] +fn can_not_provide_liquidity_for_schedule_rewards_when_its_only_activated_for_liq_minting() { + ExtBuilder::new() + .issue(ALICE, REWARD_TOKEN, REWARD_AMOUNT) + .issue(BOB, LIQUIDITY_TOKEN, 100) + .execute_with_default_mocks(|| { + System::set_block_number(1); + + ProofOfStake::update_pool_promotion(RuntimeOrigin::root(), LIQUIDITY_TOKEN, 1u8) + .unwrap(); + + assert_err_ignore_postinfo!( + ProofOfStake::activate_liquidity_for_3rdparty_rewards( + RuntimeOrigin::signed(BOB), + LIQUIDITY_TOKEN, + 100, + REWARD_TOKEN, + None, + ), + Error::::NotAPromotedPool + ); + }); +} + +#[test] +#[serial] +fn can_not_provide_liquidity_for_mining_rewards_when_its_only_activated_for_schedule_rewards() { + ExtBuilder::new() + .issue(ALICE, REWARD_TOKEN, REWARD_AMOUNT) + .issue(BOB, LIQUIDITY_TOKEN, 100) + .execute_with_default_mocks(|| { + System::set_block_number(1); + + ProofOfStake::reward_pool( + RuntimeOrigin::signed(ALICE), + REWARDED_PAIR, + REWARD_TOKEN, + REWARD_AMOUNT, + 10u32.into(), + ) + .unwrap(); + + assert_err!( + ProofOfStake::activate_liquidity_for_native_rewards( + RuntimeOrigin::signed(BOB), + LIQUIDITY_TOKEN, + 100, + None, + ), + Error::::NotAPromotedPool + ); + }); +} + +use frame_support::dispatch::{GetDispatchInfo, Pays}; +use sp_runtime::{traits::Dispatchable, Permill}; + +#[test] +#[serial] +fn activate_deactivate_calls_are_free_of_charge() { + ExtBuilder::new() + .issue(ALICE, REWARD_TOKEN, REWARD_AMOUNT) + .issue(BOB, LIQUIDITY_TOKEN, 100) + .execute_with_default_mocks(|| { + System::set_block_number(1); + + ProofOfStake::reward_pool( + RuntimeOrigin::signed(ALICE), + REWARDED_PAIR, + REWARD_TOKEN, + REWARD_AMOUNT, + 10u32.into(), + ) + .unwrap(); + + let activate_call = + mock::RuntimeCall::ProofOfStake(Call::activate_liquidity_for_3rdparty_rewards { + liquidity_token_id: LIQUIDITY_TOKEN, + amount: 100, + reward_token: REWARD_TOKEN, + use_balance_from: None, + }); + + let deactivate_call = + mock::RuntimeCall::ProofOfStake(Call::deactivate_liquidity_for_3rdparty_rewards { + liquidity_token_id: LIQUIDITY_TOKEN, + amount: 100, + reward_token: REWARD_TOKEN, + }); + + assert_eq!( + activate_call.dispatch(RuntimeOrigin::signed(BOB)).unwrap().pays_fee, + Pays::No + ); + + assert_eq!( + deactivate_call.dispatch(RuntimeOrigin::signed(BOB)).unwrap().pays_fee, + Pays::No + ); + }); +} + +#[test] +#[serial] +fn unsuccessul_activate_deactivate_calls_charges_fees() { + ExtBuilder::new() + .issue(ALICE, REWARD_TOKEN, REWARD_AMOUNT) + .issue(BOB, LIQUIDITY_TOKEN, 100) + .execute_with_default_mocks(|| { + System::set_block_number(1); + + let activate_call = + mock::RuntimeCall::ProofOfStake(Call::activate_liquidity_for_3rdparty_rewards { + liquidity_token_id: LIQUIDITY_TOKEN, + amount: 100, + reward_token: REWARD_TOKEN, + use_balance_from: None, + }); + + let deactivate_call = + mock::RuntimeCall::ProofOfStake(Call::deactivate_liquidity_for_3rdparty_rewards { + liquidity_token_id: LIQUIDITY_TOKEN, + amount: 100, + reward_token: REWARD_TOKEN, + }); + + assert_eq!( + activate_call + .dispatch(RuntimeOrigin::signed(BOB)) + .unwrap_err() + .post_info + .pays_fee, + Pays::Yes + ); + + assert_eq!( + deactivate_call + .dispatch(RuntimeOrigin::signed(BOB)) + .unwrap_err() + .post_info + .pays_fee, + Pays::Yes + ); + }); +} + +#[test] +#[serial] +fn claim_rewards_from_multiple_sessions_at_once() { + ExtBuilder::new() + .issue(ALICE, REWARD_TOKEN, REWARD_AMOUNT) + .issue(BOB, LIQUIDITY_TOKEN, 100) + .issue(CHARLIE, LIQUIDITY_TOKEN, 100) + .issue(EVE, LIQUIDITY_TOKEN, 100) + .execute_with_default_mocks(|| { + System::set_block_number(1); + + TokensOf::::mint(LIQUIDITY_TOKEN, &BOB, 100).unwrap(); + TokensOf::::mint(LIQUIDITY_TOKEN, &CHARLIE, 100).unwrap(); + TokensOf::::mint(LIQUIDITY_TOKEN, &EVE, 100).unwrap(); + + let amount = 10_000u128; + + ProofOfStake::reward_pool( + RuntimeOrigin::signed(ALICE), + REWARDED_PAIR, + REWARD_TOKEN, + REWARD_AMOUNT, + 10u32.into(), + ) + .unwrap(); + + roll_to_session::(1); + ProofOfStake::activate_liquidity_for_3rdparty_rewards( + RuntimeOrigin::signed(BOB), + LIQUIDITY_TOKEN, + 100, + REWARD_TOKEN, + None, + ) + .unwrap(); + assert_eq!( + ProofOfStake::calculate_3rdparty_rewards_amount(BOB, LIQUIDITY_TOKEN, REWARD_TOKEN), + Ok(0) + ); + + roll_to_session::(2); + assert_eq!( + ProofOfStake::calculate_3rdparty_rewards_amount(BOB, LIQUIDITY_TOKEN, REWARD_TOKEN), + Ok(1000) + ); + + roll_to_session::(5); + assert_eq!( + ProofOfStake::calculate_3rdparty_rewards_amount(BOB, LIQUIDITY_TOKEN, REWARD_TOKEN), + Ok(4000) + ); + }); +} + +#[test] +#[serial] +fn multi_user_rewards_distributeion_scenario() { + ExtBuilder::new() + .issue(ALICE, REWARD_TOKEN, REWARD_AMOUNT) + .issue(BOB, LIQUIDITY_TOKEN, 100) + .issue(CHARLIE, LIQUIDITY_TOKEN, 100) + .issue(EVE, LIQUIDITY_TOKEN, 100) + .execute_with_default_mocks(|| { + System::set_block_number(1); + + TokensOf::::mint(LIQUIDITY_TOKEN, &BOB, 100).unwrap(); + TokensOf::::mint(LIQUIDITY_TOKEN, &CHARLIE, 100).unwrap(); + TokensOf::::mint(LIQUIDITY_TOKEN, &EVE, 100).unwrap(); + + let amount = 10_000u128; + + ProofOfStake::reward_pool( + RuntimeOrigin::signed(ALICE), + REWARDED_PAIR, + REWARD_TOKEN, + REWARD_AMOUNT, + 10u32.into(), + ) + .unwrap(); + + roll_to_session::(1); + ProofOfStake::activate_liquidity_for_3rdparty_rewards( + RuntimeOrigin::signed(BOB), + LIQUIDITY_TOKEN, + 100, + REWARD_TOKEN, + None, + ) + .unwrap(); + assert_eq!( + ProofOfStake::calculate_3rdparty_rewards_amount(BOB, LIQUIDITY_TOKEN, REWARD_TOKEN), + Ok(0) + ); + + roll_to_session::(2); + ProofOfStake::activate_liquidity_for_3rdparty_rewards( + RuntimeOrigin::signed(CHARLIE), + LIQUIDITY_TOKEN, + 100, + REWARD_TOKEN, + None, + ) + .unwrap(); + + assert_eq!( + ProofOfStake::calculate_3rdparty_rewards_amount( + CHARLIE, + LIQUIDITY_TOKEN, + REWARD_TOKEN + ), + Ok(0) + ); + assert_eq!( + ProofOfStake::calculate_3rdparty_rewards_amount(BOB, LIQUIDITY_TOKEN, REWARD_TOKEN), + Ok(1000) + ); + + roll_to_session::(3); + assert_eq!( + ProofOfStake::calculate_3rdparty_rewards_amount(BOB, LIQUIDITY_TOKEN, REWARD_TOKEN), + Ok(1500) + ); + assert_eq!( + ProofOfStake::calculate_3rdparty_rewards_amount( + CHARLIE, + LIQUIDITY_TOKEN, + REWARD_TOKEN + ), + Ok(500) + ); + }); +} + +#[test] +#[serial] +fn test_all_scheduled_rewards_are_distributed_when_activated_instantly() { + ExtBuilder::new() + .issue(ALICE, REWARD_TOKEN, REWARD_AMOUNT) + .issue(BOB, LIQUIDITY_TOKEN, 100) + .execute_with_default_mocks(|| { + System::set_block_number(1); + + let amount = 10_000u128; + + ProofOfStake::reward_pool( + RuntimeOrigin::signed(ALICE), + REWARDED_PAIR, + REWARD_TOKEN, + REWARD_AMOUNT, + 10u32.into(), + ) + .unwrap(); + ProofOfStake::activate_liquidity_for_3rdparty_rewards( + RuntimeOrigin::signed(BOB), + LIQUIDITY_TOKEN, + 100, + REWARD_TOKEN, + None, + ) + .unwrap(); + + roll_to_session::(12); + + assert_eq!( + ProofOfStake::calculate_3rdparty_rewards_amount(BOB, LIQUIDITY_TOKEN, REWARD_TOKEN), + Ok(REWARD_AMOUNT) + ); + }); +} + +#[test] +#[serial] +fn test_all_scheduled_rewards_are_distributed_when_activated_after_few_sessions() { + ExtBuilder::new() + .issue(ALICE, REWARD_TOKEN, REWARD_AMOUNT) + .issue(BOB, LIQUIDITY_TOKEN, 100) + .execute_with_default_mocks(|| { + System::set_block_number(1); + + let amount = 10_000u128; + + ProofOfStake::reward_pool( + RuntimeOrigin::signed(ALICE), + REWARDED_PAIR, + REWARD_TOKEN, + REWARD_AMOUNT, + 10u32.into(), + ) + .unwrap(); + + roll_to_session::(7); + ProofOfStake::activate_liquidity_for_3rdparty_rewards( + RuntimeOrigin::signed(BOB), + LIQUIDITY_TOKEN, + 100, + REWARD_TOKEN, + None, + ) + .unwrap(); + + roll_to_session::(15); + + assert_eq!( + ProofOfStake::calculate_3rdparty_rewards_amount(BOB, LIQUIDITY_TOKEN, REWARD_TOKEN), + Ok(REWARD_AMOUNT) + ); + }); +} + +#[test] +#[serial] +fn test_all_scheduled_rewards_are_distributed_when_activated_schedule_is_finished() { + ExtBuilder::new() + .issue(ALICE, REWARD_TOKEN, REWARD_AMOUNT) + .issue(BOB, LIQUIDITY_TOKEN, 100) + .execute_with_default_mocks(|| { + System::set_block_number(1); + + let amount = 10_000u128; + + ProofOfStake::reward_pool( + RuntimeOrigin::signed(ALICE), + REWARDED_PAIR, + REWARD_TOKEN, + REWARD_AMOUNT, + 10u32.into(), + ) + .unwrap(); + + roll_to_session::(15); + + ProofOfStake::activate_liquidity_for_3rdparty_rewards( + RuntimeOrigin::signed(BOB), + LIQUIDITY_TOKEN, + 100, + REWARD_TOKEN, + None, + ) + .unwrap(); + + roll_to_session::(16); + + assert_eq!( + ProofOfStake::calculate_3rdparty_rewards_amount(BOB, LIQUIDITY_TOKEN, REWARD_TOKEN), + Ok(REWARD_AMOUNT) + ); + }); +} + +#[test] +#[serial] +fn test_multiple_activations_in_same_block() { + ExtBuilder::new() + .issue(ALICE, REWARD_TOKEN, REWARD_AMOUNT) + .issue(BOB, LIQUIDITY_TOKEN, 100) + .issue(CHARLIE, LIQUIDITY_TOKEN, 100) + .execute_with_default_mocks(|| { + System::set_block_number(1); + + let amount = 10_000u128; + + ProofOfStake::reward_pool( + RuntimeOrigin::signed(ALICE), + REWARDED_PAIR, + REWARD_TOKEN, + REWARD_AMOUNT, + 10u32.into(), + ) + .unwrap(); + + roll_to_session::(1); + + ProofOfStake::activate_liquidity_for_3rdparty_rewards( + RuntimeOrigin::signed(BOB), + LIQUIDITY_TOKEN, + 100, + REWARD_TOKEN, + None, + ) + .unwrap(); + + ProofOfStake::activate_liquidity_for_3rdparty_rewards( + RuntimeOrigin::signed(CHARLIE), + LIQUIDITY_TOKEN, + 100, + REWARD_TOKEN, + None, + ) + .unwrap(); + + roll_to_session::(2); + + assert_eq!( + ProofOfStake::calculate_3rdparty_rewards_amount(BOB, LIQUIDITY_TOKEN, REWARD_TOKEN), + Ok(REWARD_AMOUNT / 10 / 2) + ); + }); +} + +#[test] +#[serial] +fn rewards_are_available_in_next_session_after_rewards_are_provided() { + ExtBuilder::new() + .issue(ALICE, REWARD_TOKEN, 10 * 3 * REWARD_AMOUNT) + .issue(BOB, LIQUIDITY_TOKEN, 100) + .issue(CHARLIE, LIQUIDITY_TOKEN, 100) + .issue(EVE, LIQUIDITY_TOKEN, 100) + .execute_with_default_mocks(|| { + System::set_block_number(1); + + ProofOfStake::reward_pool( + RuntimeOrigin::signed(ALICE), + REWARDED_PAIR, + REWARD_TOKEN, + 10 * 3 * REWARD_AMOUNT, + 10u32.into(), + ) + .unwrap(); + ProofOfStake::activate_liquidity_for_3rdparty_rewards( + RuntimeOrigin::signed(BOB), + LIQUIDITY_TOKEN, + 100, + REWARD_TOKEN, + None, + ) + .unwrap(); + + assert_eq!( + 0u128, + ProofOfStake::calculate_3rdparty_rewards_amount(BOB, LIQUIDITY_TOKEN, REWARD_TOKEN) + .unwrap() + ); + assert_eq!( + 0u128, + ProofOfStake::calculate_3rdparty_rewards_amount( + CHARLIE, + LIQUIDITY_TOKEN, + REWARD_TOKEN + ) + .unwrap() + ); + assert_eq!( + 0u128, + ProofOfStake::calculate_3rdparty_rewards_amount(EVE, LIQUIDITY_TOKEN, REWARD_TOKEN) + .unwrap() + ); + + roll_to_session::(1); + + assert_eq!( + 0u128, + ProofOfStake::calculate_3rdparty_rewards_amount(BOB, LIQUIDITY_TOKEN, REWARD_TOKEN) + .unwrap() + ); + assert_eq!( + 0u128, + ProofOfStake::calculate_3rdparty_rewards_amount( + CHARLIE, + LIQUIDITY_TOKEN, + REWARD_TOKEN + ) + .unwrap() + ); + assert_eq!( + 0u128, + ProofOfStake::calculate_3rdparty_rewards_amount(EVE, LIQUIDITY_TOKEN, REWARD_TOKEN) + .unwrap() + ); + ProofOfStake::activate_liquidity_for_3rdparty_rewards( + RuntimeOrigin::signed(CHARLIE), + LIQUIDITY_TOKEN, + 100, + REWARD_TOKEN, + None, + ) + .unwrap(); + assert_eq!( + 0u128, + ProofOfStake::calculate_3rdparty_rewards_amount(BOB, LIQUIDITY_TOKEN, REWARD_TOKEN) + .unwrap() + ); + assert_eq!( + 0u128, + ProofOfStake::calculate_3rdparty_rewards_amount( + CHARLIE, + LIQUIDITY_TOKEN, + REWARD_TOKEN + ) + .unwrap() + ); + assert_eq!( + 0u128, + ProofOfStake::calculate_3rdparty_rewards_amount(EVE, LIQUIDITY_TOKEN, REWARD_TOKEN) + .unwrap() + ); + + roll_to_session::(2); + + assert_eq!( + 15_000u128, + ProofOfStake::calculate_3rdparty_rewards_amount(BOB, LIQUIDITY_TOKEN, REWARD_TOKEN) + .unwrap() + ); + assert_eq!( + 15_000u128, + ProofOfStake::calculate_3rdparty_rewards_amount( + CHARLIE, + LIQUIDITY_TOKEN, + REWARD_TOKEN + ) + .unwrap() + ); + assert_eq!( + 0u128, + ProofOfStake::calculate_3rdparty_rewards_amount(EVE, LIQUIDITY_TOKEN, REWARD_TOKEN) + .unwrap() + ); + ProofOfStake::activate_liquidity_for_3rdparty_rewards( + RuntimeOrigin::signed(EVE), + LIQUIDITY_TOKEN, + 100, + REWARD_TOKEN, + None, + ) + .unwrap(); + assert_eq!( + 15_000u128, + ProofOfStake::calculate_3rdparty_rewards_amount(BOB, LIQUIDITY_TOKEN, REWARD_TOKEN) + .unwrap() + ); + assert_eq!( + 15_000u128, + ProofOfStake::calculate_3rdparty_rewards_amount( + CHARLIE, + LIQUIDITY_TOKEN, + REWARD_TOKEN + ) + .unwrap() + ); + assert_eq!( + 0u128, + ProofOfStake::calculate_3rdparty_rewards_amount(EVE, LIQUIDITY_TOKEN, REWARD_TOKEN) + .unwrap() + ); + + roll_to_session::(3); + + assert_eq!( + 15_000u128 + 10_000u128, + ProofOfStake::calculate_3rdparty_rewards_amount(BOB, LIQUIDITY_TOKEN, REWARD_TOKEN) + .unwrap() + ); + assert_eq!( + 15_000u128 + 10_000u128, + ProofOfStake::calculate_3rdparty_rewards_amount( + CHARLIE, + LIQUIDITY_TOKEN, + REWARD_TOKEN + ) + .unwrap() + ); + assert_eq!( + 10_000u128, + ProofOfStake::calculate_3rdparty_rewards_amount(EVE, LIQUIDITY_TOKEN, REWARD_TOKEN) + .unwrap() + ); + }); +} + +#[test] +#[serial] +fn multiple_activations_and_deactivations_from_multiple_users_on_the_same_schedule() { + ExtBuilder::new() + .issue(ALICE, REWARD_TOKEN, 10 * 3 * REWARD_AMOUNT) + .issue(ALICE, LIQUIDITY_TOKEN, 100) + .issue(BOB, LIQUIDITY_TOKEN, 100) + .issue(CHARLIE, LIQUIDITY_TOKEN, 100) + .issue(EVE, LIQUIDITY_TOKEN, 100) + .execute_with_default_mocks(|| { + System::set_block_number(1); + + /// 1000 rewards per session + ProofOfStake::reward_pool( + RuntimeOrigin::signed(ALICE), + REWARDED_PAIR, + REWARD_TOKEN, + REWARD_AMOUNT, + 10u32.into(), + ) + .unwrap(); + + ProofOfStake::activate_liquidity_for_3rdparty_rewards( + RuntimeOrigin::signed(ALICE), + LIQUIDITY_TOKEN, + 100, + REWARD_TOKEN, + None, + ) + .unwrap(); + ProofOfStake::activate_liquidity_for_3rdparty_rewards( + RuntimeOrigin::signed(BOB), + LIQUIDITY_TOKEN, + 100, + REWARD_TOKEN, + None, + ) + .unwrap(); + + roll_to_session::(2); + + assert_eq!( + 500u128, + ProofOfStake::calculate_3rdparty_rewards_amount( + ALICE, + LIQUIDITY_TOKEN, + REWARD_TOKEN + ) + .unwrap() + ); + assert_eq!( + 500u128, + ProofOfStake::calculate_3rdparty_rewards_amount(BOB, LIQUIDITY_TOKEN, REWARD_TOKEN) + .unwrap() + ); + assert_eq!( + 0u128, + ProofOfStake::calculate_3rdparty_rewards_amount( + CHARLIE, + LIQUIDITY_TOKEN, + REWARD_TOKEN + ) + .unwrap() + ); + assert_eq!( + 0u128, + ProofOfStake::calculate_3rdparty_rewards_amount(EVE, LIQUIDITY_TOKEN, REWARD_TOKEN) + .unwrap() + ); + + ProofOfStake::activate_liquidity_for_3rdparty_rewards( + RuntimeOrigin::signed(CHARLIE), + LIQUIDITY_TOKEN, + 50, + REWARD_TOKEN, + None, + ) + .unwrap(); + ProofOfStake::activate_liquidity_for_3rdparty_rewards( + RuntimeOrigin::signed(EVE), + LIQUIDITY_TOKEN, + 100, + REWARD_TOKEN, + None, + ) + .unwrap(); + ProofOfStake::deactivate_liquidity_for_3rdparty_rewards( + RuntimeOrigin::signed(ALICE), + LIQUIDITY_TOKEN, + 50, + REWARD_TOKEN, + ) + .unwrap(); + ProofOfStake::deactivate_liquidity_for_3rdparty_rewards( + RuntimeOrigin::signed(BOB), + LIQUIDITY_TOKEN, + 100, + REWARD_TOKEN, + ) + .unwrap(); + ProofOfStake::deactivate_liquidity_for_3rdparty_rewards( + RuntimeOrigin::signed(EVE), + LIQUIDITY_TOKEN, + 100, + REWARD_TOKEN, + ) + .unwrap(); + + assert_eq!( + 500u128, + ProofOfStake::calculate_3rdparty_rewards_amount( + ALICE, + LIQUIDITY_TOKEN, + REWARD_TOKEN + ) + .unwrap() + ); + assert_eq!( + 500u128, + ProofOfStake::calculate_3rdparty_rewards_amount(BOB, LIQUIDITY_TOKEN, REWARD_TOKEN) + .unwrap() + ); + assert_eq!( + 0u128, + ProofOfStake::calculate_3rdparty_rewards_amount( + CHARLIE, + LIQUIDITY_TOKEN, + REWARD_TOKEN + ) + .unwrap() + ); + assert_eq!( + 0u128, + ProofOfStake::calculate_3rdparty_rewards_amount(EVE, LIQUIDITY_TOKEN, REWARD_TOKEN) + .unwrap() + ); + + roll_to_session::(3); + + assert_eq!( + 1000u128, + ProofOfStake::calculate_3rdparty_rewards_amount( + ALICE, + LIQUIDITY_TOKEN, + REWARD_TOKEN + ) + .unwrap() + ); + assert_eq!( + 500u128, + ProofOfStake::calculate_3rdparty_rewards_amount(BOB, LIQUIDITY_TOKEN, REWARD_TOKEN) + .unwrap() + ); + assert_eq!( + 500u128, + ProofOfStake::calculate_3rdparty_rewards_amount( + CHARLIE, + LIQUIDITY_TOKEN, + REWARD_TOKEN + ) + .unwrap() + ); + assert_eq!( + 0u128, + ProofOfStake::calculate_3rdparty_rewards_amount(EVE, LIQUIDITY_TOKEN, REWARD_TOKEN) + .unwrap() + ); + + ProofOfStake::deactivate_liquidity_for_3rdparty_rewards( + RuntimeOrigin::signed(ALICE), + LIQUIDITY_TOKEN, + 50, + REWARD_TOKEN, + ) + .unwrap(); + ProofOfStake::activate_liquidity_for_3rdparty_rewards( + RuntimeOrigin::signed(ALICE), + LIQUIDITY_TOKEN, + 100, + REWARD_TOKEN, + None, + ) + .unwrap(); + ProofOfStake::activate_liquidity_for_3rdparty_rewards( + RuntimeOrigin::signed(BOB), + LIQUIDITY_TOKEN, + 100, + REWARD_TOKEN, + None, + ) + .unwrap(); + ProofOfStake::activate_liquidity_for_3rdparty_rewards( + RuntimeOrigin::signed(CHARLIE), + LIQUIDITY_TOKEN, + 50, + REWARD_TOKEN, + None, + ) + .unwrap(); + ProofOfStake::activate_liquidity_for_3rdparty_rewards( + RuntimeOrigin::signed(EVE), + LIQUIDITY_TOKEN, + 100, + REWARD_TOKEN, + None, + ) + .unwrap(); + + assert_eq!( + 1000u128, + ProofOfStake::calculate_3rdparty_rewards_amount( + ALICE, + LIQUIDITY_TOKEN, + REWARD_TOKEN + ) + .unwrap() + ); + assert_eq!( + 500u128, + ProofOfStake::calculate_3rdparty_rewards_amount(BOB, LIQUIDITY_TOKEN, REWARD_TOKEN) + .unwrap() + ); + assert_eq!( + 500u128, + ProofOfStake::calculate_3rdparty_rewards_amount( + CHARLIE, + LIQUIDITY_TOKEN, + REWARD_TOKEN + ) + .unwrap() + ); + assert_eq!( + 0u128, + ProofOfStake::calculate_3rdparty_rewards_amount(EVE, LIQUIDITY_TOKEN, REWARD_TOKEN) + .unwrap() + ); + + roll_to_session::(4); + + assert_eq!( + 1249u128, + ProofOfStake::calculate_3rdparty_rewards_amount( + ALICE, + LIQUIDITY_TOKEN, + REWARD_TOKEN + ) + .unwrap() + ); + assert_eq!( + 749u128, + ProofOfStake::calculate_3rdparty_rewards_amount(BOB, LIQUIDITY_TOKEN, REWARD_TOKEN) + .unwrap() + ); + assert_eq!( + 749u128, + ProofOfStake::calculate_3rdparty_rewards_amount( + CHARLIE, + LIQUIDITY_TOKEN, + REWARD_TOKEN + ) + .unwrap() + ); + assert_eq!( + 249u128, + ProofOfStake::calculate_3rdparty_rewards_amount(EVE, LIQUIDITY_TOKEN, REWARD_TOKEN) + .unwrap() + ); + }); +} + +#[test] +#[serial] +fn activity_for_schedule_rewards_can_be_activated_only_after_pool_is_rewarded_for_the_first_time() { + ExtBuilder::new() + .issue(ALICE, REWARD_TOKEN, 10 * 3 * REWARD_AMOUNT) + .issue(BOB, LIQUIDITY_TOKEN, 100) + .execute_with_default_mocks(|| { + System::set_block_number(1); + + assert_err_ignore_postinfo!( + ProofOfStake::activate_liquidity_for_3rdparty_rewards( + RuntimeOrigin::signed(BOB), + LIQUIDITY_TOKEN, + 100, + REWARD_TOKEN, + None, + ), + Error::::NotAPromotedPool + ); + + ProofOfStake::update_pool_promotion(RuntimeOrigin::root(), LIQUIDITY_TOKEN, 2u8) + .unwrap(); + + assert_err_ignore_postinfo!( + ProofOfStake::activate_liquidity_for_3rdparty_rewards( + RuntimeOrigin::signed(BOB), + LIQUIDITY_TOKEN, + 100, + REWARD_TOKEN, + None, + ), + Error::::NotAPromotedPool + ); + + ProofOfStake::reward_pool( + RuntimeOrigin::signed(ALICE), + REWARDED_PAIR, + REWARD_TOKEN, + REWARD_AMOUNT / 2, + 10u32.into(), + ) + .unwrap(); + + assert_ok!(ProofOfStake::activate_liquidity_for_3rdparty_rewards( + RuntimeOrigin::signed(BOB), + LIQUIDITY_TOKEN, + 100, + REWARD_TOKEN, + None, + )); + }); +} + +#[test] +#[serial] +fn reject_3rdparty_rewards_with_non_liq_token_and_too_small_volume() { + ExtBuilder::new() + .issue(ALICE, REWARD_TOKEN, REWARD_AMOUNT) + .build() + .execute_with(|| { + System::set_block_number(1); + let too_small_volume = Some((min_req_volume() - 1, min_req_volume() - 1)); + + let check_pool_exist_mock = MockValuationApi::check_pool_exist_context(); + check_pool_exist_mock.expect().return_const(Ok(())); + + let get_valuation_for_paired_mock = + MockValuationApi::get_valuation_for_paired_context(); + get_valuation_for_paired_mock.expect().return_const(0u128); + + let find_valuation_mock = MockValuationApi::find_valuation_context(); + find_valuation_mock.expect().return_const(Ok(10u128)); + + let get_reserve_and_lp_supply_mock = + MockValuationApi::get_reserve_and_lp_supply_context(); + get_reserve_and_lp_supply_mock.expect().return_const(too_small_volume); + + roll_to_session::(4); + + assert_err!( + ProofOfStake::reward_pool( + RuntimeOrigin::signed(ALICE), + REWARDED_PAIR, + REWARD_TOKEN, + 10, + 5u32.into() + ), + Error::::TooSmallVolume + ); + }); +} + +#[test] +#[serial] +fn accept_3rdparty_rewards_with_non_liq_token_and_proper_valuation() { + ExtBuilder::new() + .issue(ALICE, REWARD_TOKEN, REWARD_AMOUNT) + .build() + .execute_with(|| { + System::set_block_number(1); + let min_volume = Some((min_req_volume(), min_req_volume())); + + let check_pool_exist_mock = MockValuationApi::check_pool_exist_context(); + check_pool_exist_mock.expect().return_const(Ok(())); + + let get_valuation_for_paired_mock = + MockValuationApi::get_valuation_for_paired_context(); + get_valuation_for_paired_mock.expect().return_const(0u128); + + let find_valuation_mock = MockValuationApi::find_valuation_context(); + find_valuation_mock.expect().return_const(Ok(10u128)); + + let get_reserve_and_lp_supply_mock = + MockValuationApi::get_reserve_and_lp_supply_context(); + get_reserve_and_lp_supply_mock.expect().return_const(min_volume); + + roll_to_session::(4); + + assert_ok!(ProofOfStake::reward_pool( + RuntimeOrigin::signed(ALICE), + REWARDED_PAIR, + REWARD_TOKEN, + 10, + 5u32.into() + ),); + }); +} + +#[test] +#[serial] +fn reject_3rdparty_rewards_with_liq_token_and_too_small_volume() { + ExtBuilder::new() + .issue(ALICE, REWARD_TOKEN, REWARD_AMOUNT) + .build() + .execute_with(|| { + System::set_block_number(1); + let too_small_volume = Some((min_req_volume() - 1, min_req_volume() - 1)); + + let check_pool_exist_mock = MockValuationApi::check_pool_exist_context(); + check_pool_exist_mock.expect().return_const(Ok(())); + + let get_valuation_for_paired_mock = + MockValuationApi::get_valuation_for_paired_context(); + get_valuation_for_paired_mock.expect().return_const(10u128); + + let get_reserve_and_lp_supply_mock = + MockValuationApi::get_reserve_and_lp_supply_context(); + get_reserve_and_lp_supply_mock.expect().return_const(None); + + let find_paired_pool_mock = MockValuationApi::find_paired_pool_context(); + find_paired_pool_mock.expect().return_const(Ok((0, (0, 5), (9, 0u128)))); + + roll_to_session::(4); + + assert_err!( + ProofOfStake::reward_pool( + RuntimeOrigin::signed(ALICE), + REWARDED_PAIR, + REWARD_TOKEN, + 10, + 5u32.into() + ), + Error::::TooSmallVolume + ); + }); +} + +#[test] +#[serial] +fn accept_3rdparty_rewards_with_liq_token_and_min_volume() { + ExtBuilder::new() + .issue(ALICE, REWARD_TOKEN, REWARD_AMOUNT) + .build() + .execute_with(|| { + System::set_block_number(1); + + let check_pool_exist_mock = MockValuationApi::check_pool_exist_context(); + check_pool_exist_mock.expect().return_const(Ok(())); + + let get_valuation_for_paired_mock = + MockValuationApi::get_valuation_for_paired_context(); + get_valuation_for_paired_mock.expect().return_const(10u128); + + let get_reserve_and_lp_supply_mock = + MockValuationApi::get_reserve_and_lp_supply_context(); + get_reserve_and_lp_supply_mock.expect().return_const(None); + + let find_paired_pool_mock = MockValuationApi::find_paired_pool_context(); + find_paired_pool_mock + .expect() + .return_const(Ok((0, (0, 5), (min_req_volume(), 0u128)))); + + roll_to_session::(4); + + assert_ok!(ProofOfStake::reward_pool( + RuntimeOrigin::signed(ALICE), + REWARDED_PAIR, + REWARD_TOKEN, + 10, + 5u32.into() + ),); + }); +} + +#[test] +#[serial] +fn user_can_withdraw_liquidity_from_withdrown_rewards_when_its_not_used_for_liquidity_mining() { + ExtBuilder::new() + .issue(ALICE, REWARD_TOKEN, REWARD_AMOUNT) + .issue(BOB, LIQUIDITY_TOKEN, 100) + .issue(CHARLIE, LIQUIDITY_TOKEN, 100) + .execute_with_default_mocks(|| { + System::set_block_number(1); + + ProofOfStake::reward_pool( + RuntimeOrigin::signed(ALICE), + REWARDED_PAIR, + REWARD_TOKEN, + REWARD_AMOUNT, + 10u32.into(), + ) + .unwrap(); + + roll_to_session::(1); + ProofOfStake::activate_liquidity_for_3rdparty_rewards( + RuntimeOrigin::signed(BOB), + LIQUIDITY_TOKEN, + 100, + REWARD_TOKEN, + None, + ) + .unwrap(); + + ProofOfStake::activate_liquidity_for_3rdparty_rewards( + RuntimeOrigin::signed(CHARLIE), + LIQUIDITY_TOKEN, + 100, + REWARD_TOKEN, + None, + ) + .unwrap(); + + roll_to_session::(2); + + assert_eq!( + ProofOfStake::calculate_3rdparty_rewards_amount(BOB, LIQUIDITY_TOKEN, REWARD_TOKEN), + Ok(500) + ); + + assert_eq!( + ProofOfStake::calculate_3rdparty_rewards_amount( + CHARLIE, + LIQUIDITY_TOKEN, + REWARD_TOKEN + ), + Ok(500) + ); + ProofOfStake::deactivate_liquidity_for_3rdparty_rewards( + RuntimeOrigin::signed(CHARLIE), + LIQUIDITY_TOKEN, + 100, + REWARD_TOKEN, + ) + .unwrap(); + + roll_to_session::(3); + + assert_eq!( + ProofOfStake::calculate_3rdparty_rewards_amount(BOB, LIQUIDITY_TOKEN, REWARD_TOKEN), + Ok(1500) + ); + + assert_eq!( + ProofOfStake::calculate_3rdparty_rewards_amount( + CHARLIE, + LIQUIDITY_TOKEN, + REWARD_TOKEN + ), + Ok(500) + ); + }); +} + +#[test] +#[serial] +fn test_NotEnoughAssets_is_triggered_when_user_wants_to_deactive_more_tokens_that_he_owns() { + ExtBuilder::new() + .issue(ALICE, REWARD_TOKEN, REWARD_AMOUNT) + .issue(BOB, LIQUIDITY_TOKEN, 100) + .execute_with_default_mocks(|| { + System::set_block_number(1); + + ProofOfStake::reward_pool( + RuntimeOrigin::signed(ALICE), + REWARDED_PAIR, + REWARD_TOKEN, + REWARD_AMOUNT, + 10u32.into(), + ) + .unwrap(); + + roll_to_session::(1); + ProofOfStake::activate_liquidity_for_3rdparty_rewards( + RuntimeOrigin::signed(BOB), + LIQUIDITY_TOKEN, + 100, + REWARD_TOKEN, + None, + ) + .unwrap(); + + assert_err_ignore_postinfo!( + ProofOfStake::deactivate_liquidity_for_3rdparty_rewards( + RuntimeOrigin::signed(CHARLIE), + LIQUIDITY_TOKEN, + 101, + REWARD_TOKEN, + ), + Error::::NotEnoughAssets + ); + }); +} + +#[test] +#[serial] +fn test_cumulative_rewards_per_liquidity_over_multiple_sessions() { + ExtBuilder::new() + .issue(ALICE, LIQUIDITY_TOKEN, 100_000_000_000u128) + .issue(BOB, REWARD_TOKEN, 100_000_000_000_000_000_000_000_000u128) + .issue(BOB, LIQUIDITY_TOKEN, 500_000_000_000u128) + .execute_with_default_mocks(|| { + forward_to_block::(5); + ProofOfStake::reward_pool( + RuntimeOrigin::signed(BOB), + REWARDED_PAIR, + REWARD_TOKEN, + 10000u128, + 10u32.into(), + ) + .unwrap(); + + assert_eq!( + ScheduleRewardsPerLiquidity::::get((LIQUIDITY_TOKEN, REWARD_TOKEN)), + (U256::from(0), 0) + ); + + ProofOfStake::activate_liquidity_for_3rdparty_rewards( + RuntimeOrigin::signed(BOB), + LIQUIDITY_TOKEN, + 100u128, + REWARD_TOKEN, + None, + ) + .unwrap(); + + assert_eq!( + ScheduleRewardsPerLiquidity::::get((LIQUIDITY_TOKEN, REWARD_TOKEN)), + (U256::from(0), 0) + ); + + ScheduleRewardsCalculator::::update_cumulative_rewards( + LIQUIDITY_TOKEN, + REWARD_TOKEN, + ); + + assert_eq!( + ScheduleRewardsPerLiquidity::::get((LIQUIDITY_TOKEN, REWARD_TOKEN)), + (U256::from(0), 0) + ); + + forward_to_block::(9); + + ScheduleRewardsCalculator::::update_cumulative_rewards( + LIQUIDITY_TOKEN, + REWARD_TOKEN, + ); + assert_eq!( + ScheduleRewardsPerLiquidity::::get((LIQUIDITY_TOKEN, REWARD_TOKEN)), + (U256::from(0), 0) + ); + + forward_to_block::(18); + + ScheduleRewardsCalculator::::update_cumulative_rewards( + LIQUIDITY_TOKEN, + REWARD_TOKEN, + ); + assert_eq!( + ScheduleRewardsPerLiquidity::::get((LIQUIDITY_TOKEN, REWARD_TOKEN)), + (U256::from(0), 0) + ); + assert_eq!( + ProofOfStake::calculate_3rdparty_rewards_amount(BOB, LIQUIDITY_TOKEN, REWARD_TOKEN) + .unwrap(), + 0 + ); + + forward_to_block::(25); + + ScheduleRewardsCalculator::::update_cumulative_rewards( + LIQUIDITY_TOKEN, + REWARD_TOKEN, + ); + assert_eq!( + ScheduleRewardsPerLiquidity::::get((LIQUIDITY_TOKEN, REWARD_TOKEN)), + (U256::from(u128::MAX) * 1000 / 100, 1) + ); + + forward_to_block::(30); + ScheduleRewardsCalculator::::update_cumulative_rewards( + LIQUIDITY_TOKEN, + REWARD_TOKEN, + ); + + assert_eq!( + ScheduleRewardsPerLiquidity::::get((LIQUIDITY_TOKEN, REWARD_TOKEN)), + (U256::from(2) * (U256::from(u128::MAX) * 1000 / 100), 2) + ); + + assert_eq!( + ProofOfStake::calculate_3rdparty_rewards_amount(BOB, LIQUIDITY_TOKEN, REWARD_TOKEN) + .unwrap(), + 2000 + ); + + ScheduleRewardsCalculator::::update_cumulative_rewards( + LIQUIDITY_TOKEN, + REWARD_TOKEN, + ); + }); +} + +#[test] +#[serial] +fn test_amount_of_rewards_when_activation_happens_after_schedule_was_processed_in_current_session() +{ + ExtBuilder::new() + .issue(ALICE, LIQUIDITY_TOKEN, 100_000_000_000u128) + .issue(BOB, REWARD_TOKEN, 100_000_000_000_000_000_000_000_000u128) + .issue(BOB, LIQUIDITY_TOKEN, 500_000_000_000u128) + .execute_with_default_mocks(|| { + forward_to_block::(36); + ProofOfStake::reward_pool( + RuntimeOrigin::signed(BOB), + REWARDED_PAIR, + REWARD_TOKEN, + 100_000_000_000_000_000_000_000_000u128, + 7u32.into(), + ) + .unwrap(); + + forward_to_block::(40); + ProofOfStake::activate_liquidity_for_3rdparty_rewards( + RuntimeOrigin::signed(BOB), + LIQUIDITY_TOKEN, + 100_000_000_000u128, + REWARD_TOKEN, + None, + ) + .unwrap(); + + forward_to_block::(50); + ProofOfStake::activate_liquidity_for_3rdparty_rewards( + RuntimeOrigin::signed(ALICE), + LIQUIDITY_TOKEN, + 100_000_000_000u128, + REWARD_TOKEN, + None, + ) + .unwrap(); + + forward_to_block::(64); + ProofOfStake::activate_liquidity_for_3rdparty_rewards( + RuntimeOrigin::signed(BOB), + LIQUIDITY_TOKEN, + 100_000_000_000u128, + REWARD_TOKEN, + None, + ) + .unwrap(); + + roll_to_next_session::(); + roll_to_next_session::(); + roll_to_next_session::(); + roll_to_next_session::(); + roll_to_next_session::(); + + let alice_rewards = ProofOfStake::calculate_3rdparty_rewards_amount( + ALICE, + LIQUIDITY_TOKEN, + REWARD_TOKEN, + ) + .unwrap(); + let bob_rewards = + ProofOfStake::calculate_3rdparty_rewards_amount(BOB, LIQUIDITY_TOKEN, REWARD_TOKEN) + .unwrap(); + + // + 1 because of rounding + assert_eq!((alice_rewards + bob_rewards + 1), 100_000_000_000_000_000_000_000_000); + }); +} + +#[test] +#[serial] +fn test_event_is_emmited_when_pool_is_rewarded() { + ExtBuilder::new() + .issue(ALICE, LIQUIDITY_TOKEN, 100_000_000_000u128) + .issue(BOB, REWARD_TOKEN, 100_000_000_000_000_000_000_000_000u128) + .issue(BOB, LIQUIDITY_TOKEN, 500_000_000_000u128) + .execute_with_default_mocks(|| { + forward_to_block::(36); + ProofOfStake::reward_pool( + RuntimeOrigin::signed(BOB), + REWARDED_PAIR, + REWARD_TOKEN, + 100_000_000_000_000_000_000_000_000u128, + 7u32.into(), + ) + .unwrap(); + + assert_event_emitted!(Event::::ThirdPartySuccessfulPoolPromotion( + BOB, + LIQUIDITY_TOKEN, + REWARD_TOKEN, + 100_000_000_000_000_000_000_000_000u128 + )); + }); +} diff --git a/gasp-node/pallets/proof-of-stake/src/utils.rs b/gasp-node/pallets/proof-of-stake/src/utils.rs new file mode 100644 index 000000000..2844be88b --- /dev/null +++ b/gasp-node/pallets/proof-of-stake/src/utils.rs @@ -0,0 +1,78 @@ +use core::convert::TryInto; + +use crate as pos; +use frame_support::traits::Hooks; +use frame_system::{pallet_prelude::*, Config, Pallet}; +use mangata_support::traits::LiquidityMiningApi; +use pos::BalanceOf; +use sp_core::Get; +use sp_runtime::Saturating; + +pub type TokensOf = ::Currency; +pub type XykOf = ::ValuationApi; +use orml_tokens::MultiTokenCurrencyExtended; + +pub fn roll_to_next_block() +where + T: pos::Config, + T: frame_system::Config, +{ + let new_block_number = frame_system::Pallet::::block_number().saturating_add(1u32.into()); + forward_to_block::(new_block_number); +} + +pub fn roll_to_next_session() +where + T: pos::Config, + T: frame_system::Config, +{ + let current_session = pos::Pallet::::session_index(); + roll_to_session::(current_session + 1); +} + +pub fn roll_to_session(n: u32) +where + T: pos::Config, + T: frame_system::Config, +{ + while pos::Pallet::::session_index() < n { + roll_to_next_block::(); + } +} + +pub fn forward_to_block(n: BlockNumberFor) +where + T: pos::Config, + T: frame_system::Config, +{ + forward_to_block_with_custom_rewards::(n, 10000u128.try_into().unwrap_or_default()); +} + +pub fn forward_to_block_with_custom_rewards(n: BlockNumberFor, rewards: BalanceOf) +where + T: pos::Config, + T: frame_system::Config, +{ + while frame_system::Pallet::::block_number() < n { + let new_block_number = + frame_system::Pallet::::block_number().saturating_add(1u32.into()); + frame_system::Pallet::::set_block_number(new_block_number); + + frame_system::Pallet::::on_initialize(new_block_number); + pos::Pallet::::on_initialize(new_block_number); + + if pos::Pallet::::is_new_session() { + TokensOf::::mint( + pos::Pallet::::native_token_id().into(), + &::LiquidityMiningIssuanceVault::get().into(), + rewards, + ) + .unwrap(); + + pos::Pallet::::distribute_rewards(rewards); + } + + pos::Pallet::::on_finalize(new_block_number); + frame_system::Pallet::::on_finalize(new_block_number); + } +} diff --git a/gasp-node/pallets/proof-of-stake/src/weights.rs b/gasp-node/pallets/proof-of-stake/src/weights.rs new file mode 100644 index 000000000..7e015e514 --- /dev/null +++ b/gasp-node/pallets/proof-of-stake/src/weights.rs @@ -0,0 +1,126 @@ +// This file is part of Mangata. + +// Copyright (C) 2020-2022 Mangata Foundation. +// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 + +// This program 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. + +// This program 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 this program. If not, see . + +//! Autogenerated weights for pallet_proof_of_stake +//! +//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev +//! DATE: 2023-05-12, STEPS: `2`, REPEAT: 2, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! EXECUTION: Some(Wasm), WASM-EXECUTION: Compiled, CHAIN: Some("kusama-local"), DB CACHE: 1024 + +// Executed Command: +// /home/dev/mangata-node/scripts/..//target/release/mangata-node +// benchmark +// pallet +// --chain +// kusama-local +// --execution +// wasm +// --wasm-execution +// compiled +// --pallet +// pallet_proof_of_stake +// --extrinsic +// * +// --steps +// 2 +// --repeat +// 2 +// --output +// ./benchmarks/pallet_proof_of_stake_weights.rs +// --template +// ./templates/module-weight-template.hbs + +#![cfg_attr(rustfmt, rustfmt_skip)] +#![allow(unused_parens)] +#![allow(unused_imports)] +#![allow(clippy::unnecessary_cast)] + +use frame_support::{traits::Get, weights::{Weight, constants::RocksDbWeight}}; +use sp_std::marker::PhantomData; + +/// Weight functions needed for pallet_proof_of_stake. +pub trait WeightInfo { + fn claim_native_rewards() -> Weight; + fn claim_3rdparty_rewards() -> Weight; + fn update_pool_promotion() -> Weight; + fn activate_liquidity_for_native_rewards() -> Weight; + fn deactivate_liquidity_for_native_rewards() -> Weight; + fn deactivate_liquidity_for_3rdparty_rewards() -> Weight; + fn activate_liquidity_for_3rdparty_rewards() -> Weight; + fn reward_pool() -> Weight; +} + +// For backwards compatibility and tests +impl WeightInfo for () { + // Storage: ProofOfStake PromotedPoolRewards (r:1 w:1) + fn update_pool_promotion() -> Weight { + (Weight::from_parts(52_090_000, 0)) + .saturating_add(RocksDbWeight::get().reads(1 as u64)) + .saturating_add(RocksDbWeight::get().writes(1 as u64)) + } + // Storage: ProofOfStake PromotedPoolRewards (r:1 w:0) + // Storage: MultiPurposeLiquidity ReserveStatus (r:1 w:1) + // Storage: Tokens Accounts (r:1 w:1) + // Storage: ProofOfStake RewardsInfo (r:1 w:1) + // Storage: ProofOfStake TotalActivatedLiquidity (r:1 w:1) + fn activate_liquidity_for_native_rewards() -> Weight { + (Weight::from_parts(116_150_000, 0)) + .saturating_add(RocksDbWeight::get().reads(5 as u64)) + .saturating_add(RocksDbWeight::get().writes(4 as u64)) + } + // Storage: ProofOfStake PromotedPoolRewards (r:1 w:0) + // Storage: ProofOfStake RewardsInfo (r:1 w:1) + // Storage: ProofOfStake TotalActivatedLiquidity (r:1 w:1) + // Storage: MultiPurposeLiquidity ReserveStatus (r:1 w:1) + // Storage: Tokens Accounts (r:1 w:1) + fn deactivate_liquidity_for_native_rewards() -> Weight{ + (Weight::from_parts(118_250_000, 0)) + .saturating_add(RocksDbWeight::get().reads(5 as u64)) + .saturating_add(RocksDbWeight::get().writes(4 as u64)) + } + + fn deactivate_liquidity_for_3rdparty_rewards() -> Weight { + (Weight::from_parts(118_250_000, 0)) + .saturating_add(RocksDbWeight::get().reads(5 as u64)) + .saturating_add(RocksDbWeight::get().writes(4 as u64)) + } + + fn activate_liquidity_for_3rdparty_rewards() -> Weight { + (Weight::from_parts(118_250_000, 0)) + .saturating_add(RocksDbWeight::get().reads(5 as u64)) + .saturating_add(RocksDbWeight::get().writes(4 as u64)) + } + + fn reward_pool() -> Weight { + (Weight::from_parts(118_250_000, 0)) + .saturating_add(RocksDbWeight::get().reads(5 as u64)) + .saturating_add(RocksDbWeight::get().writes(4 as u64)) + } + + fn claim_3rdparty_rewards() -> Weight { + (Weight::from_parts(118_250_000, 0)) + .saturating_add(RocksDbWeight::get().reads(5 as u64)) + .saturating_add(RocksDbWeight::get().writes(4 as u64)) + } + + fn claim_native_rewards() -> Weight { + (Weight::from_parts(118_250_000, 0)) + .saturating_add(RocksDbWeight::get().reads(5 as u64)) + .saturating_add(RocksDbWeight::get().writes(4 as u64)) + } +} diff --git a/gasp-node/pallets/rolldown/Cargo.toml b/gasp-node/pallets/rolldown/Cargo.toml new file mode 100644 index 000000000..92251a16e --- /dev/null +++ b/gasp-node/pallets/rolldown/Cargo.toml @@ -0,0 +1,72 @@ +[package] +authors = ["Mangata team"] +edition = "2018" +name = "pallet-rolldown" +version = "0.1.0" + +[package.metadata.docs.rs] +targets = ["x86_64-unknown-linux-gnu"] + +[dependencies] +alloy-sol-types = { workspace = true, default-features = false } +alloy-primitives = { workspace = true, default-features = false } +codec = { workspace = true, default-features = false } +hex = { workspace = true, default-features = false } +hex-literal = { workspace = true, default-features = false } +log = { workspace = true, default-features = false } +scale-info = { workspace = true, default-features = false, features = ["derive"] } +serde = { workspace = true, default-features = false, features = ["derive"] } +serde_json = { workspace = true, default-features = false } +array-bytes.workspace = true +itertools = { workspace = true, default-features = false } +rs_merkle = { version = "1.4.2", default-features = false } + +frame-benchmarking = { workspace = true, default-features = false } +frame-support = { workspace = true, default-features = false } +frame-system = { workspace = true, default-features = false } +mangata-support = { workspace = true, default-features = false } +mangata-types = { workspace = true, default-features = false } +sp-core = { workspace = true, default-features = false } +sp-runtime = { workspace = true, default-features = false } +sp-std = { workspace = true, default-features = false } +sp-crypto-hashing = { workspace = true, default-features = false } + +orml-tokens = { workspace = true, default-features = false } + +pallet-sequencer-staking = { path = "../sequencer-staking", default-features = false } + +[dev-dependencies] +env_logger.workspace = true +lazy_static.workspace = true +serial_test.workspace = true +mockall.workspace = true + +sp-io = { workspace = true, default-features = false } +orml-traits = { workspace = true, default-features = false } + + +[features] +default = ["std"] +enable-trading = [] +std = [ + "alloy-sol-types/std", + "alloy-primitives/std", + "codec/std", + "frame-benchmarking/std", + "frame-support/std", + "frame-system/std", + "hex/std", + "mangata-support/std", + "orml-tokens/std", + "rs_merkle/std", + "serde/std", + "serde_json/std", + "sp-std/std", + "sp-core/std", + "sp-runtime/std", + "pallet-sequencer-staking/std", +] + +runtime-benchmarks = ["frame-benchmarking/runtime-benchmarks", "pallet-sequencer-staking/runtime-benchmarks"] + +try-runtime = ["frame-support/try-runtime", "frame-system/try-runtime", "orml-tokens/try-runtime", "sp-runtime/try-runtime"] diff --git a/gasp-node/pallets/rolldown/rpc/Cargo.toml b/gasp-node/pallets/rolldown/rpc/Cargo.toml new file mode 100644 index 000000000..85f8e4c4f --- /dev/null +++ b/gasp-node/pallets/rolldown/rpc/Cargo.toml @@ -0,0 +1,41 @@ +[package] +authors = ['Mangata team'] +name = "rolldown-rpc" +version = "2.0.0" +edition = "2018" +description = "RPC calls for Proof of Stake" +license = "GPL-3.0-or-later" + +[dependencies] +codec = { workspace = true } +jsonrpsee = { workspace = true, features = ["server", "client", "macros"] } +serde = { workspace = true, features = ["derive"], optional = true } + +# Substrate packages + +sp-api = { workspace = true, default-features = false } +sp-blockchain = { workspace = true, default-features = false } +sp-rpc = { workspace = true, default-features = false } +sp-core = { workspace = true, default-features = false } +sp-std = { workspace = true, default-features = false } +sp-runtime = { workspace = true, default-features = false } +mangata-types = { workspace = true, default-features = false } +array-bytes = { workspace = true } + +# local packages + +rolldown-runtime-api = { version = "2.0.0", path = "../runtime-api", default-features = false } + +[features] +default = ["std"] + +std = [ + "serde", + "sp-api/std", + "sp-core/std", + "sp-std/std", + "sp-runtime/std", + "rolldown-runtime-api/std", + "mangata-types/std", + "codec/std", +] diff --git a/gasp-node/pallets/rolldown/rpc/src/lib.rs b/gasp-node/pallets/rolldown/rpc/src/lib.rs new file mode 100644 index 000000000..94078b37a --- /dev/null +++ b/gasp-node/pallets/rolldown/rpc/src/lib.rs @@ -0,0 +1,201 @@ +// Copyright (C) 2021 Mangata team +use jsonrpsee::{ + core::{async_trait, RpcResult}, + proc_macros::rpc, + types::error::ErrorObject, +}; + +use array_bytes::hex2bytes; +use codec::{Decode, Encode}; +use rolldown_runtime_api::RolldownRuntimeApi; +pub use sp_api::ProvideRuntimeApi; +use sp_blockchain::HeaderBackend; +use sp_core::H256; +use sp_runtime::traits::Block as BlockT; +use std::sync::Arc; + +#[rpc(client, server)] +pub trait RolldownApi { + /// Calculates amount of available native rewards + /// + /// * `account` - user account address + /// * `liquidity_token` - liquidity token id + /// * `at` - optional block hash + + #[method(name = "rolldown_get_abi_encoded_l2_request")] + fn get_abi_encoded_l2_request( + &self, + chain: Chain, + request_id: u128, + at: Option, + ) -> RpcResult>; + + #[method(name = "rolldown_get_native_sequencer_update")] + fn get_native_sequencer_update( + &self, + hex_payload: String, + at: Option, + ) -> RpcResult>; + + #[method(name = "rolldown_verify_sequencer_update")] + fn verify_sequencer_update( + &self, + chain: Chain, + hash: H256, + request_id: u128, + at: Option, + ) -> RpcResult; + + #[method(name = "rolldown_get_merkle_root")] + fn get_merkle_root( + &self, + chain: Chain, + range: (u128, u128), + at: Option, + ) -> RpcResult; + + #[method(name = "rolldown_get_merkle_proof")] + fn get_merkle_proof( + &self, + chain: Chain, + range: (u128, u128), + tx_id: u128, + at: Option, + ) -> RpcResult>; + + #[method(name = "rolldown_verify_merkle_proof")] + fn verify_merkle_proof( + &self, + chain: Chain, + range: (u128, u128), + tx_id: u128, + root: H256, + proof: Vec, + at: Option, + ) -> RpcResult; +} + +pub struct Rolldown { + client: Arc, + _marker: std::marker::PhantomData, +} + +impl Rolldown { + pub fn new(client: Arc) -> Self { + Self { client, _marker: Default::default() } + } +} + +#[async_trait] +impl RolldownApiServer<::Hash, L1Update, Chain> + for Rolldown +where + Block: BlockT, + L1Update: Decode, + Chain: Encode, + C: Send + Sync + 'static, + C: ProvideRuntimeApi, + C: HeaderBackend, + C::Api: RolldownRuntimeApi, +{ + fn get_native_sequencer_update( + &self, + hex_payload: String, + at: Option<::Hash>, + ) -> RpcResult> { + let api = self.client.runtime_api(); + let at = at.unwrap_or(self.client.info().best_hash); + + let payload = hex2bytes(hex_payload).map_err(|e| { + ErrorObject::owned(0, "Unable to serve the request", Some(format!("{:?}", e))) + })?; + + api.get_native_sequencer_update(at, payload).map_err(|e| { + ErrorObject::owned(1, "Unable to serve the request", Some(format!("{:?}", e))) + }) + } + + fn verify_sequencer_update( + &self, + chain: Chain, + hash: H256, + request_id: u128, + at: Option<::Hash>, + ) -> RpcResult { + let api = self.client.runtime_api(); + let at = at.unwrap_or(self.client.info().best_hash); + api.verify_sequencer_update(at, chain, hash, request_id) + .map_err(|e| { + ErrorObject::owned(1, "Unable to serve the request", Some(format!("{:?}", e))) + }) + .and_then(|e| match e { + Some(result) => Ok(result), + None => Err(ErrorObject::owned( + 1, + "Unable to serve the request", + Some("Request does not exist".to_string()), + )), + }) + } + + fn get_merkle_root( + &self, + chain: Chain, + range: (u128, u128), + at: Option<::Hash>, + ) -> RpcResult { + let api = self.client.runtime_api(); + let at = at.unwrap_or(self.client.info().best_hash); + + api.get_merkle_root(at, chain, range).map_err(|e| { + ErrorObject::owned(1, "Unable to serve the request", Some(format!("{:?}", e))) + }) + } + + fn get_merkle_proof( + &self, + chain: Chain, + range: (u128, u128), + tx_id: u128, + at: Option<::Hash>, + ) -> RpcResult> { + let api = self.client.runtime_api(); + let at = at.unwrap_or(self.client.info().best_hash); + + api.get_merkle_proof_for_tx(at, chain, range, tx_id).map_err(|e| { + ErrorObject::owned(1, "Unable to serve the request", Some(format!("{:?}", e))) + }) + } + + fn verify_merkle_proof( + &self, + chain: Chain, + range: (u128, u128), + tx_id: u128, + root: H256, + proof: Vec, + at: Option<::Hash>, + ) -> RpcResult { + let api = self.client.runtime_api(); + let at = at.unwrap_or(self.client.info().best_hash); + + api.verify_merkle_proof_for_tx(at, chain, range, tx_id, root, proof) + .map_err(|e| { + ErrorObject::owned(1, "Unable to serve the request", Some(format!("{:?}", e))) + }) + } + + fn get_abi_encoded_l2_request( + &self, + chain: Chain, + request_id: u128, + at: Option<::Hash>, + ) -> RpcResult> { + let api = self.client.runtime_api(); + let at = at.unwrap_or(self.client.info().best_hash); + + api.get_abi_encoded_l2_request(at, chain, request_id).map_err(|e| { + ErrorObject::owned(1, "Unable to serve the request", Some(format!("{:?}", e))) + }) + } +} diff --git a/gasp-node/pallets/rolldown/runtime-api/Cargo.toml b/gasp-node/pallets/rolldown/runtime-api/Cargo.toml new file mode 100644 index 000000000..2b1aebeac --- /dev/null +++ b/gasp-node/pallets/rolldown/runtime-api/Cargo.toml @@ -0,0 +1,21 @@ +[package] +authors = ['Mangata team'] +name = "rolldown-runtime-api" +version = "2.0.0" +edition = "2018" +license = "GPL-3.0-or-later" + +[dependencies] + +sp-api = { workspace = true, default-features = false } +sp-std = { workspace = true, default-features = false } +sp-core = { workspace = true, default-features = false } + +[features] +default = ["std"] + +std = [ + "sp-api/std", + "sp-std/std", + "sp-core/std", +] diff --git a/gasp-node/pallets/rolldown/runtime-api/src/lib.rs b/gasp-node/pallets/rolldown/runtime-api/src/lib.rs new file mode 100644 index 000000000..af8eeea3a --- /dev/null +++ b/gasp-node/pallets/rolldown/runtime-api/src/lib.rs @@ -0,0 +1,22 @@ +// Copyright (C) 2021 Mangata team +#![cfg_attr(not(feature = "std"), no_std)] + +use sp_core::{Decode, Encode, H256}; +use sp_std::vec::Vec; +sp_api::decl_runtime_apis! { + pub trait RolldownRuntimeApi where + L1Update: Decode, + Chain: Encode + { + fn get_abi_encoded_l2_request(chain: Chain, requestId: u128) -> Vec; + fn get_native_sequencer_update(hex_payload: Vec) -> Option; + fn verify_sequencer_update(chain: Chain, hash: H256, request_id: u128) -> Option; + fn get_last_processed_request_on_l2(chain: Chain) -> Option; + fn get_number_of_pending_requests(chain: Chain) -> Option; + fn get_total_number_of_deposits() -> u128; + fn get_total_number_of_withdrawals() -> u128; + fn get_merkle_root(chain: Chain, range : (u128, u128)) -> H256; + fn get_merkle_proof_for_tx(chain: Chain, range : (u128, u128), tx_id: u128) -> Vec; + fn verify_merkle_proof_for_tx(chain: Chain, range: (u128, u128), tx_id: u128, root: H256, proof: Vec) -> bool; + } +} diff --git a/gasp-node/pallets/rolldown/src/benchmarking.rs b/gasp-node/pallets/rolldown/src/benchmarking.rs new file mode 100644 index 000000000..96f801bfd --- /dev/null +++ b/gasp-node/pallets/rolldown/src/benchmarking.rs @@ -0,0 +1,1062 @@ +//! Rolldown pallet benchmarking. + +use super::*; +use crate::{messages::L1UpdateRequest, Pallet as Rolldown}; +use frame_benchmarking::{v2::*, whitelisted_caller}; +use frame_support::{ + assert_ok, + dispatch::{DispatchErrorWithPostInfo, DispatchResultWithPostInfo}, + traits::OriginTrait, +}; +use frame_system::RawOrigin; +use hex_literal::hex; +use sp_core::{Get, H256}; +use sp_std::{marker::PhantomData, prelude::*}; + +// TODO +// Dedup this +pub(crate) struct L1UpdateBuilder(Option, Vec); + +impl L1UpdateBuilder { + pub fn new() -> Self { + Self(None, Default::default()) + } + + pub fn with_offset(mut self, offset: u128) -> Self { + self.0 = Some(offset); + self + } + + pub fn with_requests(mut self, requests: Vec) -> Self { + self.1 = requests; + self + } + + pub fn build(self) -> messages::L1Update { + let mut update = messages::L1Update::default(); + + for (id, r) in self.1.into_iter().enumerate() { + let rid = if let Some(offset) = self.0 { (id as u128) + offset } else { r.id() }; + match r { + L1UpdateRequest::Deposit(mut d) => { + d.requestId.id = rid; + update.pendingDeposits.push(d); + }, + L1UpdateRequest::CancelResolution(mut c) => { + c.requestId.id = rid; + update.pendingCancelResolutions.push(c); + }, + } + } + update + } +} + +impl Default for L1UpdateBuilder { + fn default() -> Self { + Self(Some(1u128), Default::default()) + } +} + +#[benchmarks(where T::AccountId: From<[u8;20]>, BalanceOf: From, CurrencyIdOf: From, ::ChainId: From)] +mod benchmarks { + use super::*; + + const MANUAL_BATCH_EXTRA_FEE: u128 = 700u128; + + const TOKEN_ID: u32 = 1u32; // ETH token + + const WITHDRAWAL_AMOUNT: u128 = 1000u128; + const MINT_AMOUNT: u128 = 1_000_000_000__000_000_000_000_000_000u128; + const FERRY_TIP_NONE: Option = None; + const FERRY_TIP: u128 = 101u128; + const STAKE_AMOUNT: u128 = 1_000_000__000_000_000_000_000_000u128; // If SlashFineAmount in seq staking is increased this may also need to be + const DEPOSIT_AMOUNT: u128 = 1_000u128; + + const ETH_RECIPIENT_ACCOUNT: [u8; 20] = hex!("0000000000000000000000000000000000000004"); + const DUMMY_TOKEN_ADDRESS: [u8; 20] = hex!("0000000000000000000000000000000000000030"); + + const FERRY_ACCOUNT: [u8; 20] = hex!("0000000000000000000000000000000000000040"); + const USER_ACCOUNT: [u8; 20] = hex!("0000000000000000000000000000000000000005"); + const SEQUENCER_ACCOUNT: [u8; 20] = hex!("0000000000000000000000000000000000000010"); + const SEQUENCER_ACCOUNT_2: [u8; 20] = hex!("0000000000000000000000000000000000000011"); + const SEQUENCER_ALIAS_ACCOUNT: [u8; 20] = hex!("0000000000000000000000000000000000000020"); + + const FIRST_SCHEDULED_UPDATE_ID: u128 = 1u128; + const FIRST_BATCH_ID: u128 = 1u128; + const FIRST_REQUEST_ID: u128 = 1u128; + const DUMMY_REQUEST_ID: u128 = 77u128; + + trait ToBalance { + fn to_balance(self) -> BalanceOf; + } + + impl ToBalance for u128 { + fn to_balance(self) -> BalanceOf { + self.try_into().ok().expect("u128 should fit into Balance type") + } + } + + fn setup_whitelisted_account() -> Result { + let caller: T::AccountId = whitelisted_caller(); + T::Tokens::mint(T::NativeCurrencyId::get(), &caller, MINT_AMOUNT.to_balance::())?; + Ok(caller) + } + + fn setup_account(who: T::AccountId) -> DispatchResultWithPostInfo + where + T::AccountId: From<[u8; 20]>, + { + T::Tokens::mint(T::NativeCurrencyId::get(), &who, MINT_AMOUNT.to_balance::())?; + T::Tokens::mint(TOKEN_ID.into(), &who, MINT_AMOUNT.to_balance::())?; + Ok(().into()) + } + + fn setup_and_do_withdrawal(who: T::AccountId) -> DispatchResultWithPostInfo + where + T::AccountId: From<[u8; 20]>, + { + T::Tokens::mint(TOKEN_ID.into(), &who, MINT_AMOUNT.to_balance::())?; + let (l1_aset_chain, l1_asset_address) = + match T::AssetRegistryProvider::get_asset_l1_id(TOKEN_ID.into()) + .ok_or(Error::::L1AssetNotFound)? + { + L1Asset::Ethereum(v) => (crate::messages::Chain::Ethereum, v), + L1Asset::Arbitrum(v) => (crate::messages::Chain::Arbitrum, v), + L1Asset::Base(v) => (crate::messages::Chain::Base, v), + }; + Rolldown::::withdraw( + RawOrigin::Signed(who).into(), + l1_aset_chain.into(), + ETH_RECIPIENT_ACCOUNT, + l1_asset_address, + WITHDRAWAL_AMOUNT, + FERRY_TIP_NONE, + )?; + Ok(().into()) + } + + fn setup_sequencer( + sequencer: T::AccountId, + sequencer_alias: Option, + should_be_active: bool, + should_be_selected: bool, + ) -> DispatchResultWithPostInfo + where + ::ChainId: From, + T::AccountId: From<[u8; 20]>, + { + T::Tokens::mint(TOKEN_ID.into(), &sequencer, MINT_AMOUNT.to_balance::())?; + T::Tokens::mint(T::NativeCurrencyId::get(), &sequencer, MINT_AMOUNT.to_balance::())?; + if let Some(ref sequencer_alias) = sequencer_alias { + T::Tokens::mint(TOKEN_ID.into(), &sequencer_alias, MINT_AMOUNT.to_balance::())?; + T::Tokens::mint( + T::NativeCurrencyId::get(), + &sequencer_alias, + MINT_AMOUNT.to_balance::(), + )?; + } + let (l1_aset_chain, l1_asset_address) = + get_chain_and_address_for_asset_id::(TOKEN_ID.into())?; + pallet_sequencer_staking::Pallet::::provide_sequencer_stake( + (RawOrigin::Root).into(), + l1_aset_chain.into(), + STAKE_AMOUNT.try_into().ok().expect("u128 should fit into Balance type"), + sequencer_alias, + pallet_sequencer_staking::StakeAction::StakeOnly, + sequencer.clone(), + )?; + + if should_be_active { + pallet_sequencer_staking::ActiveSequencers::::try_mutate(|active_sequencers| { + active_sequencers + .entry(l1_aset_chain.into()) + .or_default() + .try_push(sequencer.clone()) + .expect("ActiveSequencers push works"); + Ok::<(), DispatchError>(()) + })?; + Rolldown::::new_sequencer_active(l1_aset_chain.into(), &sequencer) + } + + if should_be_selected { + pallet_sequencer_staking::SelectedSequencer::::mutate(|selected| { + selected.insert(l1_aset_chain.into(), sequencer.into()) + }); + } + + Ok(().into()) + } + + fn get_chain_and_address_for_asset_id( + asset_id: CurrencyIdOf, + ) -> Result<(crate::messages::Chain, [u8; 20]), DispatchErrorWithPostInfo> { + let (l1_aset_chain, l1_asset_address) = + match T::AssetRegistryProvider::get_asset_l1_id(TOKEN_ID.into()) + .ok_or(Error::::L1AssetNotFound)? + { + L1Asset::Ethereum(v) => (crate::messages::Chain::Ethereum, v), + L1Asset::Arbitrum(v) => (crate::messages::Chain::Arbitrum, v), + L1Asset::Base(v) => (crate::messages::Chain::Base, v), + }; + Ok((l1_aset_chain, l1_asset_address)) + } + + #[benchmark] + fn set_manual_batch_extra_fee() -> Result<(), BenchmarkError> { + assert_eq!(ManualBatchExtraFee::::get(), BalanceOf::::zero()); + + #[extrinsic_call] + _(RawOrigin::Root, MANUAL_BATCH_EXTRA_FEE.to_balance::()); + + assert_eq!(ManualBatchExtraFee::::get(), MANUAL_BATCH_EXTRA_FEE.to_balance::()); + + Ok(()) + } + + // Note that even though r/w on L2RequestsBatchLast would count as 1, since the value is unbounded each r/w might be far more expensive than expected + #[benchmark] + fn create_batch() -> Result<(), BenchmarkError> { + setup_account::(USER_ACCOUNT.into())?; + setup_and_do_withdrawal::(USER_ACCOUNT.into())?; + setup_sequencer::( + SEQUENCER_ACCOUNT.into(), + Some(SEQUENCER_ALIAS_ACCOUNT.into()), + false, + false, + )?; + + ManualBatchExtraFee::::set(MANUAL_BATCH_EXTRA_FEE.to_balance::()); + + let (l1_aset_chain, l1_asset_address) = + get_chain_and_address_for_asset_id::(TOKEN_ID.into())?; + assert!( + L2RequestsBatch::::get::<(::ChainId, _)>(( + l1_aset_chain.into(), + FIRST_BATCH_ID + )) + .is_none(), + "BEFORE L2RequestsBatch {:?} chain {:?} batch should be uninit", + l1_aset_chain, + FIRST_BATCH_ID + ); + assert!( + L2RequestsBatchLast::::get().get(&l1_aset_chain.into()).is_none(), + "BEFORE L2RequestsBatchLast {:?} chain should be uninit", + l1_aset_chain + ); + + let acc: T::AccountId = SEQUENCER_ALIAS_ACCOUNT.into(); + whitelist_account!(acc); + #[extrinsic_call] + _( + RawOrigin::Signed(SEQUENCER_ALIAS_ACCOUNT.into()), + l1_aset_chain.into(), + Some(SEQUENCER_ACCOUNT.into()), + ); + + assert!( + L2RequestsBatch::::get::<(::ChainId, _)>(( + l1_aset_chain.into(), + FIRST_BATCH_ID + )) + .is_some(), + "AFTER L2RequestsBatch {:?} chain {:?} batch should be init", + l1_aset_chain, + FIRST_BATCH_ID + ); + assert!( + L2RequestsBatchLast::::get().get(&l1_aset_chain.into()).is_some(), + "BEFORE L2RequestsBatchLast {:?} chain should be init", + l1_aset_chain + ); + + Ok(()) + } + + // Note that even though r/w on L2RequestsBatchLast would count as 1, since the value is unbounded each r/w might be far more expensive than expected + #[benchmark] + fn force_create_batch() -> Result<(), BenchmarkError> { + setup_account::(USER_ACCOUNT.into())?; + setup_and_do_withdrawal::(USER_ACCOUNT.into())?; + setup_sequencer::( + SEQUENCER_ACCOUNT.into(), + Some(SEQUENCER_ALIAS_ACCOUNT.into()), + false, + false, + )?; + + ManualBatchExtraFee::::put(MANUAL_BATCH_EXTRA_FEE.to_balance::()); + + let (l1_aset_chain, l1_asset_address) = + get_chain_and_address_for_asset_id::(TOKEN_ID.into())?; + assert!( + L2RequestsBatch::::get::<(::ChainId, _)>(( + l1_aset_chain.into(), + FIRST_BATCH_ID + )) + .is_none(), + "BEFORE L2RequestsBatch {:?} chain {:?} batch should be uninit", + l1_aset_chain, + FIRST_BATCH_ID + ); + assert!( + L2RequestsBatchLast::::get().get(&l1_aset_chain.into()).is_none(), + "BEFORE L2RequestsBatchLast {:?} chain should be uninit", + l1_aset_chain + ); + + #[extrinsic_call] + _(RawOrigin::Root, l1_aset_chain.into(), (1u128, 1u128), SEQUENCER_ACCOUNT.into()); + + assert!( + L2RequestsBatch::::get::<(::ChainId, _)>(( + l1_aset_chain.into(), + FIRST_BATCH_ID + )) + .is_some(), + "AFTER L2RequestsBatch {:?} chain {:?} batch should be init", + l1_aset_chain, + FIRST_BATCH_ID + ); + assert!( + L2RequestsBatchLast::::get().get(&l1_aset_chain.into()).is_some(), + "BEFORE L2RequestsBatchLast {:?} chain should be init", + l1_aset_chain + ); + + Ok(()) + } + + #[benchmark] + fn update_l2_from_l1(x: Linear<2, 200>) -> Result<(), BenchmarkError> { + setup_sequencer::( + SEQUENCER_ACCOUNT.into(), + Some(SEQUENCER_ALIAS_ACCOUNT.into()), + true, + true, + )?; + let (l1_aset_chain, l1_asset_address) = + get_chain_and_address_for_asset_id::(TOKEN_ID.into())?; + let l1_chain: ::ChainId = l1_aset_chain.into(); + + let x_deposits: usize = (x as usize) / 2; + let x_cancel_resolution: usize = (x as usize) - x_deposits; + let update = L1UpdateBuilder::default() + .with_requests( + [ + vec![L1UpdateRequest::Deposit(Default::default()); x_deposits], + vec![ + L1UpdateRequest::CancelResolution(Default::default()); + x_cancel_resolution + ], + ] + .concat(), + ) + .build(); + let update_hash = update.abi_encode_hash(); + + let metadata = UpdateMetadata { + min_id: 0u128, + max_id: 0u128, + update_size: 0u128, + sequencer: SEQUENCER_ACCOUNT, + update_hash, + }; + + >::set_block_number(1u32.into()); + let dispute_period_end = >::block_number().saturated_into::() + + Rolldown::::get_dispute_period(l1_chain).unwrap(); + assert!( + PendingSequencerUpdates::::get(dispute_period_end, l1_chain).is_none(), + "BEFORE PendingSequencerUpdates {:?} dispute_period_end {:?} l1_chain should be uninit", + dispute_period_end, + l1_chain + ); + + let acc: T::AccountId = SEQUENCER_ACCOUNT.into(); + whitelist_account!(acc); + #[extrinsic_call] + _(RawOrigin::Signed(SEQUENCER_ACCOUNT.into()), update, update_hash); + + assert!( + PendingSequencerUpdates::::get(dispute_period_end, l1_chain).is_some(), + "AFTER PendingSequencerUpdates {:?} dispute_period_end {:?} l1_chain should be init", + dispute_period_end, + l1_chain + ); + Ok(()) + } + + #[benchmark] + fn update_l2_from_l1_unsafe(x: Linear<2, 200>) -> Result<(), BenchmarkError> { + setup_sequencer::( + SEQUENCER_ACCOUNT.into(), + Some(SEQUENCER_ALIAS_ACCOUNT.into()), + true, + true, + )?; + let (l1_aset_chain, l1_asset_address) = + get_chain_and_address_for_asset_id::(TOKEN_ID.into())?; + let l1_chain: ::ChainId = l1_aset_chain.into(); + + let x_deposits: usize = (x as usize) / 2; + let x_cancel_resolution: usize = (x as usize) - x_deposits; + let update = L1UpdateBuilder::default() + .with_requests( + [ + vec![L1UpdateRequest::Deposit(Default::default()); x_deposits], + vec![ + L1UpdateRequest::CancelResolution(Default::default()); + x_cancel_resolution + ], + ] + .concat(), + ) + .build(); + + >::set_block_number(1u32.into()); + let dispute_period_end = >::block_number().saturated_into::() + + Rolldown::::get_dispute_period(l1_chain).unwrap(); + assert!( + PendingSequencerUpdates::::get(dispute_period_end, l1_chain).is_none(), + "BEFORE PendingSequencerUpdates {:?} dispute_period_end {:?} l1_chain should be uninit", + dispute_period_end, + l1_chain + ); + + let acc: T::AccountId = SEQUENCER_ACCOUNT.into(); + whitelist_account!(acc); + #[extrinsic_call] + _(RawOrigin::Signed(SEQUENCER_ACCOUNT.into()), update); + + assert!( + PendingSequencerUpdates::::get(dispute_period_end, l1_chain).is_some(), + "AFTER PendingSequencerUpdates {:?} dispute_period_end {:?} l1_chain should be init", + dispute_period_end, + l1_chain + ); + Ok(()) + } + + #[benchmark] + fn force_update_l2_from_l1(x: Linear<2, 200>) -> Result<(), BenchmarkError> { + let (l1_aset_chain, l1_asset_address) = + get_chain_and_address_for_asset_id::(TOKEN_ID.into())?; + let l1_chain: ::ChainId = l1_aset_chain.into(); + + let x_deposits: usize = (x as usize) / 2; + let x_cancel_resolution: usize = (x as usize) - x_deposits; + let update = L1UpdateBuilder::default() + .with_requests( + [ + vec![L1UpdateRequest::Deposit(Default::default()); x_deposits], + vec![ + L1UpdateRequest::CancelResolution(Default::default()); + x_cancel_resolution + ], + ] + .concat(), + ) + .build(); + + assert!( + MaxAcceptedRequestIdOnl2::::get(l1_chain).is_zero(), + "BEFORE MaxAcceptedRequestIdOnl2 {:?} l1_chain should be zero", + l1_chain + ); + + #[extrinsic_call] + _(RawOrigin::Root, update); + + assert!( + !MaxAcceptedRequestIdOnl2::::get(l1_chain).is_zero(), + "AFTER MaxAcceptedRequestIdOnl2 {:?} l1_chain should NOT be zero", + l1_chain + ); + Ok(()) + } + + //FIX: check and possibly allign this benchmark + #[benchmark] + fn cancel_requests_from_l1() -> Result<(), BenchmarkError> { + setup_sequencer::(SEQUENCER_ACCOUNT.into(), None, true, false)?; + setup_sequencer::(SEQUENCER_ACCOUNT_2.into(), None, true, false)?; + let (l1_aset_chain, l1_asset_address) = + get_chain_and_address_for_asset_id::(TOKEN_ID.into())?; + let l1_chain: ::ChainId = l1_aset_chain.into(); + + let requests = vec![L1UpdateRequest::Deposit(Default::default())]; + let update = L1UpdateBuilder::default() + .with_requests(vec![L1UpdateRequest::Deposit(Default::default())]) + .build(); + + let update_hash = H256::default(); + let sequencer_account: T::AccountId = SEQUENCER_ACCOUNT.into(); + let metadata = UpdateMetadata { + min_id: 1u128, + max_id: 1u128, + update_size: 1u128, + sequencer: sequencer_account.clone(), + update_hash: H256::zero(), + }; + + PendingSequencerUpdates::::insert(DUMMY_REQUEST_ID, l1_chain, metadata); + + PendingSequencerUpdateContent::::insert(update_hash, update); + + assert!( + L2Requests::::get(l1_chain, RequestId::from((Origin::L2, FIRST_REQUEST_ID))) + .is_none(), + "BEFORE L2Requests {:?} l1_chain {:?} requestId should be none", + l1_chain, + FIRST_REQUEST_ID + ); + + let acc: T::AccountId = SEQUENCER_ACCOUNT_2.into(); + whitelist_account!(acc); + #[extrinsic_call] + _(RawOrigin::Signed(SEQUENCER_ACCOUNT_2.into()), l1_aset_chain.into(), DUMMY_REQUEST_ID); + + assert!( + L2Requests::::get(l1_chain, RequestId::from((Origin::L2, FIRST_REQUEST_ID))) + .is_some(), + "AFTER L2Requests {:?} l1_chain {:?} requestId should be some", + l1_chain, + FIRST_REQUEST_ID + ); + Ok(()) + } + + //FIX: check and possibly allign this benchmark + #[benchmark] + fn force_cancel_requests_from_l1() -> Result<(), BenchmarkError> { + setup_sequencer::(SEQUENCER_ACCOUNT.into(), None, true, true)?; + let (l1_aset_chain, l1_asset_address) = + get_chain_and_address_for_asset_id::(TOKEN_ID.into())?; + let l1_chain: ::ChainId = l1_aset_chain.into(); + + let metadata = UpdateMetadata:: { + min_id: 1u128, + max_id: 1u128, + update_size: 1u128, + sequencer: T::AddressConverter::convert(SEQUENCER_ACCOUNT), + update_hash: H256::zero(), + }; + + let sequencer_account: T::AccountId = SEQUENCER_ACCOUNT.into(); + PendingSequencerUpdates::::insert(DUMMY_REQUEST_ID, l1_chain, metadata); + + assert!( + T::SequencerStakingProvider::is_selected_sequencer(l1_chain, &SEQUENCER_ACCOUNT.into()), + "suboptimal code path" + ); + assert!( + PendingSequencerUpdates::::get(DUMMY_REQUEST_ID, l1_chain).is_some(), + "BEFORE PendingSequencerUpdates {:?} requestId {:?} l1_chain should be some", + DUMMY_REQUEST_ID, + l1_chain + ); + + #[extrinsic_call] + _(RawOrigin::Root, l1_aset_chain.into(), DUMMY_REQUEST_ID); + + assert!( + PendingSequencerUpdates::::get(DUMMY_REQUEST_ID, l1_chain).is_none(), + "AFTER PendingSequencerUpdates {:?} requestId {:?} l1_chain should be none", + DUMMY_REQUEST_ID, + l1_chain + ); + Ok(()) + } + + #[benchmark] + fn withdraw() -> Result<(), BenchmarkError> { + setup_account::(USER_ACCOUNT.into())?; + + let (l1_aset_chain, l1_asset_address) = + get_chain_and_address_for_asset_id::(TOKEN_ID.into())?; + let l1_chain: ::ChainId = l1_aset_chain.into(); + + assert!( + !<::WithdrawFee>::convert(l1_chain).is_zero(), + "withdraw fee should not be zero, suboptimal code path" + ); + assert!( + L2Requests::::get(l1_chain, RequestId::from((Origin::L2, FIRST_REQUEST_ID))) + .is_none(), + "BEFORE L2Requests {:?} l1_chain {:?} requestId should be none", + l1_chain, + DUMMY_REQUEST_ID + ); + + let acc: T::AccountId = USER_ACCOUNT.into(); + whitelist_account!(acc); + #[extrinsic_call] + _( + RawOrigin::Signed(USER_ACCOUNT.into()), + l1_chain.into(), + ETH_RECIPIENT_ACCOUNT, + l1_asset_address, + WITHDRAWAL_AMOUNT, + FERRY_TIP_NONE, + ); + + assert!( + L2Requests::::get(l1_chain, RequestId::from((Origin::L2, FIRST_REQUEST_ID))) + .is_some(), + "AFTER L2Requests {:?} l1_chain {:?} requestId should be some", + l1_chain, + DUMMY_REQUEST_ID + ); + Ok(()) + } + + #[benchmark] + fn refund_failed_deposit() -> Result<(), BenchmarkError> { + setup_account::(USER_ACCOUNT.into())?; + + let (l1_aset_chain, l1_asset_address) = + get_chain_and_address_for_asset_id::(TOKEN_ID.into())?; + let l1_chain: ::ChainId = l1_aset_chain.into(); + + FailedL1Deposits::::insert( + (l1_chain, DUMMY_REQUEST_ID), + (>::from(USER_ACCOUNT), H256::default()), + ); + + assert!( + L2Requests::::get(l1_chain, RequestId::from((Origin::L2, FIRST_REQUEST_ID))) + .is_none(), + "BEFORE L2Requests {:?} l1_chain {:?} requestId should be none", + l1_chain, + DUMMY_REQUEST_ID + ); + + let acc: T::AccountId = USER_ACCOUNT.into(); + whitelist_account!(acc); + #[extrinsic_call] + _(RawOrigin::Signed(USER_ACCOUNT.into()), l1_chain, DUMMY_REQUEST_ID); + + assert!( + L2Requests::::get(l1_chain, RequestId::from((Origin::L2, FIRST_REQUEST_ID))) + .is_some(), + "AFTER L2Requests {:?} l1_chain {:?} requestId should be some", + l1_chain, + DUMMY_REQUEST_ID + ); + Ok(()) + } + + #[benchmark] + fn ferry_deposit() -> Result<(), BenchmarkError> { + setup_account::(FERRY_ACCOUNT.into())?; + + let (l1_aset_chain, l1_asset_address) = + get_chain_and_address_for_asset_id::(TOKEN_ID.into())?; + let l1_chain: ::ChainId = l1_aset_chain.into(); + + let deposit = messages::Deposit { + depositRecipient: ETH_RECIPIENT_ACCOUNT.into(), + requestId: RequestId::from((Origin::L1, DUMMY_REQUEST_ID)), + tokenAddress: l1_asset_address, + amount: DEPOSIT_AMOUNT.into(), + timeStamp: Default::default(), + ferryTip: FERRY_TIP.into(), + }; + let deposit_hash = deposit.abi_encode_hash(); + + assert!( + FerriedDeposits::::get((l1_chain, deposit_hash)).is_none(), + "BEFORE FerriedDeposits {:?} l1_chain {:?} deposit_hash should be none", + l1_chain, + deposit_hash + ); + + let acc: T::AccountId = FERRY_ACCOUNT.into(); + whitelist_account!(acc); + #[extrinsic_call] + _( + RawOrigin::Signed(FERRY_ACCOUNT.into()), + l1_chain, + RequestId::from((Origin::L1, DUMMY_REQUEST_ID)), + ETH_RECIPIENT_ACCOUNT.into(), + l1_asset_address, + DEPOSIT_AMOUNT.into(), + Default::default(), + FERRY_TIP.into(), + deposit_hash, + ); + + assert!( + FerriedDeposits::::get((l1_chain, deposit_hash)).is_some(), + "AFTER FerriedDeposits {:?} l1_chain {:?} deposit_hash should be some", + l1_chain, + deposit_hash + ); + Ok(()) + } + + #[benchmark] + fn ferry_deposit_unsafe() -> Result<(), BenchmarkError> { + setup_account::(FERRY_ACCOUNT.into())?; + + let (l1_aset_chain, l1_asset_address) = + get_chain_and_address_for_asset_id::(TOKEN_ID.into())?; + let l1_chain: ::ChainId = l1_aset_chain.into(); + let deposit = messages::Deposit { + depositRecipient: ETH_RECIPIENT_ACCOUNT.into(), + requestId: RequestId::from((Origin::L1, DUMMY_REQUEST_ID)), + tokenAddress: l1_asset_address, + amount: DEPOSIT_AMOUNT.into(), + timeStamp: Default::default(), + ferryTip: FERRY_TIP.into(), + }; + let deposit_hash = deposit.abi_encode_hash(); + + assert!( + FerriedDeposits::::get((l1_chain, deposit_hash)).is_none(), + "BEFORE FerriedDeposits {:?} l1_chain {:?} deposit_hash should be none", + l1_chain, + deposit_hash + ); + + let acc: T::AccountId = FERRY_ACCOUNT.into(); + whitelist_account!(acc); + #[extrinsic_call] + _( + RawOrigin::Signed(FERRY_ACCOUNT.into()), + l1_chain, + RequestId::from((Origin::L1, DUMMY_REQUEST_ID)), + ETH_RECIPIENT_ACCOUNT.into(), + l1_asset_address, + DEPOSIT_AMOUNT.into(), + Default::default(), + FERRY_TIP.into(), + ); + + assert!( + FerriedDeposits::::get((l1_chain, deposit_hash)).is_some(), + "AFTER FerriedDeposits {:?} l1_chain {:?} deposit_hash should be some", + l1_chain, + deposit_hash + ); + Ok(()) + } + + #[benchmark] + fn process_deposit() -> Result<(), BenchmarkError> { + let (l1_aset_chain, l1_asset_address) = + get_chain_and_address_for_asset_id::(TOKEN_ID.into())?; + let l1_chain: ::ChainId = l1_aset_chain.into(); + let deposit = messages::Deposit { + depositRecipient: ETH_RECIPIENT_ACCOUNT.into(), + requestId: RequestId::from((Origin::L1, DUMMY_REQUEST_ID)), + tokenAddress: l1_asset_address, + amount: DEPOSIT_AMOUNT.into(), + timeStamp: Default::default(), + ferryTip: FERRY_TIP.into(), + }; + let deposit_hash = deposit.abi_encode_hash(); + + FerriedDeposits::::insert( + (l1_chain, deposit_hash), + &>::from(ETH_RECIPIENT_ACCOUNT), + ); + + let balance_before = + T::Tokens::free_balance(TOKEN_ID.into(), Ð_RECIPIENT_ACCOUNT.into()); + + #[block] + { + Rolldown::::process_deposit(l1_chain, &deposit); + } + + let balance_after = T::Tokens::free_balance(TOKEN_ID.into(), Ð_RECIPIENT_ACCOUNT.into()); + assert_eq!( + balance_after, + balance_before + DEPOSIT_AMOUNT.into(), + "Balance should increase by {:?}", + DEPOSIT_AMOUNT + ); + Ok(()) + } + + // This benchmark doesn't consider the size of ActiveSequencers and SequencerRights L1 BtreeMap Entry + // This should be fine since the impact should be low... + // Also there is no way reasonable way to acquire that value when this function is actually called in the on_initiliaze hook (we don't wanna read these values everytime)... + #[benchmark] + fn process_cancel_resolution() -> Result<(), BenchmarkError> { + setup_sequencer::(SEQUENCER_ACCOUNT.into(), None, true, true)?; + setup_sequencer::(SEQUENCER_ACCOUNT_2.into(), None, true, false)?; + + let (l1_aset_chain, l1_asset_address) = + get_chain_and_address_for_asset_id::(TOKEN_ID.into())?; + let l1_chain: ::ChainId = l1_aset_chain.into(); + + let cancel_request = messages::Cancel { + requestId: RequestId::from((Origin::L2, Default::default())), + updater: SEQUENCER_ACCOUNT.into(), + canceler: SEQUENCER_ACCOUNT_2.into(), + range: messages::Range { start: u128::default(), end: u128::default() }, + hash: H256::default(), + }; + + L2Requests::::insert( + l1_chain, + RequestId::from((Origin::L2, DUMMY_REQUEST_ID)), + (L2Request::Cancel(cancel_request.clone()), cancel_request.abi_encode_hash()), + ); + AwaitingCancelResolution::::mutate(l1_chain, |v| { + v.insert((SEQUENCER_ACCOUNT.into(), DUMMY_REQUEST_ID, DisputeRole::Submitter)) + }); + AwaitingCancelResolution::::mutate(l1_chain, |v| { + v.insert((SEQUENCER_ACCOUNT_2.into(), DUMMY_REQUEST_ID, DisputeRole::Canceler)) + }); + + let cancel_resolution = messages::CancelResolution { + requestId: RequestId::from((Origin::L1, Default::default())), + l2RequestId: DUMMY_REQUEST_ID, + cancelJustified: true, + timeStamp: Default::default(), + }; + + let slash_fine_amount = pallet_sequencer_staking::SlashFineAmount::::get(); + pallet_sequencer_staking::SequencerStake::::insert( + ( + >::from(SEQUENCER_ACCOUNT), + Into::<::ChainId>::into(l1_aset_chain), + ), + slash_fine_amount, + ); + assert!( + pallet_sequencer_staking::SequencerStake::::get(( + >::from(SEQUENCER_ACCOUNT), + Into::<::ChainId>::into(l1_aset_chain) + )) < pallet_sequencer_staking::MinimalStakeAmount::::get() + slash_fine_amount, + "suboptimal code path" + ); + + assert!( + T::SequencerStakingProvider::is_active_sequencer(l1_chain, &SEQUENCER_ACCOUNT.into()), + "suboptimal code path" + ); + assert!( + T::SequencerStakingProvider::is_active_sequencer(l1_chain, &SEQUENCER_ACCOUNT_2.into()), + "suboptimal code path" + ); + assert!( + T::SequencerStakingProvider::is_selected_sequencer(l1_chain, &SEQUENCER_ACCOUNT.into()), + "suboptimal code path" + ); + + assert!( + AwaitingCancelResolution::::get(l1_chain) + .get(&(SEQUENCER_ACCOUNT.into(), DUMMY_REQUEST_ID, DisputeRole::Submitter)) + .is_some(), + "BEFORE AwaitingCancelResolution {:?} l1_chain {:?} sequencer should be some", + l1_chain, + SEQUENCER_ACCOUNT + ); + assert!( + AwaitingCancelResolution::::get(l1_chain) + .get(&(SEQUENCER_ACCOUNT_2.into(), DUMMY_REQUEST_ID, DisputeRole::Canceler)) + .is_some(), + "BEFORE AwaitingCancelResolution {:?} l1_chain {:?} sequencer should be some", + l1_chain, + SEQUENCER_ACCOUNT_2 + ); + + let balance_before = + T::Tokens::total_balance(T::NativeCurrencyId::get(), &SEQUENCER_ACCOUNT.into()); + + #[block] + { + Rolldown::::process_cancel_resolution(l1_chain, &cancel_resolution); + } + + let balance_after = + T::Tokens::total_balance(T::NativeCurrencyId::get(), &SEQUENCER_ACCOUNT.into()); + assert_eq!( + balance_after + + TryInto::::try_into(slash_fine_amount) + .ok() + .expect("should work") + .to_balance::(), + balance_before, + "Balance should decrease by {:?}", + slash_fine_amount + ); + assert!( + AwaitingCancelResolution::::get(l1_chain) + .get(&(SEQUENCER_ACCOUNT.into(), DUMMY_REQUEST_ID, DisputeRole::Submitter)) + .is_none(), + "AFTER AwaitingCancelResolution {:?} l1_chain {:?} sequencer should be none", + l1_chain, + SEQUENCER_ACCOUNT + ); + assert!( + AwaitingCancelResolution::::get(l1_chain) + .get(&(SEQUENCER_ACCOUNT_2.into(), DUMMY_REQUEST_ID, DisputeRole::Canceler)) + .is_none(), + "AFTER AwaitingCancelResolution {:?} l1_chain {:?} sequencer should be none", + l1_chain, + SEQUENCER_ACCOUNT_2 + ); + + Ok(()) + } + + #[benchmark] + fn load_next_update_from_execution_queue() -> Result<(), BenchmarkError> { + let current_execution_id = 1u128; + let next_execution_id = 2u128; + let scheduled_at: BlockNumberFor = 19u32.into(); + let (l1_aset_chain, _) = get_chain_and_address_for_asset_id::(TOKEN_ID.into())?; + let l1_chain: ::ChainId = l1_aset_chain.into(); + + UpdatesExecutionQueue::::remove(current_execution_id); + UpdatesExecutionQueue::::insert( + next_execution_id, + (scheduled_at, l1_chain, H256::zero(), 10u128), + ); + UpdatesExecutionQueueNextId::::put(current_execution_id); + + #[block] + { + Rolldown::::load_next_update_from_execution_queue(); + } + + assert_eq!(UpdatesExecutionQueueNextId::::get(), next_execution_id); + + Ok(()) + } + + #[benchmark] + fn schedule_request_for_execution_if_dispute_period_has_passsed() -> Result<(), BenchmarkError> + { + setup_account::(USER_ACCOUNT.into())?; + setup_and_do_withdrawal::(USER_ACCOUNT.into())?; + + let block_for_automatic_batch = + (::MerkleRootAutomaticBatchSize::get() + 1u128).saturated_into::(); + let chain: ::ChainId = crate::messages::Chain::Ethereum.into(); + let update_hash = + H256::from(hex!("1111111111111111111111111111111111111111111111111111111111111111")); + + let sequencer_account: T::AccountId = SEQUENCER_ACCOUNT.into(); + let metadata = UpdateMetadata { + min_id: 1u128, + max_id: 1u128, + update_size: 1u128, + sequencer: sequencer_account.clone(), + update_hash, + }; + + PendingSequencerUpdates::::insert(1u128, chain, metadata); + assert_eq!(LastScheduledUpdateIdInExecutionQueue::::get(), 0u128); + assert_eq!(UpdatesExecutionQueue::::get(FIRST_SCHEDULED_UPDATE_ID), None); + + #[block] + { + Rolldown::::schedule_request_for_execution_if_dispute_period_has_passsed( + block_for_automatic_batch.into(), + ); + } + + assert_eq!(LastScheduledUpdateIdInExecutionQueue::::get(), 1u128); + assert_eq!( + UpdatesExecutionQueue::::get(FIRST_SCHEDULED_UPDATE_ID), + Some((block_for_automatic_batch.into(), chain, update_hash, 1u128)) + ); + + Ok(()) + } + + #[benchmark] + fn maybe_create_batch() -> Result<(), BenchmarkError> { + // trigger batch creating because of automatic batch size + setup_sequencer::(SEQUENCER_ACCOUNT.into(), None, true, true)?; + + let (l1_aset_chain, l1_asset_address) = + get_chain_and_address_for_asset_id::(TOKEN_ID.into())?; + let l1_chain: ::ChainId = l1_aset_chain.into(); + let automatic_batch_size = Pallet::::automatic_batch_size(); + + let last_batch_id = 1u128; + let latest_element_in_previous_batch = 123u128; + let last_batch_range = (1u128, latest_element_in_previous_batch); + let latest_element_now = latest_element_in_previous_batch + automatic_batch_size; + + assert!(T::SequencerStakingProvider::is_selected_sequencer( + l1_chain, + &SEQUENCER_ACCOUNT.into() + )); + + L2OriginRequestId::::mutate(|map| { + map.insert(l1_chain, latest_element_now.saturating_add(1u128)); + }); + + L2RequestsBatchLast::::mutate(|map| { + map.insert(l1_chain, (20u32.into(), 1u128, (1u128, latest_element_in_previous_batch))); + }); + + assert_eq!(L2RequestsBatch::::get((l1_chain, 2u128)), None); + + >::set_block_number(20u32.into()); + + #[block] + { + Rolldown::::maybe_create_batch(21u32.into()); + } + + assert_eq!( + L2RequestsBatch::::get((l1_chain, 2u128)), + Some(( + 21u32.into(), + (latest_element_in_previous_batch + 1, latest_element_now), + SEQUENCER_ACCOUNT.into() + )) + ); + + Ok(()) + } + + #[benchmark] + fn execute_requests_from_execute_queue() -> Result<(), BenchmarkError> { + let (l1_aset_chain, l1_asset_address) = + get_chain_and_address_for_asset_id::(TOKEN_ID.into())?; + let l1_chain: ::ChainId = l1_aset_chain.into(); + + >::set_block_number(20u32.into()); + let execution_id = 123u128; + let scheduled_at: BlockNumberFor = 19u32.into(); + let l1_chain: ::ChainId = l1_aset_chain.into(); + let update_hash = + H256::from(hex!("1111111111111111111111111111111111111111111111111111111111111111")); + + UpdatesExecutionQueueNextId::::put(execution_id); + UpdatesExecutionQueue::::insert( + execution_id, + (scheduled_at, l1_chain, update_hash, 10u128), + ); + LastProcessedRequestOnL2::::insert(l1_chain, 0u128); + + let update = L1UpdateBuilder::default() + .with_requests(vec![L1UpdateRequest::Deposit(Default::default()); 10_000]) + .build(); + PendingSequencerUpdateContent::::insert(update_hash, update); + + #[block] + { + Rolldown::::execute_requests_from_execute_queue(); + } + + UpdatesExecutionQueue::::get(execution_id).expect("update partially executed"); + assert_eq!( + LastProcessedRequestOnL2::::get(l1_chain), + ::RequestsPerBlock::get() + ); + Ok(()) + } +} diff --git a/gasp-node/pallets/rolldown/src/lib.rs b/gasp-node/pallets/rolldown/src/lib.rs new file mode 100644 index 000000000..5b78c90a7 --- /dev/null +++ b/gasp-node/pallets/rolldown/src/lib.rs @@ -0,0 +1,1950 @@ +#![cfg_attr(not(feature = "std"), no_std)] + +use messages::{EthAbi, EthAbiHash, L1UpdateRequest}; +pub mod messages; + +#[cfg(feature = "runtime-benchmarks")] +mod benchmarking; + +mod weight_utils; +pub mod weights; +pub use weights::WeightInfo; + +use frame_support::{ + ensure, + pallet_prelude::*, + traits::{ + tokens::currency::MultiTokenCurrency, Contains, ExistenceRequirement, Get, StorageVersion, + }, +}; +use frame_system::{ensure_signed, pallet_prelude::*}; +use messages::{Cancel, FailedDepositResolution, Origin, RequestId, Withdrawal}; +use rs_merkle::{Hasher, MerkleProof, MerkleTree}; +use scale_info::prelude::{format, string::String}; + +use sp_runtime::traits::{One, SaturatedConversion, Saturating}; +use sp_std::{collections::btree_map::BTreeMap, iter::Iterator}; + +use alloy_sol_types::SolValue; +use frame_support::{traits::WithdrawReasons, PalletId}; +use itertools::Itertools; +use mangata_support::traits::{ + AssetRegistryProviderTrait, GetMaintenanceStatusTrait, RolldownProviderTrait, + SequencerStakingProviderTrait, SequencerStakingRewardsTrait, SetMaintenanceModeOn, +}; +use mangata_types::assets::L1Asset; +use orml_tokens::{MultiTokenCurrencyExtended, MultiTokenReservableCurrency}; +use sp_core::{H256, U256}; +use sp_crypto_hashing::keccak_256; +use sp_runtime::traits::{AccountIdConversion, Convert, ConvertBack, Zero}; +use sp_std::{collections::btree_set::BTreeSet, convert::TryInto, prelude::*, vec::Vec}; + +pub type CurrencyIdOf = <::Tokens as MultiTokenCurrency< + ::AccountId, +>>::CurrencyId; + +pub type BalanceOf = + <::Tokens as MultiTokenCurrency<::AccountId>>::Balance; +pub type ChainIdOf = ::ChainId; + +type AccountIdOf = ::AccountId; + +type RoundIndex = u32; + +pub(crate) const LOG_TARGET: &'static str = "rolldown"; + +// syntactic sugar for logging. +#[macro_export] +macro_rules! log { + ($level:tt, $patter:expr $(, $values:expr)* $(,)?) => { + log::$level!( + target: crate::LOG_TARGET, + concat!("[{:?}] 💸 ", $patter), >::block_number() $(, $values)* + ) + }; +} + +#[derive(Debug, PartialEq)] +pub struct EthereumAddressConverter(sp_std::marker::PhantomData); + +impl Convert<[u8; 20], sp_runtime::AccountId20> + for EthereumAddressConverter +{ + fn convert(eth_addr: [u8; 20]) -> sp_runtime::AccountId20 { + eth_addr.into() + } +} + +impl ConvertBack<[u8; 20], sp_runtime::AccountId20> + for EthereumAddressConverter +{ + fn convert_back(eth_addr: sp_runtime::AccountId20) -> [u8; 20] { + eth_addr.into() + } +} + +#[derive(Clone)] +pub struct Keccak256Hasher {} + +impl Hasher for Keccak256Hasher { + type Hash = [u8; 32]; + + fn hash(data: &[u8]) -> [u8; 32] { + let mut output = [0u8; 32]; + let hash = keccak_256(&data[..]); + output.copy_from_slice(&hash[..]); + output + } +} + +#[derive(PartialEq, RuntimeDebug, Clone, Encode, Decode, MaxEncodedLen, TypeInfo)] +pub enum L1DepositProcessingError { + Overflow, + AssetRegistrationProblem, + MintError, +} + +impl From for Error { + fn from(e: L1DepositProcessingError) -> Self { + match e { + L1DepositProcessingError::Overflow => Error::::BalanceOverflow, + L1DepositProcessingError::AssetRegistrationProblem => Error::::L1AssetCreationFailed, + L1DepositProcessingError::MintError => Error::::MintError, + } + } +} + +#[derive(PartialEq, RuntimeDebug, Clone, Encode, Decode, MaxEncodedLen, TypeInfo)] +pub enum L1RequestProcessingError { + Overflow, + AssetRegistrationProblem, + MintError, + NotEnoughtCancelRights, + WrongCancelRequestId, + SequencerNotSlashed, +} + +impl From for L1RequestProcessingError { + fn from(err: L1DepositProcessingError) -> Self { + match err { + L1DepositProcessingError::Overflow => Self::Overflow, + L1DepositProcessingError::AssetRegistrationProblem => Self::AssetRegistrationProblem, + L1DepositProcessingError::MintError => Self::MintError, + } + } +} + +#[cfg(test)] +mod tests; + +#[cfg(test)] +mod mock; + +use crate::messages::L1Update; +pub use pallet::*; + +#[frame_support::pallet] +pub mod pallet { + + use super::*; + + const STORAGE_VERSION: StorageVersion = StorageVersion::new(0); + + #[pallet::pallet] + #[pallet::storage_version(STORAGE_VERSION)] + pub struct Pallet(PhantomData); + + #[pallet::hooks] + impl Hooks> for Pallet { + fn on_initialize(now: BlockNumberFor) -> Weight { + let mut total_weight: Weight = T::DbWeight::get().reads(1); + + if T::MaintenanceStatusProvider::is_maintenance() { + LastMaintananceMode::::put(now.saturated_into::()); + total_weight += T::DbWeight::get().writes(1); + } else { + Self::maybe_create_batch(now); + total_weight += ::WeightInfo::maybe_create_batch(); + } + + Self::schedule_request_for_execution_if_dispute_period_has_passsed(now); + total_weight += ::WeightInfo::schedule_request_for_execution_if_dispute_period_has_passsed(); + + Self::load_next_update_from_execution_queue(); + total_weight += ::WeightInfo::load_next_update_from_execution_queue(); + total_weight + } + + fn on_idle(_now: BlockNumberFor, mut remaining_weight: Weight) -> Weight { + let mut used_weight = Weight::default(); + + // given aproximated nature of weights for huge blob read/wirtes and complicated + // benchmarks of on_idle_hook lets consider only 80% of remaining weight + remaining_weight = remaining_weight.saturating_mul(8).saturating_div(10); + + // already cached by using in on_initialize hook + if !T::MaintenanceStatusProvider::is_maintenance() { + let get_update_size_cost = T::DbWeight::get().reads(2); + + if remaining_weight.ref_time() < get_update_size_cost.ref_time() { + return used_weight; + } + + remaining_weight -= get_update_size_cost; + used_weight += get_update_size_cost; + + let update_size = + Self::get_current_update_size_from_execution_queue().unwrap_or(1u128); + + // NOTE: execute_requests_from_execute_queue accounts for overal cost of processing + // biggest expected update (with 10k requests) assuming that all of them are deposits + // to accounts for most expensive case now need to substract cost of processing deposits + // and add cost of processing cancels instead + let mut cost_of_processing_requests = + ::WeightInfo::execute_requests_from_execute_queue(); + + cost_of_processing_requests += + ::WeightInfo::process_cancel_resolution() + .saturating_mul(Self::get_max_requests_per_block().saturated_into()); + cost_of_processing_requests -= ::WeightInfo::process_deposit() + .saturating_mul(Self::get_max_requests_per_block().saturated_into()); + + // account for huge read cost when reading huge update + cost_of_processing_requests += T::DbWeight::get().reads(1).saturating_mul( + weight_utils::get_read_scalling_factor(update_size.saturated_into()) + .saturated_into(), + ); + + if remaining_weight.ref_time() < cost_of_processing_requests.ref_time() { + return used_weight; + } + + // NOTE: here we could adjust the used weight based on the actual executed requests + // as the benchmarks accounts for the worst case scenario which is cancel_resultion + // processing + let _executed: Vec = Self::execute_requests_from_execute_queue(); + + used_weight += cost_of_processing_requests; + remaining_weight -= cost_of_processing_requests; + } + used_weight + } + } + + #[derive( + Eq, PartialEq, RuntimeDebug, Clone, Encode, Decode, MaxEncodedLen, TypeInfo, Default, + )] + pub struct SequencerRights { + pub read_rights: u128, + pub cancel_rights: u128, + } + + #[derive(Eq, PartialEq, RuntimeDebug, Clone, Encode, Decode, MaxEncodedLen, TypeInfo)] + pub struct UpdateMetadata { + pub max_id: u128, + pub min_id: u128, + pub update_size: u128, + pub sequencer: AccountId, + pub update_hash: H256, + } + + #[derive(Eq, PartialEq, RuntimeDebug, Clone, Copy, Encode, Decode, TypeInfo)] + pub enum L2Request { + FailedDepositResolution(FailedDepositResolution), + Cancel(Cancel), + Withdrawal(Withdrawal), + } + + #[derive(Eq, PartialEq, RuntimeDebug, Clone, Encode, Decode, TypeInfo)] + pub enum BatchSource { + Manual, + AutomaticSizeReached, + PeriodReached, + } + + #[derive( + PartialOrd, Ord, Eq, PartialEq, RuntimeDebug, Clone, Encode, Decode, MaxEncodedLen, TypeInfo, + )] + pub enum DisputeRole { + Canceler, + Submitter, + } + + #[pallet::storage] + pub type FerriedDeposits = + StorageMap<_, Blake2_128Concat, (::ChainId, H256), T::AccountId, OptionQuery>; + + #[pallet::storage] + /// stores id of the failed depoisit, so it can be refunded using [`Pallet::refund_failed_deposit`] + pub type FailedL1Deposits = StorageMap< + _, + Blake2_128Concat, + (::ChainId, u128), + (T::AccountId, H256), + OptionQuery, + >; + + #[pallet::storage] + #[pallet::getter(fn get_last_processed_request_on_l2)] + // Id of the last request originating on other chain that has been executed + pub type LastProcessedRequestOnL2 = + StorageMap<_, Blake2_128Concat, ::ChainId, u128, ValueQuery>; + + #[pallet::storage] + #[pallet::unbounded] + // Id of the next request that will originate on this chain + pub type L2OriginRequestId = + StorageValue<_, BTreeMap<::ChainId, u128>, ValueQuery>; + + #[pallet::storage] + pub type ManualBatchExtraFee = StorageValue<_, BalanceOf, ValueQuery>; + + #[pallet::storage] + #[pallet::unbounded] + #[pallet::getter(fn get_pending_requests)] + // Stores requests brought by sequencer that are under dispute period. + pub type PendingSequencerUpdates = StorageDoubleMap< + _, + Blake2_128Concat, + u128, + Blake2_128Concat, + ::ChainId, + UpdateMetadata, + OptionQuery, + >; + + #[pallet::storage] + #[pallet::unbounded] + // Stores requests brought by sequencer that are under dispute period. + pub type PendingSequencerUpdateContent = + StorageMap<_, Blake2_128Concat, H256, messages::L1Update, OptionQuery>; + + #[pallet::storage] + #[pallet::unbounded] + // queue of all updates that went through dispute period and are ready to be processed + pub type UpdatesExecutionQueue = StorageMap< + _, + Blake2_128Concat, + u128, + // scheduled_at, chain, update_hash, update_size + (BlockNumberFor, ::ChainId, H256, u128), + OptionQuery, + >; + + #[pallet::storage] + pub type LastMaintananceMode = StorageValue<_, u128, OptionQuery>; + + #[pallet::storage] + // Id of the next update to be executed + pub type UpdatesExecutionQueueNextId = StorageValue<_, u128, ValueQuery>; + + #[pallet::storage] + // Id of the last update that has been executed + pub type LastScheduledUpdateIdInExecutionQueue = StorageValue<_, u128, ValueQuery>; + + #[pallet::storage] + #[pallet::unbounded] + pub type SequencersRights = StorageMap< + _, + Blake2_128Concat, + ::ChainId, + BTreeMap, + ValueQuery, + >; + + //maps Chain and !!!! request origin id!!! to pending update + #[pallet::storage] + #[pallet::unbounded] + #[pallet::getter(fn get_l2_request)] + pub type L2Requests = StorageDoubleMap< + _, + Blake2_128Concat, + ::ChainId, + Blake2_128Concat, + RequestId, + (L2Request, H256), + OptionQuery, + >; + + #[pallet::storage] + #[pallet::unbounded] + #[pallet::getter(fn get_awaiting_cancel_resolution)] + pub type AwaitingCancelResolution = StorageMap< + _, + Blake2_128Concat, + ::ChainId, + BTreeSet<(T::AccountId, u128, DisputeRole)>, + ValueQuery, + >; + + #[pallet::storage] + #[pallet::getter(fn get_last_update_by_sequencer)] + pub type LastUpdateBySequencer = + StorageMap<_, Blake2_128Concat, (::ChainId, T::AccountId), u128, ValueQuery>; + + #[pallet::storage] + #[pallet::getter(fn get_max_accepted_request_id_on_l2)] + pub type MaxAcceptedRequestIdOnl2 = + StorageMap<_, Blake2_128Concat, ::ChainId, u128, ValueQuery>; + + #[pallet::storage] + #[pallet::getter(fn get_total_number_of_deposits)] + pub type TotalNumberOfDeposits = StorageValue<_, u128, ValueQuery>; + + #[pallet::storage] + #[pallet::getter(fn get_total_number_of_withdrawals)] + pub type TotalNumberOfWithdrawals = StorageValue<_, u128, ValueQuery>; + + #[pallet::storage] + pub type L2RequestsBatch = StorageMap< + _, + Blake2_128Concat, + (ChainIdOf, u128), + (BlockNumberFor, (u128, u128), AccountIdOf), + OptionQuery, + >; + + #[pallet::storage] + #[pallet::unbounded] + /// For each supported chain stores: + /// - last batch id + /// - range of the reqeusts in last batch + pub type L2RequestsBatchLast = StorageValue< + _, + BTreeMap<::ChainId, (BlockNumberFor, u128, (u128, u128))>, + ValueQuery, + >; + + #[pallet::storage] + pub type DisputePeriod = + StorageMap<_, Blake2_128Concat, ChainIdOf, u128, OptionQuery>; + + #[pallet::event] + #[pallet::generate_deposit(pub(super) fn deposit_event)] + pub enum Event { + L1ReadStored { + chain: ::ChainId, + sequencer: T::AccountId, + dispute_period_end: u128, + range: messages::Range, + hash: H256, + }, + RequestProcessedOnL2 { + chain: ::ChainId, + request_id: u128, + status: Result<(), L1RequestProcessingError>, + }, + L1ReadCanceled { + chain: ::ChainId, + canceled_sequencer_update: u128, + assigned_id: RequestId, + }, + TxBatchCreated { + chain: ::ChainId, + source: BatchSource, + assignee: T::AccountId, + batch_id: u128, + range: (u128, u128), + }, + WithdrawalRequestCreated { + chain: ::ChainId, + request_id: RequestId, + recipient: [u8; 20], + token_address: [u8; 20], + amount: u128, + hash: H256, + ferry_tip: u128, + }, + ManualBatchExtraFeeSet(BalanceOf), + DepositRefundCreated { + chain: ChainIdOf, + refunded_request_id: RequestId, + ferry: Option>, + }, + L1ReadScheduledForExecution { + chain: ::ChainId, + hash: H256, + }, + L1ReadIgnoredBecauseOfMaintenanceMode { + chain: ::ChainId, + hash: H256, + }, + L1ReadIgnoredBecauseOfUnknownDisputePeriod { + chain: ::ChainId, + hash: H256, + }, + DepositFerried { + chain: ::ChainId, + deposit: messages::Deposit, + deposit_hash: H256, + }, + L1ReadExecuted { + chain: ::ChainId, + hash: H256, + }, + DisputePeriodSet { + chain: ::ChainId, + dispute_period_length: u128, + }, + } + + #[pallet::error] + /// Errors + pub enum Error { + OperationFailed, + ReadRightsExhausted, + CancelRightsExhausted, + EmptyUpdate, + AddressDeserializationFailure, + RequestDoesNotExist, + NotEnoughAssets, + NotEnoughAssetsForFee, + // Ferry tip is larger then ferried amount + NotEnoughAssetsForFerryTip, + BalanceOverflow, + L1AssetCreationFailed, + MathOverflow, + TooManyRequests, + InvalidUpdate, + L1AssetNotFound, + WrongRequestId, + OnlySelectedSequencerisAllowedToUpdate, + SequencerLastUpdateStillInDisputePeriod, + SequencerAwaitingCancelResolution, + MultipleUpdatesInSingleBlock, + BlockedByMaintenanceMode, + UnsupportedAsset, + InvalidRange, + NonExistingRequestId, + UnknownAliasAccount, + FailedDepositDoesNotExist, + EmptyBatch, + TokenDoesNotExist, + NotEligibleForRefund, + FerryHashMismatch, + MintError, + AssetRegistrationProblem, + UpdateHashMishmatch, + AlreadyExecuted, + UninitializedChainId, + // Asset can be withdrawn only to sender's address + NontransferableToken, + // the deposit was already ferried + AlreadyFerried, + } + + #[cfg(feature = "runtime-benchmarks")] + pub trait RolldownBenchmarkingConfig: pallet_sequencer_staking::Config {} + + #[cfg(not(feature = "runtime-benchmarks"))] + pub trait RolldownBenchmarkingConfig {} + + #[pallet::config] + pub trait Config: frame_system::Config + RolldownBenchmarkingConfig { + type RuntimeEvent: From> + IsType<::RuntimeEvent>; + type SequencerStakingProvider: SequencerStakingProviderTrait< + Self::AccountId, + BalanceOf, + ChainIdOf, + >; + type SequencerStakingRewards: SequencerStakingRewardsTrait; + type AddressConverter: ConvertBack<[u8; 20], Self::AccountId>; + // Dummy so that we can have the BalanceOf type here for the SequencerStakingProviderTrait + type Tokens: MultiTokenCurrency + + MultiTokenReservableCurrency + + MultiTokenCurrencyExtended; + type AssetRegistryProvider: AssetRegistryProviderTrait>; + #[pallet::constant] + type RightsMultiplier: Get; + #[pallet::constant] + type RequestsPerBlock: Get; + type MaintenanceStatusProvider: GetMaintenanceStatusTrait + SetMaintenanceModeOn; + type ChainId: From + + Parameter + + Member + + Default + + TypeInfo + + MaybeSerializeDeserialize + + MaxEncodedLen + + PartialOrd + + codec::Decode + + codec::Encode + + Ord + + Copy; + type AssetAddressConverter: Convert<(ChainIdOf, [u8; 20]), L1Asset>; + // How many L2 requests needs to be created so they are grouped and assigned + // to active sequencer + #[pallet::constant] + type MerkleRootAutomaticBatchSize: Get; + // How many blocks since last batch needs to be create so the batch is created and assigned to + // active sequencer + #[pallet::constant] + type MerkleRootAutomaticBatchPeriod: Get; + type TreasuryPalletId: Get; + type NativeCurrencyId: Get>; + /// Withdrawals flat fee + type WithdrawFee: Convert, BalanceOf>; + /// Weight information for extrinsics in this pallet. + type WeightInfo: WeightInfo; + + /// Tokens which cannot be transfered by extrinsics/user or use in pool + type NontransferableTokens: Contains>; + } + + #[pallet::genesis_config] + pub struct GenesisConfig { + pub _phantom: PhantomData, + pub dispute_periods: BTreeMap, u128>, + } + + impl Default for GenesisConfig { + fn default() -> Self { + GenesisConfig { _phantom: Default::default(), dispute_periods: Default::default() } + } + } + + #[pallet::genesis_build] + impl BuildGenesisConfig for GenesisConfig { + fn build(&self) { + for (chain, period) in &self.dispute_periods { + DisputePeriod::::insert(chain, *period); + } + } + } + + // Many calls that deal with large storage items have their weight mutiplied by 2 + // We multiply by two so that we can have our large storage access (update requests) + // go upto 500 requests per update + // 500 also is really pushing it - it should probably be something like 100... + // https://substrate.stackexchange.com/questions/525/how-expensive-is-it-to-access-storage-items + #[pallet::call] + impl Pallet { + #[pallet::call_index(0)] + #[pallet::weight(::WeightInfo::update_l2_from_l1( + requests.get_requests_count().saturated_into() + ).saturating_add(T::DbWeight::get().reads(weight_utils::get_read_scalling_factor(requests.get_requests_count().saturated_into()).saturated_into())))] + pub fn update_l2_from_l1( + origin: OriginFor, + requests: messages::L1Update, + update_hash: H256, + ) -> DispatchResult { + let sequencer = ensure_signed(origin)?; + + let hash = requests.abi_encode_hash(); + ensure!(update_hash == hash, Error::::UpdateHashMishmatch); + + Self::update_impl(sequencer, requests) + } + + #[pallet::call_index(2)] + #[pallet::weight(::WeightInfo::force_update_l2_from_l1(update.pendingDeposits.len().saturating_add(update.pendingCancelResolutions.len()).saturating_mul(2) as u32))] + pub fn force_update_l2_from_l1( + origin: OriginFor, + update: messages::L1Update, + ) -> DispatchResultWithPostInfo { + let root = ensure_root(origin)?; + let chain: ::ChainId = update.chain.into(); + + ensure!( + !T::MaintenanceStatusProvider::is_maintenance(), + Error::::BlockedByMaintenanceMode + ); + + let metadata = + Self::validate_l1_update(chain, &update, T::AddressConverter::convert([0u8; 20]))?; + + let now = >::block_number(); + let update_size = update.get_requests_count(); + PendingSequencerUpdateContent::::insert(metadata.update_hash, update); + + Self::schedule_requests(now, chain, metadata); + Ok(().into()) + } + + #[pallet::call_index(3)] + // NOTE: account for worst case scenario, in the future we should introduce the mandatory + // parameter 'update_size' so we can parametrize weight with it + #[pallet::weight(::WeightInfo::cancel_requests_from_l1().saturating_mul(weight_utils::get_read_scalling_factor(10_000).saturated_into()))] + pub fn cancel_requests_from_l1( + origin: OriginFor, + chain: ::ChainId, + requests_to_cancel: u128, + ) -> DispatchResultWithPostInfo { + let canceler = ensure_signed(origin)?; + + ensure!( + !T::MaintenanceStatusProvider::is_maintenance(), + Error::::BlockedByMaintenanceMode + ); + + SequencersRights::::try_mutate(chain, |sequencers| { + let rights = + sequencers.get_mut(&canceler).ok_or(Error::::CancelRightsExhausted)?; + rights.cancel_rights = + rights.cancel_rights.checked_sub(1).ok_or(Error::::CancelRightsExhausted)?; + Ok::<_, Error>(()) + })?; + + let metadata = PendingSequencerUpdates::::take(requests_to_cancel, chain) + .ok_or(Error::::RequestDoesNotExist)?; + + let submitter = metadata.sequencer; + let request_hash = metadata.update_hash; + + let request = PendingSequencerUpdateContent::::take(request_hash) + .ok_or(Error::::RequestDoesNotExist)?; + + let l2_request_id = Self::acquire_l2_request_id(chain); + + let cancel_request = Cancel { + requestId: RequestId { origin: Origin::L2, id: l2_request_id }, + updater: submitter.clone(), + canceler: canceler.clone(), + range: request.range().ok_or(Error::::InvalidUpdate)?, + hash: request_hash, + }; + + AwaitingCancelResolution::::mutate(chain, |v| { + v.insert((submitter.clone(), l2_request_id, DisputeRole::Submitter)) + }); + AwaitingCancelResolution::::mutate(chain, |v| { + v.insert((canceler, l2_request_id, DisputeRole::Canceler)) + }); + + let l2_request_cancel = L2Request::Cancel(cancel_request); + let l2_request_cancel_hash = l2_request_cancel.abi_encode_hash(); + + L2Requests::::insert( + chain, + RequestId::from((Origin::L2, l2_request_id)), + (l2_request_cancel, l2_request_cancel_hash), + ); + + Pallet::::deposit_event(Event::L1ReadCanceled { + canceled_sequencer_update: requests_to_cancel, + chain, + assigned_id: RequestId { origin: Origin::L2, id: l2_request_id }, + }); + + Ok(().into()) + } + + #[pallet::call_index(5)] + #[pallet::weight(::WeightInfo::withdraw())] + pub fn withdraw( + origin: OriginFor, + chain: ::ChainId, + recipient: [u8; 20], + token_address: [u8; 20], + amount: u128, + ferry_tip: Option, + ) -> DispatchResultWithPostInfo { + let account = ensure_signed(origin)?; + + ensure!( + !T::MaintenanceStatusProvider::is_maintenance(), + Error::::BlockedByMaintenanceMode + ); + + ensure!( + amount >= ferry_tip.unwrap_or_default(), + Error::::NotEnoughAssetsForFerryTip + ); + + let eth_asset = T::AssetAddressConverter::convert((chain, token_address)); + let asset_id = T::AssetRegistryProvider::get_l1_asset_id(eth_asset) + .ok_or(Error::::TokenDoesNotExist)?; + + ensure!( + !T::NontransferableTokens::contains(&asset_id) || + account == T::AddressConverter::convert(recipient), + Error::::NontransferableToken, + ); + + // fail will occur if user has not enough balance + ::Tokens::ensure_can_withdraw( + asset_id.into(), + &account, + amount.try_into().or(Err(Error::::BalanceOverflow))?, + WithdrawReasons::all(), + Default::default(), + ) + .or(Err(Error::::NotEnoughAssets))?; + + let extra_fee = <::WithdrawFee>::convert(chain); + if !extra_fee.is_zero() { + ::Tokens::ensure_can_withdraw( + Self::native_token_id(), + &account, + extra_fee, + WithdrawReasons::all(), + Default::default(), + ) + .or(Err(Error::::NotEnoughAssetsForFee))?; + + ::Tokens::transfer( + Self::native_token_id(), + &account, + &Self::treasury_account_id(), + extra_fee, + ExistenceRequirement::KeepAlive, + )?; + } + + // burn tokes for user + T::Tokens::burn_and_settle( + asset_id, + &account, + amount.try_into().or(Err(Error::::BalanceOverflow))?, + )?; + + let l2_request_id = Self::acquire_l2_request_id(chain); + + let request_id = RequestId { origin: Origin::L2, id: l2_request_id }; + let withdrawal_update = Withdrawal { + requestId: request_id.clone(), + withdrawalRecipient: recipient.clone(), + tokenAddress: token_address.clone(), + amount: U256::from(amount), + ferryTip: U256::from(ferry_tip.unwrap_or_default()), + }; + + let l2_request_withdrawal = L2Request::Withdrawal(withdrawal_update); + let l2_request_withdrawal_hash = l2_request_withdrawal.abi_encode_hash(); + L2Requests::::insert( + chain, + request_id.clone(), + (l2_request_withdrawal, l2_request_withdrawal_hash), + ); + + Pallet::::deposit_event(Event::WithdrawalRequestCreated { + chain, + request_id, + recipient, + token_address, + amount, + hash: l2_request_withdrawal_hash, + ferry_tip: ferry_tip.unwrap_or_default(), + }); + TotalNumberOfWithdrawals::::mutate(|v| *v = v.saturating_add(One::one())); + + Ok(().into()) + } + + #[pallet::call_index(4)] + #[pallet::weight(::WeightInfo::force_cancel_requests_from_l1().saturating_mul(2))] + pub fn force_cancel_requests_from_l1( + origin: OriginFor, + chain: ::ChainId, + requests_to_cancel: u128, + ) -> DispatchResultWithPostInfo { + let _ = ensure_root(origin)?; + + ensure!( + !T::MaintenanceStatusProvider::is_maintenance(), + Error::::BlockedByMaintenanceMode + ); + + let metadata = PendingSequencerUpdates::::take(requests_to_cancel, chain) + .ok_or(Error::::RequestDoesNotExist)?; + + let submitter = metadata.sequencer; + let hash = metadata.update_hash; + + if T::SequencerStakingProvider::is_active_sequencer(chain, &submitter) { + SequencersRights::::mutate(chain, |sequencers| { + if let Some(rights) = sequencers.get_mut(&submitter) { + rights.read_rights = 1; + } + }); + } + + Ok(().into()) + } + + #[pallet::call_index(6)] + #[pallet::weight(::WeightInfo::create_batch())] + pub fn create_batch( + origin: OriginFor, + chain: ::ChainId, + sequencer_account: Option, + ) -> DispatchResult { + let sender = ensure_signed(origin)?; + + ensure!( + !T::MaintenanceStatusProvider::is_maintenance(), + Error::::BlockedByMaintenanceMode + ); + + let asignee = Self::get_batch_asignee(chain, &sender, sequencer_account)?; + let extra_fee = ManualBatchExtraFee::::get(); + if !extra_fee.is_zero() { + ::Tokens::transfer( + Self::native_token_id(), + &sender, + &Self::treasury_account_id(), + extra_fee, + ExistenceRequirement::KeepAlive, + )?; + } + + let range = Self::get_batch_range_from_available_requests(chain)?; + Self::persist_batch_and_deposit_event(chain, range, asignee); + Ok(().into()) + } + + #[pallet::call_index(7)] + #[pallet::weight(::WeightInfo::set_manual_batch_extra_fee())] + pub fn set_manual_batch_extra_fee( + origin: OriginFor, + balance: BalanceOf, + ) -> DispatchResult { + let _ = ensure_root(origin)?; + ManualBatchExtraFee::::set(balance); + Pallet::::deposit_event(Event::ManualBatchExtraFeeSet(balance)); + Ok(().into()) + } + + #[pallet::call_index(8)] + #[pallet::weight(::WeightInfo::refund_failed_deposit())] + /// only deposit recipient can initiate refund failed deposit + pub fn refund_failed_deposit( + origin: OriginFor, + chain: ::ChainId, + request_id: u128, + ) -> DispatchResult { + let sender = ensure_signed(origin)?; + + // NOTE: failed deposits are not reachable at this point + let (author, deposit_hash) = FailedL1Deposits::::take((chain, request_id)) + .ok_or(Error::::FailedDepositDoesNotExist)?; + + let ferry = FerriedDeposits::::get((chain, deposit_hash)); + let eligible_for_refund = ferry.clone().unwrap_or(author.clone()); + ensure!(eligible_for_refund == sender, Error::::NotEligibleForRefund); + + let l2_request_id = Self::acquire_l2_request_id(chain); + + let failed_deposit_resolution = FailedDepositResolution { + requestId: RequestId { origin: Origin::L2, id: l2_request_id }, + originRequestId: request_id, + ferry: ferry.clone().map(T::AddressConverter::convert_back).unwrap_or([0u8; 20]), + }; + + let l2_request_failed_deposit = + L2Request::FailedDepositResolution(failed_deposit_resolution); + let l2_request_failed_deposit_hash = l2_request_failed_deposit.abi_encode_hash(); + L2Requests::::insert( + chain, + RequestId::from((Origin::L2, l2_request_id)), + (l2_request_failed_deposit, l2_request_failed_deposit_hash), + ); + + Self::deposit_event(Event::DepositRefundCreated { + refunded_request_id: RequestId { origin: Origin::L1, id: request_id }, + chain, + ferry, + }); + + Ok(().into()) + } + + #[pallet::call_index(9)] + #[pallet::weight(::WeightInfo::force_create_batch())] + /// Froce create batch and assigns it to provided sequencer + /// provided requests range must exists - otherwise `[Error::InvalidRange]` error will be returned + pub fn force_create_batch( + origin: OriginFor, + chain: ::ChainId, + range: (u128, u128), + sequencer_account: AccountIdOf, + ) -> DispatchResult { + let _ = ensure_root(origin)?; + + ensure!( + !T::MaintenanceStatusProvider::is_maintenance(), + Error::::BlockedByMaintenanceMode + ); + + ensure!( + L2Requests::::contains_key(chain, RequestId { origin: Origin::L2, id: range.0 }), + Error::::InvalidRange + ); + + ensure!( + L2Requests::::contains_key(chain, RequestId { origin: Origin::L2, id: range.1 }), + Error::::InvalidRange + ); + + Self::persist_batch_and_deposit_event(chain, range, sequencer_account); + Ok(().into()) + } + + #[pallet::call_index(10)] + #[pallet::weight(::WeightInfo::ferry_deposit())] + pub fn ferry_deposit( + origin: OriginFor, + chain: ::ChainId, + request_id: RequestId, + deposit_recipient: [u8; 20], + token_address: [u8; 20], + amount: u128, + timestamp: u128, + ferry_tip: u128, + deposit_hash: H256, + ) -> DispatchResult { + let sender = ensure_signed(origin)?; + + let deposit = messages::Deposit { + depositRecipient: deposit_recipient, + requestId: request_id, + tokenAddress: token_address, + amount: amount.into(), + timeStamp: timestamp.into(), + ferryTip: ferry_tip.into(), + }; + + ensure!(deposit.abi_encode_hash() == deposit_hash, Error::::FerryHashMismatch); + Self::ferry_desposit_impl(sender, chain, deposit)?; + + Ok(().into()) + } + + #[pallet::call_index(11)] + #[pallet::weight(::WeightInfo::ferry_deposit_unsafe())] + pub fn ferry_deposit_unsafe( + origin: OriginFor, + chain: ::ChainId, + request_id: RequestId, + deposit_recipient: [u8; 20], + token_address: [u8; 20], + amount: u128, + timestamp: u128, + ferry_tip: u128, + ) -> DispatchResult { + let sender = ensure_signed(origin)?; + + let deposit = messages::Deposit { + depositRecipient: deposit_recipient, + requestId: request_id, + tokenAddress: token_address, + amount: amount.into(), + timeStamp: timestamp.into(), + ferryTip: ferry_tip.into(), + }; + + Self::ferry_desposit_impl(sender, chain, deposit)?; + + Ok(().into()) + } + + #[pallet::call_index(12)] + #[pallet::weight(::WeightInfo::update_l2_from_l1_unsafe(requests.pendingDeposits.len().saturating_add(requests.pendingCancelResolutions.len()).saturating_mul(2) as u32))] + pub fn update_l2_from_l1_unsafe( + origin: OriginFor, + requests: messages::L1Update, + ) -> DispatchResult { + let sequencer = ensure_signed(origin)?; + Self::update_impl(sequencer, requests) + } + + #[pallet::call_index(13)] + #[pallet::weight(T::DbWeight::get().reads_writes(0, 2))] + pub fn set_dispute_period( + origin: OriginFor, + chain: ::ChainId, + dispute_period_length: u128, + ) -> DispatchResult { + let _ = ensure_root(origin)?; + DisputePeriod::::insert(chain, dispute_period_length); + Self::deposit_event(Event::DisputePeriodSet { chain, dispute_period_length }); + Ok(()) + } + } +} + +impl Pallet { + fn get_dispute_period(chain: ChainIdOf) -> Option { + DisputePeriod::::get(chain) + } + + fn get_max_requests_per_block() -> u128 { + T::RequestsPerBlock::get() + } + + pub fn verify_sequencer_update( + chain: ::ChainId, + hash: H256, + request_id: u128, + ) -> Option { + let pending_requests_to_process = PendingSequencerUpdates::::get(request_id, chain); + if let Some(metadata) = pending_requests_to_process { + if let Some(l1_update) = PendingSequencerUpdateContent::::get(metadata.update_hash) { + let calculated_hash = l1_update.abi_encode_hash(); + Some(hash == calculated_hash) + } else { + None + } + } else { + None + } + } + + fn maybe_create_batch(now: BlockNumberFor) { + let batch_size = Self::automatic_batch_size(); + let batch_period: BlockNumberFor = Self::automatic_batch_period().saturated_into(); + + // weight for is_maintenance + if T::MaintenanceStatusProvider::is_maintenance() { + return; + } + + for (chain, next_id) in L2OriginRequestId::::get().iter() { + let last_id = next_id.saturating_sub(1); + + let (last_batch_block_number, last_batch_id, last_id_in_batch) = + L2RequestsBatchLast::::get() + .get(&chain) + .cloned() + .map(|(block_number, batch_id, (_, last_reqeust_id))| { + (block_number, batch_id, last_reqeust_id) + }) + .unwrap_or_default(); + + let trigger = if last_id >= last_id_in_batch + batch_size { + Some(BatchSource::AutomaticSizeReached) + } else if now >= last_batch_block_number + batch_period { + Some(BatchSource::PeriodReached) + } else { + None + }; + + if let Some(trigger) = trigger { + let updater = T::SequencerStakingProvider::selected_sequencer(*chain) + .unwrap_or(T::AddressConverter::convert([0u8; 20])); + let batch_id = last_batch_id.saturating_add(1); + let range_start = last_id_in_batch.saturating_add(1); + let range_end = sp_std::cmp::min( + range_start.saturating_add(batch_size.saturating_sub(1)), + last_id, + ); + if range_end >= range_start { + L2RequestsBatch::::insert( + (chain, batch_id), + (now, (range_start, range_end), updater.clone()), + ); + L2RequestsBatchLast::::mutate(|batches| { + batches.insert(chain.clone(), (now, batch_id, (range_start, range_end))); + }); + Pallet::::deposit_event(Event::TxBatchCreated { + chain: *chain, + source: trigger, + assignee: updater, + batch_id, + range: (range_start, range_end), + }); + break + } + } + } + Default::default() + } + + fn schedule_request_for_execution_if_dispute_period_has_passsed(now: BlockNumberFor) { + let block_number = >::block_number().saturated_into::(); + + for (l1, metadata) in PendingSequencerUpdates::::iter_prefix(block_number) { + let sequencer = metadata.sequencer.clone(); + let l1_read_hash = metadata.update_hash.clone(); + let update_size = metadata.update_size.clone(); + if let Some(dispute_period) = Self::get_dispute_period(l1) { + if T::SequencerStakingProvider::is_active_sequencer(l1, &sequencer) { + SequencersRights::::mutate(l1, |sequencers| { + if let Some(rights) = sequencers.get_mut(&sequencer) { + rights.read_rights.saturating_accrue(T::RightsMultiplier::get()); + } + }); + } + + let update_creation_block = block_number.saturating_sub(dispute_period); + + match LastMaintananceMode::::get() { + Some(last_maintanance_mode) + if update_creation_block <= last_maintanance_mode => + { + Self::deposit_event(Event::L1ReadIgnoredBecauseOfMaintenanceMode { + chain: l1, + hash: l1_read_hash, + }); + }, + _ => { + Self::schedule_requests(now, l1, metadata); + Self::deposit_event(Event::L1ReadScheduledForExecution { + chain: l1, + hash: l1_read_hash, + }); + }, + } + } else { + Self::deposit_event(Event::L1ReadIgnoredBecauseOfUnknownDisputePeriod { + chain: l1, + hash: l1_read_hash, + }); + } + } + + let _ = PendingSequencerUpdates::::clear_prefix(block_number, u32::MAX, None); + } + + fn process_single_request( + l1: ::ChainId, + request: &messages::L1UpdateRequest, + ) -> bool { + let request_id = request.id(); + if request_id <= LastProcessedRequestOnL2::::get(l1) { + return true; + } + + let status = match request.clone() { + messages::L1UpdateRequest::Deposit(deposit) => { + let deposit_status = Self::process_deposit(l1, &deposit); + TotalNumberOfDeposits::::mutate(|v| *v = v.saturating_add(One::one())); + deposit_status.or_else(|err| { + let who: T::AccountId = T::AddressConverter::convert(deposit.depositRecipient); + FailedL1Deposits::::insert( + (l1, deposit.requestId.id), + (who, deposit.abi_encode_hash()), + ); + Err(err.into()) + }) + }, + messages::L1UpdateRequest::CancelResolution(cancel) => + Self::process_cancel_resolution(l1, &cancel).or_else(|err| { + T::MaintenanceStatusProvider::trigger_maintanance_mode(); + Err(err) + }), + }; + + Pallet::::deposit_event(Event::RequestProcessedOnL2 { + chain: l1, + request_id, + status: status.clone(), + }); + LastProcessedRequestOnL2::::insert(l1, request.id()); + status.is_ok() + } + + fn get_current_update_size_from_execution_queue() -> Option { + UpdatesExecutionQueue::::get(UpdatesExecutionQueueNextId::::get()) + .map(|(_, _, _, size)| size) + } + + fn load_next_update_from_execution_queue() -> bool { + let current_execution_id = UpdatesExecutionQueueNextId::::get(); + let next_execution_id = current_execution_id.saturating_add(1u128); + match ( + UpdatesExecutionQueue::::get(current_execution_id), + UpdatesExecutionQueue::::get(next_execution_id), + ) { + (None, Some(_)) => { + UpdatesExecutionQueueNextId::::mutate(Saturating::saturating_inc); + true + }, + _ => false, + } + } + + fn execute_requests_from_execute_queue() -> Vec { + let limit = Self::get_max_requests_per_block(); + match ( + UpdatesExecutionQueue::::get(UpdatesExecutionQueueNextId::::get()), + LastMaintananceMode::::get(), + ) { + (Some((scheduled_at, _, _, _)), Some(last_maintanance_mode)) + if scheduled_at.saturated_into::() <= last_maintanance_mode => + { + UpdatesExecutionQueue::::remove(UpdatesExecutionQueueNextId::::get()); + UpdatesExecutionQueueNextId::::mutate(Saturating::saturating_inc); + Default::default() + }, + (Some((_, l1, hash, _)), _) => { + if let Some(update) = PendingSequencerUpdateContent::::get(hash) { + let requests = update + .into_requests() + .into_iter() + .filter(|request| request.id() > LastProcessedRequestOnL2::::get(l1)) + .take(limit.saturated_into()) + .collect::>(); + + if requests.is_empty() { + UpdatesExecutionQueue::::remove(UpdatesExecutionQueueNextId::::get()); + PendingSequencerUpdateContent::::remove(hash); + Self::deposit_event(Event::L1ReadExecuted { chain: l1, hash }); + Default::default() + } else { + for r in requests.iter() { + if !Self::process_single_request(l1, &r) { + // maintanance mode triggered + break; + } + } + requests + } + } else { + UpdatesExecutionQueue::::remove(UpdatesExecutionQueueNextId::::get()); + Default::default() + } + }, + _ => Default::default(), + } + } + + fn schedule_requests( + now: BlockNumberFor, + chain: ::ChainId, + metadata: UpdateMetadata, + ) { + MaxAcceptedRequestIdOnl2::::mutate(chain, |cnt| { + *cnt = sp_std::cmp::max(*cnt, metadata.max_id) + }); + + let id = LastScheduledUpdateIdInExecutionQueue::::mutate(|id| { + id.saturating_inc(); + *id + }); + let size = metadata.max_id.saturating_sub(metadata.min_id).saturating_add(1); + UpdatesExecutionQueue::::insert(id, (now, chain, metadata.update_hash, size)); + } + + /// !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + /// NOTE: This function is not transactional, so even if it fails at some point that DOES NOT + /// REVERT PREVIOUS CHANGES TO STORAGE, whoever is modifying it should take that into account! + /// !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + fn process_deposit( + l1: ::ChainId, + deposit: &messages::Deposit, + ) -> Result<(), L1DepositProcessingError> { + let amount = TryInto::::try_into(deposit.amount) + .map_err(|_| L1DepositProcessingError::Overflow)? + .try_into() + .map_err(|_| L1DepositProcessingError::Overflow)?; + + let eth_asset = T::AssetAddressConverter::convert((l1, deposit.tokenAddress)); + + let asset_id = match T::AssetRegistryProvider::get_l1_asset_id(eth_asset.clone()) { + Some(id) => id, + None => T::AssetRegistryProvider::create_l1_asset(eth_asset) + .map_err(|_| L1DepositProcessingError::AssetRegistrationProblem)?, + }; + + let account = FerriedDeposits::::get((l1, deposit.abi_encode_hash())) + .unwrap_or(T::AddressConverter::convert(deposit.depositRecipient)); + + T::Tokens::mint(asset_id, &account, amount) + .map_err(|_| L1DepositProcessingError::MintError)?; + + Ok(()) + } + + /// !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + /// NOTE: This function is not transactional, so even if it fails at some point that DOES NOT + /// REVERT PREVIOUS CHANGES TO STORAGE, whoever is modifying it should take that into account! + /// !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + fn process_cancel_resolution( + l1: ::ChainId, + cancel_resolution: &messages::CancelResolution, + ) -> Result<(), L1RequestProcessingError> { + let cancel_request_id = cancel_resolution.l2RequestId; + let cancel_justified = cancel_resolution.cancelJustified; + + let cancel_update = + match L2Requests::::get(l1, RequestId::from((Origin::L2, cancel_request_id))) { + Some((L2Request::Cancel(cancel), _)) => Some(cancel), + _ => None, + } + .ok_or(L1RequestProcessingError::WrongCancelRequestId)?; + + let updater = cancel_update.updater; + let canceler = cancel_update.canceler; + let (to_be_slashed, to_reward) = if cancel_justified { + (updater.clone(), Some(canceler.clone())) + } else { + (canceler.clone(), None) + }; + + if T::SequencerStakingProvider::is_active_sequencer(l1, &updater) { + SequencersRights::::mutate(l1, |sequencers| { + if let Some(rights) = sequencers.get_mut(&updater) { + rights.read_rights.saturating_inc(); + } + }); + } + if T::SequencerStakingProvider::is_active_sequencer(l1, &canceler) { + SequencersRights::::mutate(l1, |sequencers| { + if let Some(rights) = sequencers.get_mut(&canceler) { + rights.cancel_rights.saturating_inc(); + } + }); + } + + AwaitingCancelResolution::::mutate(l1, |v| { + v.remove(&(updater, cancel_request_id, DisputeRole::Submitter)) + }); + AwaitingCancelResolution::::mutate(l1, |v| { + v.remove(&(canceler, cancel_request_id, DisputeRole::Canceler)) + }); + + // slash is after adding rights, since slash can reduce stake below required level and remove all rights + T::SequencerStakingProvider::slash_sequencer(l1, &to_be_slashed, to_reward.as_ref()) + .map_err(|_| L1RequestProcessingError::SequencerNotSlashed)?; + + Ok(()) + } + + fn handle_sequencer_deactivation( + chain: ::ChainId, + deactivated_sequencers: BTreeSet, + ) { + SequencersRights::::mutate(chain, |sequencers_set| { + let mut removed: usize = 0; + for seq in deactivated_sequencers.iter() { + if sequencers_set.remove(seq).is_some() { + removed.saturating_inc(); + } + } + + for (_, rights) in sequencers_set.iter_mut() { + rights.cancel_rights = rights + .cancel_rights + .saturating_sub(T::RightsMultiplier::get().saturating_mul(removed as u128)); + } + }); + } + + pub fn convert_eth_l1update_to_substrate_l1update( + payload: Vec, + ) -> Result { + messages::eth_abi::L1Update::abi_decode(payload.as_ref(), true) + .map_err(|err| format!("Failed to decode L1Update: {}", err)) + .and_then(|update| { + update.try_into().map_err(|err| format!("Failed to convert L1Update: {}", err)) + }) + } + + pub fn validate_l1_update( + l1: ::ChainId, + update: &messages::L1Update, + sequencer: T::AccountId, + ) -> Result, Error> { + ensure!( + !update.pendingDeposits.is_empty() || !update.pendingCancelResolutions.is_empty(), + Error::::EmptyUpdate + ); + + ensure!( + update + .pendingDeposits + .iter() + .map(|v| v.requestId.origin) + .all(|v| v == Origin::L1), + Error::::InvalidUpdate + ); + ensure!( + update + .pendingCancelResolutions + .iter() + .map(|v| v.requestId.origin) + .all(|v| v == Origin::L1), + Error::::InvalidUpdate + ); + + // check that consecutive id + ensure!( + update + .pendingDeposits + .iter() + .map(|v| v.requestId.id) + .into_iter() + .tuple_windows() + .all(|(a, b)| a < b), + Error::::InvalidUpdate + ); + + ensure!( + update + .pendingCancelResolutions + .iter() + .map(|v| v.requestId.id) + .into_iter() + .tuple_windows() + .all(|(a, b)| a < b), + Error::::InvalidUpdate + ); + + let lowest_id = [ + update.pendingDeposits.first().map(|v| v.requestId.id), + update.pendingCancelResolutions.first().map(|v| v.requestId.id), + ] + .iter() + .filter_map(|v| v.clone()) + .into_iter() + .min() + .ok_or(Error::::InvalidUpdate)?; + + ensure!(lowest_id > 0u128, Error::::WrongRequestId); + + ensure!( + lowest_id <= LastProcessedRequestOnL2::::get(l1) + 1, + Error::::WrongRequestId + ); + + let last_id = lowest_id.saturating_sub(1u128) + + (update.pendingDeposits.len() as u128) + + (update.pendingCancelResolutions.len() as u128); + + ensure!(last_id >= LastProcessedRequestOnL2::::get(l1), Error::::WrongRequestId); + + let mut deposit_it = update.pendingDeposits.iter(); + let mut cancel_it = update.pendingCancelResolutions.iter(); + + let mut deposit = deposit_it.next(); + let mut cancel = cancel_it.next(); + + for id in (lowest_id..last_id).into_iter() { + match (deposit, cancel) { + (Some(d), _) if d.requestId.id == id => { + deposit = deposit_it.next(); + }, + (_, Some(c)) if c.requestId.id == id => { + cancel = cancel_it.next(); + }, + _ => return Err(Error::::InvalidUpdate.into()), + } + } + + Ok(UpdateMetadata { + sequencer, + update_hash: update.abi_encode_hash(), + update_size: update.pendingDeposits.len() as u128 + + update.pendingCancelResolutions.len() as u128, + min_id: lowest_id, + max_id: last_id, + }) + } + + pub fn update_impl(sequencer: T::AccountId, read: messages::L1Update) -> DispatchResult { + // let l1 = read.chain; + let l1 = read.chain.into(); + ensure!( + !T::MaintenanceStatusProvider::is_maintenance(), + Error::::BlockedByMaintenanceMode + ); + + ensure!( + T::SequencerStakingProvider::is_selected_sequencer(l1, &sequencer), + Error::::OnlySelectedSequencerisAllowedToUpdate + ); + let metadata = Self::validate_l1_update(l1, &read, sequencer.clone())?; + + // check json length to prevent big data spam, maybe not necessary as it will be checked later and slashed + let current_block_number = + >::block_number().saturated_into::(); + let dispute_period_length = + Self::get_dispute_period(l1).ok_or(Error::::UninitializedChainId)?; + + let dispute_period_end: u128 = current_block_number + dispute_period_length; + + // ensure sequencer has rights to update + if let Some(rights) = SequencersRights::::get(&l1).get(&sequencer) { + if rights.read_rights == 0u128 { + log!(debug, "{:?} does not have sufficient read_rights", sequencer); + return Err(Error::::OperationFailed.into()) + } + } else { + log!(debug, "{:?} not a sequencer, CHEEKY BASTARD!", sequencer); + return Err(Error::::OperationFailed.into()) + } + + // // Decrease read_rights by 1 + SequencersRights::::mutate(l1, |sequencers_set| { + if let Some(rights) = sequencers_set.get_mut(&sequencer) { + rights.read_rights -= 1; + } + }); + + ensure!( + !PendingSequencerUpdates::::contains_key(dispute_period_end, l1), + Error::::MultipleUpdatesInSingleBlock + ); + + PendingSequencerUpdates::::insert(dispute_period_end, l1, metadata.clone()); + + PendingSequencerUpdateContent::::insert(metadata.update_hash, read.clone()); + + LastUpdateBySequencer::::insert((l1, &sequencer), current_block_number); + + let requests_range = read.range().ok_or(Error::::InvalidUpdate)?; + + Pallet::::deposit_event(Event::L1ReadStored { + chain: l1, + sequencer: sequencer.clone(), + dispute_period_end, + range: requests_range, + hash: metadata.update_hash, + }); + + // 2 storage reads & writes in seqs pallet + T::SequencerStakingRewards::note_update_author(&sequencer); + + Ok(().into()) + } + + fn count_of_read_rights_under_dispute(chain: ChainIdOf, sequencer: &AccountIdOf) -> u128 { + let mut read_rights = 0u128; + let last_update = LastUpdateBySequencer::::get((chain, sequencer)); + let dispute_period = Self::get_dispute_period(chain).unwrap_or(u128::MAX); + + if last_update != 0 && + last_update.saturating_add(dispute_period) >= + >::block_number().saturated_into::() + { + read_rights += 1; + } + + read_rights.saturating_accrue( + AwaitingCancelResolution::::get(chain) + .iter() + .filter(|(acc, _, role)| acc == sequencer && *role == DisputeRole::Submitter) + .count() as u128, + ); + + read_rights + } + + fn count_of_cancel_rights_under_dispute( + chain: ChainIdOf, + sequencer: &AccountIdOf, + ) -> usize { + AwaitingCancelResolution::::get(chain) + .iter() + .filter(|(acc, _, role)| acc == sequencer && *role == DisputeRole::Canceler) + .count() + } + + pub fn create_merkle_tree( + chain: ChainIdOf, + range: (u128, u128), + ) -> Option> { + let l2_requests = (range.0..=range.1) + .into_iter() + .map(|id| match L2Requests::::get(chain, RequestId { origin: Origin::L2, id }) { + Some((_, hash)) => Some(hash.into()), + None => None, + }) + .collect::>>(); + + l2_requests.map(|txs| MerkleTree::::from_leaves(txs.as_ref())) + } + + pub fn get_merkle_root(chain: ChainIdOf, range: (u128, u128)) -> H256 { + if let Some(tree) = Self::create_merkle_tree(chain, range) { + H256::from(tree.root().unwrap_or_default()) + } else { + H256::from([0u8; 32]) + } + } + + pub fn get_merkle_proof_for_tx( + chain: ChainIdOf, + range: (u128, u128), + tx_id: u128, + ) -> Vec { + if tx_id < range.0 || tx_id > range.1 { + return Default::default() + } + + let tree = Self::create_merkle_tree(chain, range); + if let Some(merkle_tree) = tree { + let idx = tx_id as usize - range.0 as usize; + let indices_to_prove = vec![idx]; + let merkle_proof = merkle_tree.proof(&indices_to_prove); + merkle_proof.proof_hashes().iter().map(|hash| H256::from(hash)).collect() + } else { + Default::default() + } + } + + pub fn max_id(chain: ChainIdOf, range: (u128, u128)) -> u128 { + let mut max_id = 0u128; + for id in range.0..=range.1 { + if let Some((L2Request::Withdrawal(withdrawal), _)) = + L2Requests::::get(chain, RequestId { origin: Origin::L2, id }) + { + if withdrawal.requestId.id > max_id { + max_id = withdrawal.requestId.id; + } + } + } + max_id + } + + pub(crate) fn automatic_batch_size() -> u128 { + ::MerkleRootAutomaticBatchSize::get() + } + + pub(crate) fn automatic_batch_period() -> u128 { + ::MerkleRootAutomaticBatchPeriod::get() + } + + fn acquire_l2_request_id(chain: ChainIdOf) -> u128 { + L2OriginRequestId::::mutate(|val| { + // request ids start from id == 1 + val.entry(chain).or_insert(1u128); + let id = val[&chain]; + val.entry(chain).and_modify(|v| v.saturating_inc()); + id + }) + } + + #[cfg(test)] + fn get_next_l2_request_id(chain: ChainIdOf) -> u128 { + L2OriginRequestId::::get().get(&chain).cloned().unwrap_or(1u128) + } + + fn get_latest_l2_request_id(chain: ChainIdOf) -> Option { + L2OriginRequestId::::get().get(&chain).cloned().map(|v| v.saturating_sub(1)) + } + + pub fn verify_merkle_proof_for_tx( + chain: ChainIdOf, + range: (u128, u128), + root_hash: H256, + tx_id: u128, + proof: Vec, + ) -> bool { + let proof = + MerkleProof::::new(proof.into_iter().map(Into::into).collect()); + + let inclusive_range = range.0..=range.1; + if !inclusive_range.contains(&tx_id) { + return false + } + + let pos = inclusive_range.clone().position(|elem| elem == tx_id); + let request = L2Requests::::get(chain, RequestId { origin: Origin::L2, id: tx_id }); + if let (Some((req, _)), Some(pos)) = (request, pos) { + proof.verify( + root_hash.into(), + &[pos], + &[req.abi_encode_hash().into()], + inclusive_range.count(), + ) + } else { + false + } + } + + pub(crate) fn treasury_account_id() -> T::AccountId { + T::TreasuryPalletId::get().into_account_truncating() + } + + fn native_token_id() -> CurrencyIdOf { + ::NativeCurrencyId::get() + } + + pub fn get_abi_encoded_l2_request(chain: ChainIdOf, request_id: u128) -> Vec { + match L2Requests::::get(chain, RequestId::from((Origin::L2, request_id))) { + Some((L2Request::FailedDepositResolution(deposit), _)) => deposit.abi_encode(), + Some((L2Request::Cancel(cancel), _)) => cancel.abi_encode(), + Some((L2Request::Withdrawal(withdrawal), _)) => withdrawal.abi_encode(), + None => Default::default(), + } + } + + fn get_batch_range_from_available_requests( + chain: ChainIdOf, + ) -> Result<(u128, u128), Error> { + let last_request_id = L2RequestsBatchLast::::get() + .get(&chain) + .cloned() + .map(|(_block_number, _batch_id, range)| range.1) + .unwrap_or_default(); + let range_start = last_request_id.saturating_add(1u128); + let latest_req_id = Self::get_latest_l2_request_id(chain).ok_or(Error::::EmptyBatch)?; + + let range_end = sp_std::cmp::min( + range_start.saturating_add(Self::automatic_batch_size().saturating_sub(1)), + latest_req_id, + ); + + if L2Requests::::contains_key(chain, RequestId { origin: Origin::L2, id: range_start }) { + Ok((range_start, range_end)) + } else { + Err(Error::::EmptyBatch) + } + } + + fn get_next_batch_id(chain: ChainIdOf) -> u128 { + let last_batch_id = L2RequestsBatchLast::::get() + .get(&chain) + .cloned() + .map(|(_block_number, batch_id, _range)| batch_id) + .unwrap_or_default(); + last_batch_id.saturating_add(1u128) + } + + fn persist_batch_and_deposit_event( + chain: ChainIdOf, + range: (u128, u128), + asignee: T::AccountId, + ) { + let now: BlockNumberFor = >::block_number(); + let batch_id = Self::get_next_batch_id(chain); + + L2RequestsBatch::::insert((chain, batch_id), (now, (range.0, range.1), asignee.clone())); + + L2RequestsBatchLast::::mutate(|batches| { + batches.insert(chain.clone(), (now, batch_id, (range.0, range.1))); + }); + + Pallet::::deposit_event(Event::TxBatchCreated { + chain, + source: BatchSource::Manual, + assignee: asignee.clone(), + batch_id, + range: (range.0, range.1), + }); + } + + /// Deduces account that batch should be assigened to + fn get_batch_asignee( + chain: ChainIdOf, + sender: &T::AccountId, + sequencer_account: Option, + ) -> Result> { + if let Some(sequencer) = sequencer_account { + if T::SequencerStakingProvider::is_active_sequencer_alias(chain, &sequencer, sender) { + Ok(sequencer) + } else { + Err(Error::::UnknownAliasAccount) + } + } else { + Ok(sender.clone()) + } + } + + fn ferry_desposit_impl( + sender: T::AccountId, + chain: ::ChainId, + deposit: messages::Deposit, + ) -> Result<(), Error> { + ensure!( + !T::MaintenanceStatusProvider::is_maintenance(), + Error::::BlockedByMaintenanceMode + ); + + let deposit_hash = deposit.abi_encode_hash(); + + if deposit.requestId.id <= LastProcessedRequestOnL2::::get(chain) { + return Err(Error::::AlreadyExecuted); + } + + ensure!( + !FerriedDeposits::::contains_key((chain, deposit_hash)), + Error::::AlreadyFerried + ); + + let amount = deposit + .amount + .checked_sub(deposit.ferryTip) + .and_then(|v| TryInto::::try_into(v).ok()) + .and_then(|v| TryInto::>::try_into(v).ok()) + .ok_or(Error::::MathOverflow)?; + + let eth_asset = T::AssetAddressConverter::convert((chain, deposit.tokenAddress)); + let asset_id = match T::AssetRegistryProvider::get_l1_asset_id(eth_asset.clone()) { + Some(id) => id, + None => T::AssetRegistryProvider::create_l1_asset(eth_asset) + .map_err(|_| Error::::AssetRegistrationProblem)?, + }; + + let account = T::AddressConverter::convert(deposit.depositRecipient); + + T::Tokens::transfer(asset_id, &sender, &account, amount, ExistenceRequirement::KeepAlive) + .map_err(|_| Error::::NotEnoughAssets)?; + FerriedDeposits::::insert((chain, deposit_hash), sender); + + Self::deposit_event(Event::DepositFerried { chain, deposit, deposit_hash }); + + Ok(().into()) + } +} + +impl RolldownProviderTrait, AccountIdOf> for Pallet { + fn new_sequencer_active(chain: ::ChainId, sequencer: &AccountIdOf) { + SequencersRights::::mutate(chain, |sequencer_set| { + let count = sequencer_set.len() as u128; + + sequencer_set.insert( + sequencer.clone(), + SequencerRights { + read_rights: T::RightsMultiplier::get().saturating_sub( + Pallet::::count_of_read_rights_under_dispute(chain, sequencer), + ), + cancel_rights: count.saturating_mul(T::RightsMultiplier::get()).saturating_sub( + Pallet::::count_of_cancel_rights_under_dispute(chain, sequencer) as u128, + ), + }, + ); + + let sequencer_count = (sequencer_set.len() as u128).saturating_sub(1u128); + + for (s, rights) in sequencer_set.iter_mut().filter(|(s, _)| *s != sequencer) { + rights.cancel_rights = + T::RightsMultiplier::get().saturating_mul(sequencer_count).saturating_sub( + Pallet::::count_of_cancel_rights_under_dispute(chain, s) as u128, + ) + } + }); + } + + fn sequencer_unstaking( + chain: ::ChainId, + sequencer: &AccountIdOf, + ) -> DispatchResult { + ensure!( + Pallet::::count_of_read_rights_under_dispute(chain, sequencer).is_zero(), + Error::::SequencerLastUpdateStillInDisputePeriod + ); + + ensure!( + Pallet::::count_of_cancel_rights_under_dispute(chain, sequencer).is_zero(), + Error::::SequencerAwaitingCancelResolution + ); + + LastUpdateBySequencer::::remove((chain, &sequencer)); + + Ok(()) + } + + fn handle_sequencer_deactivations( + chain: ::ChainId, + deactivated_sequencers: Vec, + ) { + Pallet::::handle_sequencer_deactivation( + chain, + deactivated_sequencers.into_iter().collect(), + ); + } +} + +pub struct MultiEvmChainAddressConverter; +impl Convert<(messages::Chain, [u8; 20]), L1Asset> for MultiEvmChainAddressConverter { + fn convert((chain, address): (messages::Chain, [u8; 20])) -> L1Asset { + match chain { + messages::Chain::Ethereum => L1Asset::Ethereum(address), + messages::Chain::Arbitrum => L1Asset::Arbitrum(address), + messages::Chain::Base => L1Asset::Base(address), + } + } +} diff --git a/gasp-node/pallets/rolldown/src/messages/eth_abi.rs b/gasp-node/pallets/rolldown/src/messages/eth_abi.rs new file mode 100644 index 000000000..47c1ece6e --- /dev/null +++ b/gasp-node/pallets/rolldown/src/messages/eth_abi.rs @@ -0,0 +1,311 @@ +use alloy_sol_types::sol; +use codec::{Decode, Encode}; +use scale_info::TypeInfo; +use sp_core::U256; +use sp_std::vec::Vec; + +pub fn to_eth_u256(value: sp_core::U256) -> alloy_primitives::U256 { + let mut bytes = [0u8; 32]; + value.to_big_endian(&mut bytes); + alloy_primitives::U256::from_be_bytes(bytes) +} + +pub fn from_eth_u256(value: alloy_primitives::U256) -> U256 { + let bytes: [u8; 32] = value.to_be_bytes(); + let mut buf = [0u8; 32]; + buf.copy_from_slice(&bytes[..]); + U256::from_big_endian(&buf) +} + +impl From> for Cancel { + fn from(cancel: crate::Cancel) -> Self { + Self { + requestId: cancel.requestId.into(), + range: cancel.range.into(), + hash: alloy_primitives::FixedBytes::<32>::from_slice(&cancel.hash[..]), + } + } +} + +impl From for FailedDepositResolution { + fn from(failed_deposit_resolution: crate::FailedDepositResolution) -> Self { + Self { + requestId: failed_deposit_resolution.requestId.into(), + originRequestId: crate::messages::to_eth_u256( + failed_deposit_resolution.originRequestId.into(), + ), + ferry: failed_deposit_resolution.ferry.into(), + } + } +} + +impl From for Withdrawal { + fn from(withdrawal: crate::Withdrawal) -> Self { + Self { + requestId: withdrawal.requestId.into(), + withdrawalRecipient: withdrawal.withdrawalRecipient.into(), + tokenAddress: withdrawal.tokenAddress.into(), + amount: crate::messages::to_eth_u256(withdrawal.amount.into()), + ferryTip: crate::messages::to_eth_u256(withdrawal.ferryTip.into()), + } + } +} + +impl From for Chain { + fn from(c: crate::messages::Chain) -> Chain { + match c { + crate::messages::Chain::Ethereum => Chain::Ethereum, + crate::messages::Chain::Arbitrum => Chain::Arbitrum, + crate::messages::Chain::Base => Chain::Base, + } + } +} + +impl From for Origin { + fn from(c: crate::messages::Origin) -> Origin { + match c { + crate::messages::Origin::L1 => Origin::L1, + crate::messages::Origin::L2 => Origin::L2, + } + } +} + +impl From for Range { + fn from(range: crate::messages::Range) -> Range { + Range { start: to_eth_u256(range.start.into()), end: to_eth_u256(range.end.into()) } + } +} + +impl From for RequestId { + fn from(rid: crate::messages::RequestId) -> RequestId { + RequestId { origin: rid.origin.into(), id: to_eth_u256(U256::from(rid.id)) } + } +} + +impl From for CancelResolution { + fn from(cancel: crate::messages::CancelResolution) -> CancelResolution { + CancelResolution { + requestId: cancel.requestId.into(), + l2RequestId: to_eth_u256(cancel.l2RequestId.into()), + cancelJustified: cancel.cancelJustified.into(), + timeStamp: to_eth_u256(cancel.timeStamp), + } + } +} + +impl From for L1Update { + fn from(update: crate::messages::L1Update) -> L1Update { + L1Update { + chain: update.chain.into(), + pendingDeposits: update.pendingDeposits.into_iter().map(Into::into).collect::>(), + pendingCancelResolutions: update + .pendingCancelResolutions + .into_iter() + .map(Into::into) + .collect::>(), + } + } +} + +impl From for Deposit { + fn from(deposit: crate::messages::Deposit) -> Deposit { + Deposit { + requestId: deposit.requestId.into(), + depositRecipient: deposit.depositRecipient.into(), + tokenAddress: deposit.tokenAddress.into(), + amount: to_eth_u256(deposit.amount), + timeStamp: to_eth_u256(deposit.timeStamp), + ferryTip: to_eth_u256(deposit.ferryTip), + } + } +} + +sol! { + // L1 to L2 + #[derive(Debug)] + struct Deposit { + RequestId requestId; + address depositRecipient; + address tokenAddress; + uint256 amount; + uint256 timeStamp; + uint256 ferryTip; + } + + + #[derive(Debug)] + struct CancelResolution { + RequestId requestId; + uint256 l2RequestId; + bool cancelJustified; + uint256 timeStamp; + } + + #[derive(Debug, Eq, PartialEq, Encode, Decode, TypeInfo)] + enum Chain{ Ethereum, Arbitrum, Base } + + #[derive(Debug, Eq, PartialEq, Encode, Decode, TypeInfo)] + enum L2RequestType{ Withdrawal, Cancel, FailedDepositResolution } + + #[derive(Debug)] + struct L1Update { + Chain chain; + Deposit[] pendingDeposits; + CancelResolution[] pendingCancelResolutions; + } + + #[derive(Debug, Eq, PartialEq, Encode, Decode, TypeInfo)] + enum Origin{ L1, L2 } + + #[derive(Debug, Eq, PartialEq)] + struct RequestId { + Origin origin; + uint256 id; + } + + #[derive(Debug, PartialEq)] + struct FailedDepositResolution { + RequestId requestId; + uint256 originRequestId; + address ferry; + } + + #[derive(Debug, PartialEq)] + struct Withdrawal { + RequestId requestId; + address withdrawalRecipient; + address tokenAddress; + uint256 amount; + uint256 ferryTip; + } + + #[derive(Debug, PartialEq)] + struct Range{ + uint256 start; + uint256 end; + } + + #[derive(Debug, PartialEq)] + struct Cancel { + RequestId requestId; + Range range; + bytes32 hash; + } + +} + +#[cfg(test)] +mod test { + use super::*; + use alloy_sol_types::SolValue; + use hex_literal::hex; + use serial_test::serial; + use sp_crypto_hashing::keccak_256; + + pub trait Keccak256Hash { + fn keccak256_hash(&self) -> [u8; 32]; + } + + impl Keccak256Hash for T + where + T: SolValue, + { + fn keccak256_hash(&self) -> [u8; 32] { + Into::<[u8; 32]>::into(keccak_256(&self.abi_encode()[..])) + } + } + + #[test] + fn test_conversion_u256__small() { + let val = sp_core::U256::from(1u8); + let eth_val = alloy_primitives::U256::from(1u8); + assert_eq!(to_eth_u256(val), eth_val); + } + + #[test] + fn test_conversion_u256__big() { + let val = sp_core::U256::from([u8::MAX; 32]); + assert_eq!(from_eth_u256(to_eth_u256(val)), val); + } + + /// !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + /// NOTE: Below hash values should not be ever chaned, there are comaptible test implemented in eigen monorepo + /// to ensure abi compatibility between L1 & L2 + /// !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + #[test] + #[serial] + // this test ensures that the hash calculated on rust side matches hash calculated in contract + fn test_l1_update_hash_compare_with_solidty() { + assert_eq!( + L1Update { + chain: Chain::Ethereum, + pendingDeposits: vec![Deposit { + requestId: RequestId { + origin: Origin::L1, + id: alloy_primitives::U256::from(1) + }, + depositRecipient: hex!("1111111111111111111111111111111111111111").into(), + tokenAddress: hex!("2222222222222222222222222222222222222222").into(), + amount: alloy_primitives::U256::from(123456), + timeStamp: alloy_primitives::U256::from(987), + ferryTip: alloy_primitives::U256::from(321987) + }], + pendingCancelResolutions: vec![CancelResolution { + requestId: RequestId { + origin: Origin::L1, + id: alloy_primitives::U256::from(123) + }, + l2RequestId: alloy_primitives::U256::from(123456), + cancelJustified: true, + timeStamp: alloy_primitives::U256::from(987) + }], + } + .keccak256_hash(), + hex!("663fa3ddfe64659f67b2728637936fa8d21f18ef96c07dec110cdd8f45be6fee"), + ); + } + + #[test] + #[serial] + fn test_calculate_chain_hash() { + assert_eq!( + Chain::Ethereum.keccak256_hash(), + hex!("290decd9548b62a8d60345a988386fc84ba6bc95484008f6362f93160ef3e563"), + ); + + assert_eq!( + Chain::Arbitrum.keccak256_hash(), + hex!("b10e2d527612073b26eecdfd717e6a320cf44b4afac2b0732d9fcbe2b7fa0cf6"), + ); + } + + #[test] + #[serial] + fn test_calculate_withdrawal_hash() { + assert_eq!( + Withdrawal { + requestId: RequestId { origin: Origin::L2, id: alloy_primitives::U256::from(123) }, + withdrawalRecipient: hex!("ffffffffffffffffffffffffffffffffffffffff").into(), + tokenAddress: hex!("1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f").into(), + amount: alloy_primitives::U256::from(123456), + ferryTip: alloy_primitives::U256::from(465789) + } + .keccak256_hash(), + hex!("a931da68c445f23b06a72768d07a3513f85c0118ff80f6e284117a221869ae8b"), + ); + } + + #[test] + #[serial] + fn test_calculate_failed_deposit_resolution_hash() { + assert_eq!( + FailedDepositResolution { + requestId: RequestId { origin: Origin::L1, id: alloy_primitives::U256::from(123) }, + originRequestId: alloy_primitives::U256::from(1234), + ferry: hex!("b5b5b5b5b5b5b5b5b5b5b5b5b5b5b5b5b5b5b5b5").into() + } + .keccak256_hash(), + hex!("d3def31efb42dd99500c389f59115f0eef5e008db0ee0a81562ef3acbe02eece"), + ); + } +} diff --git a/gasp-node/pallets/rolldown/src/messages/mod.rs b/gasp-node/pallets/rolldown/src/messages/mod.rs new file mode 100644 index 000000000..59d83d63f --- /dev/null +++ b/gasp-node/pallets/rolldown/src/messages/mod.rs @@ -0,0 +1,455 @@ +#![allow(non_snake_case)] + +pub mod eth_abi; + +use self::eth_abi::to_eth_u256; +use crate::L2Request; +use alloy_sol_types::SolValue; +use codec::{Decode, Encode, MaxEncodedLen}; +use eth_abi::from_eth_u256; +use scale_info::{ + prelude::{format, string::String}, + TypeInfo, +}; +use serde::{Deserialize, Serialize}; +use sp_core::{RuntimeDebug, H256, U256}; +use sp_crypto_hashing::keccak_256; +use sp_std::{ + convert::{TryFrom, TryInto}, + vec::Vec, +}; + +pub trait NativeToEthMapping { + type EthType: SolValue; +} + +pub trait EthAbi { + fn abi_encode(&self) -> Vec; +} + +pub trait EthAbiHash { + fn abi_encode_hash(&self) -> H256; +} + +impl EthAbiHash for T +where + T: EthAbi, +{ + fn abi_encode_hash(&self) -> H256 { + let encoded = self.abi_encode(); + let hash: [u8; 32] = keccak_256(&encoded[..]).into(); + H256::from(hash) + } +} + +impl EthAbi for T +where + T: Clone, + T: NativeToEthMapping, + T: Into, + T::EthType: SolValue, +{ + fn abi_encode(&self) -> Vec { + let eth_type: ::EthType = (self.clone()).into(); + eth_type.abi_encode() + } +} + +impl EthAbi for L2Request { + fn abi_encode(&self) -> Vec { + match self { + L2Request::FailedDepositResolution(deposit) => + eth_abi::L2RequestType::FailedDepositResolution + .abi_encode() + .iter() + .chain(deposit.abi_encode().iter()) + .cloned() + .collect(), + L2Request::Cancel(cancel) => eth_abi::L2RequestType::Cancel + .abi_encode() + .iter() + .chain(cancel.abi_encode().iter()) + .cloned() + .collect(), + L2Request::Withdrawal(withdrawal) => eth_abi::L2RequestType::Withdrawal + .abi_encode() + .iter() + .chain(withdrawal.abi_encode().iter()) + .cloned() + .collect(), + } + } +} + +#[repr(u8)] +#[derive( + Default, + Copy, + Eq, + PartialEq, + RuntimeDebug, + Clone, + Encode, + Decode, + TypeInfo, + MaxEncodedLen, + Serialize, + Deserialize, + Ord, + PartialOrd, +)] +pub enum Chain { + #[default] + Ethereum, + Arbitrum, + Base, +} + +#[repr(u8)] +#[derive( + Default, Eq, PartialEq, RuntimeDebug, Clone, Encode, Decode, TypeInfo, Serialize, Copy, +)] +pub enum Origin { + #[default] + L1, + L2, +} + +#[derive(Eq, PartialEq, RuntimeDebug, Clone, Encode, Decode, TypeInfo, Serialize, Copy)] +pub struct Range { + pub start: u128, + pub end: u128, +} + +impl From<(u128, u128)> for Range { + fn from((start, end): (u128, u128)) -> Range { + Range { start, end } + } +} + +#[derive( + Eq, PartialEq, RuntimeDebug, Clone, Encode, Decode, TypeInfo, Serialize, Default, Copy, +)] +pub struct RequestId { + pub origin: Origin, + pub id: u128, +} + +impl RequestId { + pub fn new(origin: Origin, id: u128) -> Self { + Self { origin, id } + } +} + +impl From<(Origin, u128)> for RequestId { + fn from((origin, id): (Origin, u128)) -> RequestId { + RequestId { origin, id } + } +} + +// L2 to L1 + +#[derive(Eq, PartialEq, RuntimeDebug, Clone, Encode, Decode, TypeInfo, Copy)] +pub struct FailedDepositResolution { + pub requestId: RequestId, + pub originRequestId: u128, + pub ferry: [u8; 20], +} + +#[derive( + Eq, PartialEq, RuntimeDebug, Clone, Encode, Decode, TypeInfo, Serialize, Default, Copy, +)] +pub struct Withdrawal { + pub requestId: RequestId, + pub withdrawalRecipient: [u8; 20], + pub tokenAddress: [u8; 20], + pub amount: U256, + pub ferryTip: U256, +} + +impl NativeToEthMapping for Withdrawal { + type EthType = eth_abi::Withdrawal; +} + +impl From for crate::L2Request { + fn from(w: Withdrawal) -> crate::L2Request { + crate::L2Request::Withdrawal(w) + } +} + +#[derive(Eq, PartialEq, RuntimeDebug, Clone, Encode, Decode, TypeInfo, Serialize, Copy)] +pub struct Cancel { + pub requestId: RequestId, + pub updater: AccountId, + pub canceler: AccountId, + pub range: Range, + pub hash: H256, +} + +impl NativeToEthMapping for Cancel +where + Self: Clone, + AccountId: Clone, +{ + type EthType = eth_abi::Cancel; +} + +impl From> for crate::L2Request { + fn from(cancel: Cancel) -> crate::L2Request { + crate::L2Request::Cancel(cancel) + } +} + +// L1 to L2 messages +#[derive(Eq, PartialEq, RuntimeDebug, Clone, Encode, Decode, TypeInfo, Serialize, Default)] +pub struct Deposit { + pub requestId: RequestId, + pub depositRecipient: [u8; 20], + pub tokenAddress: [u8; 20], + pub amount: U256, + pub timeStamp: U256, + pub ferryTip: U256, +} + +impl NativeToEthMapping for Deposit { + type EthType = eth_abi::Deposit; +} + +#[derive(Eq, PartialEq, RuntimeDebug, Clone, Encode, Decode, TypeInfo, Serialize, Default)] +pub struct CancelResolution { + pub requestId: RequestId, + pub l2RequestId: u128, + pub cancelJustified: bool, + pub timeStamp: sp_core::U256, +} + +impl NativeToEthMapping for FailedDepositResolution { + type EthType = eth_abi::FailedDepositResolution; +} + +#[derive(Eq, PartialEq, RuntimeDebug, Clone, Encode, Decode, Default, TypeInfo, Serialize)] +pub struct L1Update { + pub chain: Chain, + pub pendingDeposits: Vec, + pub pendingCancelResolutions: Vec, +} + +impl L1Update { + pub fn get_requests_count(&self) -> u128 { + self.pendingDeposits.len() as u128 + self.pendingCancelResolutions.len() as u128 + } +} + +impl NativeToEthMapping for L1Update +where + Self: Clone, +{ + type EthType = eth_abi::L1Update; +} + +#[derive(Eq, PartialEq, RuntimeDebug, Clone, Encode, Decode, TypeInfo, Serialize)] +pub enum L1UpdateRequest { + Deposit(Deposit), + CancelResolution(CancelResolution), +} + +impl L1UpdateRequest { + pub fn request_id(&self) -> RequestId { + match self { + L1UpdateRequest::Deposit(deposit) => deposit.requestId.clone(), + L1UpdateRequest::CancelResolution(cancel) => cancel.requestId.clone(), + } + } + + pub fn id(&self) -> u128 { + match self { + L1UpdateRequest::Deposit(deposit) => deposit.requestId.id.clone(), + L1UpdateRequest::CancelResolution(cancel) => cancel.requestId.id.clone(), + } + } + + pub fn origin(&self) -> Origin { + match self { + L1UpdateRequest::Deposit(deposit) => deposit.requestId.origin.clone(), + L1UpdateRequest::CancelResolution(cancel) => cancel.requestId.origin.clone(), + } + } +} + +impl From for L1UpdateRequest { + fn from(deposit: Deposit) -> L1UpdateRequest { + L1UpdateRequest::Deposit(deposit) + } +} + +impl From for L1UpdateRequest { + fn from(cancel: CancelResolution) -> L1UpdateRequest { + L1UpdateRequest::CancelResolution(cancel) + } +} + +impl L1Update { + pub fn range(&self) -> Option { + let first = [ + self.pendingDeposits.first().map(|v| v.requestId.id), + self.pendingCancelResolutions.first().map(|v| v.requestId.id), + ] + .iter() + .cloned() + .filter_map(|v| v) + .min(); + + let last = [ + self.pendingDeposits.last().map(|v| v.requestId.id), + self.pendingCancelResolutions.last().map(|v| v.requestId.id), + ] + .iter() + .cloned() + .filter_map(|v| v) + .max(); + if let (Some(first), Some(last)) = (first, last) { + Some(Range { start: first, end: last }) + } else { + None + } + } + + pub fn into_requests(self) -> Vec { + let mut result: Vec = Default::default(); + + let L1Update { chain, pendingDeposits, pendingCancelResolutions } = self; + let _ = chain; + + let mut deposits_it = pendingDeposits.into_iter().peekable(); + let mut cancel_it = pendingCancelResolutions.into_iter().peekable(); + + loop { + let min = [ + deposits_it.peek().map(|v| v.requestId.id), + cancel_it.peek().map(|v| v.requestId.id), + ] + .iter() + .cloned() + .filter_map(|v| v) + .min(); + + match (deposits_it.peek(), cancel_it.peek(), min) { + (Some(deposit), _, Some(min)) if deposit.requestId.id == min => { + if let Some(elem) = deposits_it.next() { + result.push(L1UpdateRequest::Deposit(elem.clone())); + } + }, + (_, Some(cancel), Some(min)) if cancel.requestId.id == min => { + if let Some(elem) = cancel_it.next() { + result.push(L1UpdateRequest::CancelResolution(elem.clone())); + } + }, + _ => break, + } + } + result + } +} + +impl TryFrom for Deposit { + type Error = String; + + fn try_from(deposit: eth_abi::Deposit) -> Result { + let requestId = deposit.requestId.try_into()?; + let depositRecipient = deposit + .depositRecipient + .try_into() + .map_err(|e| format!("Error converting requestId: {}", e))?; + let tokenAddress = deposit + .tokenAddress + .try_into() + .map_err(|e| format!("Error converting tokenAddress: {}", e))?; + + Ok(Self { + requestId, + depositRecipient, + tokenAddress, + amount: from_eth_u256(deposit.amount), + timeStamp: from_eth_u256(deposit.timeStamp), + ferryTip: from_eth_u256(deposit.ferryTip), + }) + } +} + +impl TryFrom for L1Update { + type Error = String; + + fn try_from(update: eth_abi::L1Update) -> Result { + let pending_deposits: Result, _> = + update.pendingDeposits.into_iter().map(|d| d.try_into()).collect(); + let pending_cancel_resultions: Result, _> = + update.pendingCancelResolutions.into_iter().map(|c| c.try_into()).collect(); + + Ok(Self { + chain: update.chain.try_into()?, + pendingDeposits: pending_deposits + .map_err(|e| format!("Error converting pendingDeposits: {}", e))?, + pendingCancelResolutions: pending_cancel_resultions + .map_err(|e| format!("Error converting pendingCancelResolutions: {}", e))?, + }) + } +} + +impl TryFrom for RequestId { + type Error = String; // Change to appropriate error type + + fn try_from(request_id: eth_abi::RequestId) -> Result { + let origin = request_id.origin.try_into(); + let id: Result = request_id.id.try_into(); + + Ok(Self { + origin: origin.map_err(|e| format!("Error converting origin: {}", e))?, + id: id.map_err(|e| format!("Error converting id: {}", e))?, + }) + } +} + +impl TryFrom for Origin { + type Error = String; + + fn try_from(origin: eth_abi::Origin) -> Result { + match origin { + eth_abi::Origin::L1 => Ok(Origin::L1), + eth_abi::Origin::L2 => Ok(Origin::L2), + _ => Err(String::from("Invalid origin type")), + } + } +} + +impl TryFrom for Chain { + type Error = String; + + fn try_from(origin: eth_abi::Chain) -> Result { + match origin { + eth_abi::Chain::Ethereum => Ok(Chain::Ethereum), + eth_abi::Chain::Arbitrum => Ok(Chain::Arbitrum), + eth_abi::Chain::Base => Ok(Chain::Base), + _ => Err(String::from("Invalid origin type")), + } + } +} + +impl TryFrom for CancelResolution { + type Error = String; + + fn try_from(value: eth_abi::CancelResolution) -> Result { + let request_id: RequestId = value + .requestId + .try_into() + .map_err(|e| format!("Error converting requestId: {}", e))?; + let l2_request_id = value.l2RequestId.try_into(); + + Ok(Self { + requestId: request_id, + l2RequestId: l2_request_id + .map_err(|e| format!("Error converting l2_request_id: {}", e))?, + cancelJustified: value.cancelJustified, + timeStamp: from_eth_u256(value.timeStamp), + }) + } +} diff --git a/gasp-node/pallets/rolldown/src/mock.rs b/gasp-node/pallets/rolldown/src/mock.rs new file mode 100644 index 000000000..c59da0173 --- /dev/null +++ b/gasp-node/pallets/rolldown/src/mock.rs @@ -0,0 +1,355 @@ +// Copyright (C) 2020 Mangata team + +use super::*; + +use crate as rolldown; +use core::convert::TryFrom; +use frame_support::{construct_runtime, derive_impl, parameter_types, traits::Nothing}; +use sp_runtime::traits::One; +use std::collections::HashSet; + +use frame_support::traits::ConstU128; +pub use mangata_support::traits::ProofOfStakeRewardsApi; +use mangata_types::assets::L1Asset; +use orml_traits::parameter_type_with_key; +use sp_runtime::{ + traits::{ConvertBack, ConvertToValue}, + BuildStorage, Saturating, +}; + +pub(crate) type AccountId = u64; +pub(crate) type Amount = i128; +pub(crate) type Balance = u128; +pub(crate) type TokenId = u32; + +pub mod consts { + pub const MILLION: u128 = 1_000_000; + pub const THOUSAND: u128 = 1_000; + pub const ALICE: u64 = 2; + pub const BOB: u64 = 3; + pub const CHARLIE: u64 = 4; + pub const CHAIN: crate::messages::Chain = crate::messages::Chain::Ethereum; +} + +type Block = frame_system::mocking::MockBlock; + +construct_runtime!( + pub enum Test { + System: frame_system, + Tokens: orml_tokens, + Rolldown: rolldown + } +); + +#[derive_impl(frame_system::config_preludes::TestDefaultConfig as frame_system::DefaultConfig)] +impl frame_system::Config for Test { + type Block = Block; +} + +parameter_type_with_key! { + pub ExistentialDeposits: |currency_id: TokenId| -> Balance { + match currency_id { + _ => 0, + } + }; +} + +impl orml_tokens::Config for Test { + type RuntimeEvent = RuntimeEvent; + type Balance = Balance; + type Amount = Amount; + type CurrencyId = TokenId; + type WeightInfo = (); + type ExistentialDeposits = ExistentialDeposits; + type MaxLocks = (); + type DustRemovalWhitelist = (); + type MaxReserves = (); + type ReserveIdentifier = [u8; 8]; + type CurrencyHooks = (); + type NontransferableTokens = Nothing; + type NontransferableTokensAllowList = Nothing; +} + +mockall::mock! { + pub SequencerStakingProviderApi {} + + impl SequencerStakingProviderTrait for SequencerStakingProviderApi { + fn is_active_sequencer(chain: messages::Chain, sequencer: &AccountId) -> bool; + fn is_active_sequencer_alias(chain: messages::Chain, sequencer: &AccountId, alias: &AccountId) -> bool; + fn slash_sequencer<'a>(chain: messages::Chain, to_be_slashed: &AccountId, maybe_to_reward: Option<&'a AccountId>) -> DispatchResult; + fn is_selected_sequencer(chain: messages::Chain, sequencer: &AccountId) -> bool; + fn selected_sequencer(chain: messages::Chain) -> Option; + } +} + +mockall::mock! { + pub AssetRegistryProviderApi {} + impl AssetRegistryProviderTrait for AssetRegistryProviderApi { + fn get_l1_asset_id(l1_asset: L1Asset) -> Option; + fn create_l1_asset(l1_asset: L1Asset) -> Result; + fn get_asset_l1_id(asset_id: TokenId) -> Option; + fn create_pool_asset(lp_asset: TokenId, asset_1: TokenId, asset_2: TokenId) -> DispatchResult; + } +} + +mockall::mock! { + pub MaintenanceStatusProviderApi {} + + impl GetMaintenanceStatusTrait for MaintenanceStatusProviderApi { + fn is_maintenance() -> bool; + fn is_upgradable() -> bool; + } + + impl SetMaintenanceModeOn for MaintenanceStatusProviderApi { + fn trigger_maintanance_mode(); + } +} + +pub struct DummyAddressConverter(); + +impl Convert<[u8; 20], AccountId> for DummyAddressConverter { + fn convert(account: [u8; 20]) -> AccountId { + let mut bytes = [0u8; 8]; + bytes.copy_from_slice(&account[0..8]); + AccountId::from_be_bytes(bytes) + } +} + +impl ConvertBack<[u8; 20], AccountId> for DummyAddressConverter { + fn convert_back(account: AccountId) -> [u8; 20] { + let mut address = [0u8; 20]; + let bytes: Vec = account + .to_be_bytes() + .iter() + .cloned() + .chain(std::iter::repeat(0u8).take(12)) + .into_iter() + .collect(); + + address.copy_from_slice(&bytes[..]); + address + } +} + +parameter_types! { + pub const TreasuryPalletId: PalletId = PalletId(*b"rolldown"); + pub const NativeCurrencyId: u32 = 0; +} + +impl rolldown::RolldownBenchmarkingConfig for Test {} + +impl rolldown::Config for Test { + type RuntimeEvent = RuntimeEvent; + type SequencerStakingProvider = MockSequencerStakingProviderApi; + type Tokens = orml_tokens::MultiTokenCurrencyAdapter; + type AssetRegistryProvider = MockAssetRegistryProviderApi; + type AddressConverter = DummyAddressConverter; + type RequestsPerBlock = ConstU128<10>; + type MaintenanceStatusProvider = MockMaintenanceStatusProviderApi; + type ChainId = messages::Chain; + type RightsMultiplier = ConstU128<1>; + type AssetAddressConverter = crate::MultiEvmChainAddressConverter; + type MerkleRootAutomaticBatchSize = ConstU128<10>; + type MerkleRootAutomaticBatchPeriod = ConstU128<25>; + type TreasuryPalletId = TreasuryPalletId; + type NativeCurrencyId = NativeCurrencyId; + type SequencerStakingRewards = (); + type WithdrawFee = ConvertToValue>; + type WeightInfo = (); + type NontransferableTokens = Nothing; +} + +pub struct ExtBuilder { + ext: sp_io::TestExternalities, +} + +impl ExtBuilder { + pub fn new() -> Self { + let mut t = frame_system::GenesisConfig::::default() + .build_storage() + .expect("Frame system builds valid default genesis config"); + + rolldown::GenesisConfig:: { + _phantom: Default::default(), + dispute_periods: [(crate::messages::Chain::Ethereum, 5u128)].iter().cloned().collect(), + } + .assimilate_storage(&mut t) + .expect("Tokens storage can be assimilated"); + + let mut ext = sp_io::TestExternalities::new(t); + + ext.execute_with(|| { + for s in vec![consts::ALICE, consts::BOB, consts::CHARLIE].iter() { + Pallet::::new_sequencer_active(consts::CHAIN, s); + } + }); + + Self { ext } + } + + pub fn new_without_default_sequencers() -> Self { + let mut t = frame_system::GenesisConfig::::default() + .build_storage() + .expect("Frame system builds valid default genesis config"); + + rolldown::GenesisConfig:: { + _phantom: Default::default(), + dispute_periods: [(crate::messages::Chain::Ethereum, 5u128)].iter().cloned().collect(), + } + .assimilate_storage(&mut t) + .expect("Tokens storage can be assimilated"); + + let ext = sp_io::TestExternalities::new(t); + + Self { ext } + } + + fn create_if_does_not_exists(&mut self, token_id: TokenId) { + self.ext.execute_with(|| { + while token_id >= Tokens::next_asset_id() { + Tokens::create(RuntimeOrigin::root(), 0, 0).unwrap(); + } + }); + } + + pub fn issue(mut self, who: AccountId, token_id: TokenId, balance: Balance) -> Self { + self.create_if_does_not_exists(token_id); + self.ext + .execute_with(|| Tokens::mint(RuntimeOrigin::root(), token_id, who, balance).unwrap()); + return self + } + + pub fn build(self) -> sp_io::TestExternalities { + self.ext + } + + pub fn all_mocks() -> HashSet { + [ + Mocks::IsActiveSequencer, + Mocks::IsSelectedSequencer, + Mocks::SelectedSequencer, + Mocks::GetL1AssetId, + Mocks::MaintenanceMode, + ] + .iter() + .cloned() + .collect() + } + + pub fn execute_with_default_mocks(self, f: impl FnOnce() -> R) -> R { + self.execute_with_mocks(Self::all_mocks(), f) + } + + pub fn execute_without_mocks( + self, + disabled: impl IntoIterator + Clone, + f: impl FnOnce() -> R, + ) -> R { + let disabled: HashSet = disabled.into_iter().collect(); + let difference: HashSet = Self::all_mocks().difference(&disabled).cloned().collect(); + self.execute_with_mocks(difference, f) + } + + pub fn execute_with_mocks(mut self, mocks: HashSet, f: impl FnOnce() -> R) -> R { + self.ext.execute_with(|| { + let is_liquidity_token_mock = + MockSequencerStakingProviderApi::is_active_sequencer_context(); + let is_selected_sequencer_mock = + MockSequencerStakingProviderApi::is_selected_sequencer_context(); + let get_l1_asset_id_mock = MockAssetRegistryProviderApi::get_l1_asset_id_context(); + let is_maintenance_mock = MockMaintenanceStatusProviderApi::is_maintenance_context(); + let selected_sequencer_mock = + MockSequencerStakingProviderApi::selected_sequencer_context(); + + if mocks.contains(&Mocks::IsActiveSequencer) { + is_liquidity_token_mock.expect().return_const(true); + } + + if mocks.contains(&Mocks::IsSelectedSequencer) { + is_selected_sequencer_mock.expect().return_const(true); + } + + if mocks.contains(&Mocks::GetL1AssetId) { + get_l1_asset_id_mock.expect().return_const(crate::tests::ETH_TOKEN_ADDRESS_MGX); + } + + if mocks.contains(&Mocks::SelectedSequencer) { + selected_sequencer_mock.expect().return_const(None); + } + + if mocks.contains(&Mocks::MaintenanceMode) { + is_maintenance_mock.expect().return_const(false); + } + + f() + }) + } +} + +#[derive(Eq, PartialEq, Hash, Copy, Clone)] +pub enum Mocks { + IsActiveSequencer, + IsSelectedSequencer, + SelectedSequencer, + GetL1AssetId, + MaintenanceMode, +} + +pub fn forward_to_next_block() +where + T: frame_system::Config, + T: rolldown::Config, +{ + forward_to_block::(frame_system::Pallet::::block_number() + One::one()); +} + +pub fn forward_to_block(n: BlockNumberFor) +where + T: frame_system::Config, + T: rolldown::Config, +{ + while frame_system::Pallet::::block_number() < n { + rolldown::Pallet::::on_finalize(frame_system::Pallet::::block_number()); + frame_system::Pallet::::on_finalize(frame_system::Pallet::::block_number()); + let new_block_number = + frame_system::Pallet::::block_number().saturating_add(1u32.into()); + frame_system::Pallet::::set_block_number(new_block_number); + + frame_system::Pallet::::on_initialize(new_block_number); + rolldown::Pallet::::on_initialize(new_block_number); + rolldown::Pallet::::on_idle(new_block_number, Weight::from_parts(u64::MAX, 0u64)); + } +} + +pub(crate) fn events() -> Vec> { + System::events() + .into_iter() + .map(|r| r.event) + .filter_map(|e| if let RuntimeEvent::Rolldown(inner) = e { Some(inner) } else { None }) + .collect::>() +} + +#[macro_export] +macro_rules! assert_eq_events { + ($events:expr) => { + match &$events { + e => similar_asserts::assert_eq!(*e, $crate::mock::events()), + } + }; +} + +#[macro_export] +macro_rules! assert_event_emitted { + ($event:expr) => { + match &$event { + e => { + assert!( + $crate::mock::events().iter().find(|x| *x == e).is_some(), + "Event {:?} was not found in events: \n {:?}", + e, + crate::mock::events() + ); + }, + } + }; +} diff --git a/gasp-node/pallets/rolldown/src/tests.rs b/gasp-node/pallets/rolldown/src/tests.rs new file mode 100644 index 000000000..30d09331e --- /dev/null +++ b/gasp-node/pallets/rolldown/src/tests.rs @@ -0,0 +1,3456 @@ +use crate::{ + messages::Chain, + mock::{consts::*, *}, + *, +}; + +use frame_support::{assert_err, assert_noop, assert_ok, error::BadOrigin}; +use hex_literal::hex; +use messages::L1UpdateRequest; + +use serial_test::serial; + +use sp_runtime::traits::{ConvertBack, ConvertToValue}; +use sp_std::iter::FromIterator; + +pub const ETH_TOKEN_ADDRESS: [u8; 20] = hex!("2CD2188119797153892438E57364D95B32975560"); +pub const ETH_TOKEN_ADDRESS_MGX: TokenId = 100_u32; +pub const ETH_RECIPIENT_ACCOUNT: [u8; 20] = hex!("0000000000000000000000000000000000000004"); +pub const ETH_RECIPIENT_ACCOUNT_MGX: AccountId = CHARLIE; + +pub type TokensOf = ::Tokens; + +pub(crate) struct L1UpdateBuilder(Option, Vec); + +impl L1UpdateBuilder { + pub fn new() -> Self { + Self(None, Default::default()) + } + + pub fn with_offset(mut self, offset: u128) -> Self { + self.0 = Some(offset); + self + } + + pub fn with_requests(mut self, requests: Vec) -> Self { + self.1 = requests; + self + } + + pub fn build(self) -> messages::L1Update { + Self::build_for_chain(self, Chain::Ethereum) + } + + pub fn build_for_chain(self, chain: Chain) -> messages::L1Update { + let mut update = messages::L1Update::default(); + update.chain = chain; + + for (id, r) in self.1.into_iter().enumerate() { + let rid = if let Some(offset) = self.0 { (id as u128) + offset } else { r.id() }; + match r { + L1UpdateRequest::Deposit(mut d) => { + d.requestId.id = rid; + update.pendingDeposits.push(d); + }, + L1UpdateRequest::CancelResolution(mut c) => { + c.requestId.id = rid; + update.pendingCancelResolutions.push(c); + }, + } + } + update + } +} + +impl Default for L1UpdateBuilder { + fn default() -> Self { + Self(Some(1u128), Default::default()) + } +} + +#[test] +#[serial] +fn error_on_empty_update() { + ExtBuilder::new().execute_with_default_mocks(|| { + forward_to_block::(36); + let update = L1UpdateBuilder::default().build(); + assert_err!( + Rolldown::update_l2_from_l1_unsafe(RuntimeOrigin::signed(ALICE), update), + Error::::EmptyUpdate + ); + }); +} + +#[test] +#[serial] +fn test_genesis() { + ExtBuilder::new().execute_with_default_mocks(|| { + assert_eq!( + SequencersRights::::get(consts::CHAIN).get(&ALICE).unwrap().read_rights, + 1 + ); + }); +} + +#[test] +#[serial] +fn process_single_deposit() { + ExtBuilder::new().execute_with_default_mocks(|| { + forward_to_block::(36); + let current_block_number = + >::block_number().saturated_into::(); + let dispute_period: u128 = Rolldown::get_dispute_period(consts::CHAIN).unwrap(); + let update = L1UpdateBuilder::default() + .with_requests(vec![L1UpdateRequest::Deposit(Default::default())]) + .build(); + Rolldown::update_l2_from_l1_unsafe(RuntimeOrigin::signed(ALICE), update).unwrap(); + + assert_event_emitted!(Event::L1ReadStored { + chain: messages::Chain::Ethereum, + sequencer: ALICE, + dispute_period_end: current_block_number + dispute_period, + range: (1u128, 1u128).into(), + hash: hex!("75207958ce929568193284a176e012a8cf5058dc19d73dafee61a419eb667398").into() + }); + }); +} + +#[test] +#[serial] +fn test_reject_update_with_wrong_hash() { + ExtBuilder::new().execute_with_default_mocks(|| { + forward_to_block::(36); + let update = L1UpdateBuilder::default() + .with_requests(vec![L1UpdateRequest::Deposit(Default::default())]) + .build(); + + assert_err!( + Rolldown::update_l2_from_l1(RuntimeOrigin::signed(ALICE), update, H256::zero()), + Error::::UpdateHashMishmatch + ); + }); +} + +#[test] +#[serial] +fn l2_counter_updates_when_requests_are_processed() { + ExtBuilder::new().execute_with_default_mocks(|| { + let update1 = L1UpdateBuilder::default() + .with_requests(vec![L1UpdateRequest::Deposit(Default::default())]) + .build(); + + let update2 = L1UpdateBuilder::default() + .with_requests(vec![ + L1UpdateRequest::Deposit(Default::default()), + L1UpdateRequest::Deposit(Default::default()), + ]) + .build(); + + forward_to_block::(10); + assert_eq!(Rolldown::get_last_processed_request_on_l2(Chain::Ethereum), 0_u128.into()); + Rolldown::update_l2_from_l1_unsafe(RuntimeOrigin::signed(ALICE), update1).unwrap(); + + forward_to_block::(11); + Rolldown::update_l2_from_l1_unsafe(RuntimeOrigin::signed(BOB), update2).unwrap(); + + forward_to_block::(15); + assert_eq!(Rolldown::get_last_processed_request_on_l2(Chain::Ethereum), 1u128.into()); + + forward_to_next_block::(); + forward_to_next_block::(); + assert_eq!(Rolldown::get_last_processed_request_on_l2(Chain::Ethereum), 2u128.into()); + }); +} + +#[test] +#[serial] +fn deposit_executed_after_dispute_period() { + ExtBuilder::new() + .issue(ALICE, ETH_TOKEN_ADDRESS_MGX, 0u128) + .execute_with_default_mocks(|| { + forward_to_block::(10); + let update = L1UpdateBuilder::default() + .with_requests(vec![L1UpdateRequest::Deposit(messages::Deposit { + requestId: Default::default(), + depositRecipient: DummyAddressConverter::convert_back(CHARLIE), + tokenAddress: ETH_TOKEN_ADDRESS, + amount: sp_core::U256::from(MILLION), + timeStamp: sp_core::U256::from(1), + ferryTip: sp_core::U256::from(0), + })]) + .build(); + + Rolldown::update_l2_from_l1_unsafe(RuntimeOrigin::signed(ALICE), update).unwrap(); + forward_to_block::(14); + assert!(!L2Requests::::contains_key( + Chain::Ethereum, + RequestId::new(Origin::L1, 0u128) + )); + assert_eq!(TokensOf::::free_balance(ETH_TOKEN_ADDRESS_MGX, &CHARLIE), 0_u128); + + forward_to_block::(15); + forward_to_next_block::(); + assert_eq!(TokensOf::::free_balance(ETH_TOKEN_ADDRESS_MGX, &CHARLIE), MILLION); + }); +} + +#[test] +#[serial] +fn deposit_fail_creates_update_with_status_false() { + ExtBuilder::new() + .issue(ALICE, ETH_TOKEN_ADDRESS_MGX, 0u128) + .execute_with_default_mocks(|| { + forward_to_block::(10); + let update = L1UpdateBuilder::default() + .with_requests(vec![L1UpdateRequest::Deposit(messages::Deposit { + requestId: Default::default(), + depositRecipient: DummyAddressConverter::convert_back(CHARLIE), + tokenAddress: ETH_TOKEN_ADDRESS, + amount: sp_core::U256::from("3402823669209384634633746074317682114560"), + timeStamp: sp_core::U256::from(1), + ferryTip: sp_core::U256::from(0), + })]) + .build(); + + Rolldown::update_l2_from_l1_unsafe(RuntimeOrigin::signed(ALICE), update).unwrap(); + forward_to_block::(14); + assert!(!L2Requests::::contains_key( + Chain::Ethereum, + RequestId::new(Origin::L1, 0u128) + )); + assert_eq!(TokensOf::::free_balance(ETH_TOKEN_ADDRESS_MGX, &CHARLIE), 0_u128); + + assert!(!FailedL1Deposits::::contains_key((consts::CHAIN, 1u128))); + + forward_to_block::(20); + + assert_event_emitted!(Event::RequestProcessedOnL2 { + chain: messages::Chain::Ethereum, + request_id: 1u128, + status: Err(L1RequestProcessingError::Overflow), + }); + + assert!(FailedL1Deposits::::contains_key((consts::CHAIN, 1u128))); + assert_eq!(TokensOf::::free_balance(ETH_TOKEN_ADDRESS_MGX, &CHARLIE), 0_u128); + }); +} + +#[test] +#[serial] +fn test_refund_of_failed_withdrawal() { + ExtBuilder::new() + .issue(ALICE, ETH_TOKEN_ADDRESS_MGX, 0u128) + .execute_with_default_mocks(|| { + forward_to_block::(10); + let update = L1UpdateBuilder::default() + .with_requests(vec![L1UpdateRequest::Deposit(messages::Deposit { + requestId: Default::default(), + depositRecipient: DummyAddressConverter::convert_back(CHARLIE), + tokenAddress: ETH_TOKEN_ADDRESS, + amount: sp_core::U256::from("3402823669209384634633746074317682114560"), + timeStamp: sp_core::U256::from(1), + ferryTip: sp_core::U256::from(0), + })]) + .build(); + + Rolldown::update_l2_from_l1_unsafe(RuntimeOrigin::signed(ALICE), update).unwrap(); + forward_to_block::(20); + assert_event_emitted!(Event::RequestProcessedOnL2 { + chain: messages::Chain::Ethereum, + request_id: 1u128, + status: Err(L1RequestProcessingError::Overflow), + }); + + Rolldown::refund_failed_deposit(RuntimeOrigin::signed(CHARLIE), consts::CHAIN, 1u128) + .unwrap(); + + assert_event_emitted!(Event::DepositRefundCreated { + refunded_request_id: RequestId::new(Origin::L1, 1u128), + chain: Chain::Ethereum, + ferry: None + }); + }); +} + +#[test] +#[serial] +fn test_withdrawal_can_be_refunded_only_once() { + ExtBuilder::new() + .issue(ALICE, ETH_TOKEN_ADDRESS_MGX, 0u128) + .execute_with_default_mocks(|| { + forward_to_block::(10); + let update = L1UpdateBuilder::default() + .with_requests(vec![L1UpdateRequest::Deposit(messages::Deposit { + requestId: Default::default(), + depositRecipient: DummyAddressConverter::convert_back(CHARLIE), + tokenAddress: ETH_TOKEN_ADDRESS, + amount: sp_core::U256::from("3402823669209384634633746074317682114560"), + timeStamp: sp_core::U256::from(1), + ferryTip: sp_core::U256::from(0), + })]) + .build(); + + Rolldown::update_l2_from_l1_unsafe(RuntimeOrigin::signed(ALICE), update).unwrap(); + forward_to_block::(20); + Rolldown::refund_failed_deposit(RuntimeOrigin::signed(CHARLIE), consts::CHAIN, 1u128) + .unwrap(); + assert_err!( + Rolldown::refund_failed_deposit( + RuntimeOrigin::signed(CHARLIE), + consts::CHAIN, + 1u128 + ), + Error::::FailedDepositDoesNotExist + ); + }); +} + +#[test] +#[serial] +fn test_withdrawal_can_be_refunded_only_by_account_deposit_recipient() { + ExtBuilder::new() + .issue(ALICE, ETH_TOKEN_ADDRESS_MGX, 0u128) + .execute_with_default_mocks(|| { + forward_to_block::(10); + let update = L1UpdateBuilder::default() + .with_requests(vec![L1UpdateRequest::Deposit(messages::Deposit { + requestId: Default::default(), + depositRecipient: DummyAddressConverter::convert_back(CHARLIE), + tokenAddress: ETH_TOKEN_ADDRESS, + amount: sp_core::U256::from("3402823669209384634633746074317682114560"), + timeStamp: sp_core::U256::from(1), + ferryTip: sp_core::U256::from(0), + })]) + .build(); + + Rolldown::update_l2_from_l1_unsafe(RuntimeOrigin::signed(ALICE), update).unwrap(); + forward_to_block::(20); + + assert_err!( + Rolldown::refund_failed_deposit(RuntimeOrigin::signed(ALICE), consts::CHAIN, 1u128), + Error::::NotEligibleForRefund + ); + + Rolldown::refund_failed_deposit(RuntimeOrigin::signed(CHARLIE), consts::CHAIN, 1u128) + .unwrap(); + }); +} + +#[test] +#[serial] +fn l1_upate_executed_immediately_if_force_submitted() { + ExtBuilder::new() + .issue(ALICE, ETH_TOKEN_ADDRESS_MGX, 0u128) + .execute_with_default_mocks(|| { + forward_to_block::(10); + let update = L1UpdateBuilder::default() + .with_requests(vec![L1UpdateRequest::Deposit(messages::Deposit { + requestId: Default::default(), + depositRecipient: DummyAddressConverter::convert_back(CHARLIE), + tokenAddress: ETH_TOKEN_ADDRESS, + amount: sp_core::U256::from(MILLION), + timeStamp: sp_core::U256::from(1), + ferryTip: sp_core::U256::from(0), + })]) + .build(); + + assert_eq!(TokensOf::::free_balance(ETH_TOKEN_ADDRESS_MGX, &CHARLIE), 0_u128); + assert!(!L2Requests::::contains_key( + Chain::Ethereum, + RequestId::new(Origin::L1, 1u128) + )); + assert_eq!(LastProcessedRequestOnL2::::get(Chain::Ethereum), 0u128.into()); + Rolldown::force_update_l2_from_l1(RuntimeOrigin::root(), update).unwrap(); + assert_eq!(LastProcessedRequestOnL2::::get(Chain::Ethereum), 0u128.into()); + + forward_to_next_block::(); + forward_to_next_block::(); + assert_eq!(LastProcessedRequestOnL2::::get(Chain::Ethereum), 1u128.into()); + assert_eq!(TokensOf::::free_balance(ETH_TOKEN_ADDRESS_MGX, &CHARLIE), MILLION); + }); +} + +#[test] +#[serial] +fn each_request_executed_only_once() { + ExtBuilder::new() + .issue(ALICE, ETH_TOKEN_ADDRESS_MGX, 0u128) + .execute_with_default_mocks(|| { + forward_to_block::(10); + let update = L1UpdateBuilder::default() + .with_requests(vec![L1UpdateRequest::Deposit(messages::Deposit { + requestId: Default::default(), + depositRecipient: DummyAddressConverter::convert_back(CHARLIE), + tokenAddress: ETH_TOKEN_ADDRESS, + amount: sp_core::U256::from(MILLION), + timeStamp: sp_core::U256::from(1), + ferryTip: sp_core::U256::from(0), + })]) + .build(); + Rolldown::update_l2_from_l1_unsafe(RuntimeOrigin::signed(ALICE), update.clone()) + .unwrap(); + + forward_to_block::(11); + Rolldown::update_l2_from_l1_unsafe(RuntimeOrigin::signed(BOB), update).unwrap(); + + forward_to_block::(14); + assert!(!L2Requests::::contains_key( + Chain::Ethereum, + RequestId::new(Origin::L1, 0u128) + )); + assert_eq!(TokensOf::::free_balance(ETH_TOKEN_ADDRESS_MGX, &CHARLIE), 0_u128); + + forward_to_block::(16); + assert_eq!(TokensOf::::free_balance(ETH_TOKEN_ADDRESS_MGX, &CHARLIE), MILLION); + + forward_to_block::(20); + assert_eq!(TokensOf::::free_balance(ETH_TOKEN_ADDRESS_MGX, &CHARLIE), MILLION); + }); +} + +#[test] +#[serial] +fn test_cancel_removes_pending_requests() { + ExtBuilder::new() + .issue(ETH_RECIPIENT_ACCOUNT_MGX, ETH_TOKEN_ADDRESS_MGX, MILLION) + .execute_with_default_mocks(|| { + forward_to_block::(10); + + // Arrange + let slash_sequencer_mock = MockSequencerStakingProviderApi::slash_sequencer_context(); + slash_sequencer_mock.expect().return_const(Ok(().into())); + + let deposit_update = L1UpdateBuilder::default() + .with_requests(vec![ + L1UpdateRequest::Deposit(Default::default()), + L1UpdateRequest::Deposit(Default::default()), + ]) + .build(); + + assert!(!PendingSequencerUpdates::::contains_key(15u128, Chain::Ethereum)); + + // Act + Rolldown::update_l2_from_l1_unsafe(RuntimeOrigin::signed(ALICE), deposit_update) + .unwrap(); + assert!(PendingSequencerUpdates::::contains_key(15u128, Chain::Ethereum)); + Rolldown::cancel_requests_from_l1( + RuntimeOrigin::signed(BOB), + consts::CHAIN, + 15u128.into(), + ) + .unwrap(); + + // Assert + assert!(!PendingSequencerUpdates::::contains_key(15u128, Chain::Ethereum)); + }); +} + +#[test] +#[serial] +fn test_cancel_produce_update_with_correct_hash() { + ExtBuilder::new() + .issue(ETH_RECIPIENT_ACCOUNT_MGX, ETH_TOKEN_ADDRESS_MGX, MILLION) + .execute_with_default_mocks(|| { + forward_to_block::(10); + + // Arrange + let deposit_update = L1UpdateBuilder::default() + .with_requests(vec![L1UpdateRequest::Deposit(Default::default())]) + .build(); + + // Act + Rolldown::update_l2_from_l1_unsafe(RuntimeOrigin::signed(ALICE), deposit_update) + .unwrap(); + + Rolldown::cancel_requests_from_l1( + RuntimeOrigin::signed(BOB), + consts::CHAIN, + 15u128.into(), + ) + .unwrap(); + + assert_eq!( + L2Requests::::get(Chain::Ethereum, RequestId::new(Origin::L2, 1u128)) + .unwrap() + .0, + Cancel { + requestId: RequestId::new(Origin::L2, 1u128), + updater: ALICE, + canceler: BOB, + range: (1u128, 1u128).into(), + hash: hex!("75207958ce929568193284a176e012a8cf5058dc19d73dafee61a419eb667398") + .into() + } + .into() + ); + }); +} + +#[test] +#[serial] +fn test_malicious_sequencer_is_slashed_when_honest_sequencer_cancels_malicious_read() { + ExtBuilder::new() + .issue(ETH_RECIPIENT_ACCOUNT_MGX, ETH_TOKEN_ADDRESS_MGX, MILLION) + .execute_with_default_mocks(|| { + forward_to_block::(10); + + // Arrange + + let deposit_update = L1UpdateBuilder::default() + .with_requests(vec![L1UpdateRequest::Deposit(Default::default())]) + .build(); + + let l2_request_id = Rolldown::get_next_l2_request_id(Chain::Ethereum); + let cancel_resolution = L1UpdateBuilder::default() + .with_requests(vec![L1UpdateRequest::CancelResolution( + messages::CancelResolution { + requestId: Default::default(), + l2RequestId: l2_request_id, + cancelJustified: true, + timeStamp: sp_core::U256::from(1), + }, + )]) + .with_offset(1u128) + .build(); + + assert_eq!( + *SequencersRights::::get(consts::CHAIN).get(&ALICE).unwrap(), + SequencerRights { read_rights: 1u128, cancel_rights: 2u128 } + ); + assert_eq!( + *SequencersRights::::get(consts::CHAIN).get(&BOB).unwrap(), + SequencerRights { read_rights: 1u128, cancel_rights: 2u128 } + ); + + // Act + Rolldown::update_l2_from_l1_unsafe(RuntimeOrigin::signed(ALICE), deposit_update) + .unwrap(); + assert_eq!( + *SequencersRights::::get(consts::CHAIN).get(&ALICE).unwrap(), + SequencerRights { read_rights: 0u128, cancel_rights: 2u128 } + ); + + forward_to_block::(11); + Rolldown::cancel_requests_from_l1(RuntimeOrigin::signed(BOB), consts::CHAIN, 15u128) + .unwrap(); + + assert_eq!( + *SequencersRights::::get(consts::CHAIN).get(&BOB).unwrap(), + SequencerRights { read_rights: 1u128, cancel_rights: 1u128 } + ); + + forward_to_block::(12); + Rolldown::update_l2_from_l1_unsafe(RuntimeOrigin::signed(BOB), cancel_resolution) + .unwrap(); + assert_eq!( + *SequencersRights::::get(consts::CHAIN).get(&BOB).unwrap(), + SequencerRights { read_rights: 0u128, cancel_rights: 1u128 } + ); + + forward_to_block::(16); + + let slash_sequencer_mock = MockSequencerStakingProviderApi::slash_sequencer_context(); + slash_sequencer_mock + .expect() + .withf(|chain, a, b| { + *chain == consts::CHAIN && *a == ALICE && b.cloned() == Some(BOB) + }) + .times(1) + .return_const(Ok(().into())); + + forward_to_block::(17); + forward_to_next_block::(); + assert_eq!( + *SequencersRights::::get(consts::CHAIN).get(&BOB).unwrap(), + SequencerRights { read_rights: 1u128, cancel_rights: 2u128 } + ); + }) +} + +#[test] +#[serial] +fn test_malicious_canceler_is_slashed_when_honest_read_is_canceled() { + ExtBuilder::new() + .issue(ETH_RECIPIENT_ACCOUNT_MGX, ETH_TOKEN_ADDRESS_MGX, MILLION) + .execute_with_default_mocks(|| { + forward_to_block::(10); + + // Arrange + + let deposit_update = L1UpdateBuilder::default() + .with_requests(vec![L1UpdateRequest::Deposit(Default::default())]) + .build(); + + let l2_request_id = Rolldown::get_next_l2_request_id(Chain::Ethereum); + let cancel_resolution = L1UpdateBuilder::default() + .with_requests(vec![L1UpdateRequest::CancelResolution( + messages::CancelResolution { + requestId: Default::default(), + l2RequestId: l2_request_id, + cancelJustified: false, + timeStamp: sp_core::U256::from(1), + }, + )]) + .with_offset(1u128) + .build(); + + assert_eq!( + *SequencersRights::::get(consts::CHAIN).get(&ALICE).unwrap(), + SequencerRights { read_rights: 1u128, cancel_rights: 2u128 } + ); + assert_eq!( + *SequencersRights::::get(consts::CHAIN).get(&BOB).unwrap(), + SequencerRights { read_rights: 1u128, cancel_rights: 2u128 } + ); + + // Act + Rolldown::update_l2_from_l1_unsafe(RuntimeOrigin::signed(ALICE), deposit_update) + .unwrap(); + + assert_eq!( + *SequencersRights::::get(consts::CHAIN).get(&ALICE).unwrap(), + SequencerRights { read_rights: 0u128, cancel_rights: 2u128 } + ); + forward_to_block::(11); + Rolldown::cancel_requests_from_l1(RuntimeOrigin::signed(BOB), consts::CHAIN, 15u128) + .unwrap(); + + assert_eq!( + *SequencersRights::::get(consts::CHAIN).get(&BOB).unwrap(), + SequencerRights { read_rights: 1u128, cancel_rights: 1u128 } + ); + + forward_to_block::(12); + Rolldown::update_l2_from_l1_unsafe(RuntimeOrigin::signed(BOB), cancel_resolution) + .unwrap(); + forward_to_block::(16); + + let slash_sequencer_mock = MockSequencerStakingProviderApi::slash_sequencer_context(); + slash_sequencer_mock + .expect() + .withf(|chain, a, b| *chain == consts::CHAIN && *a == BOB && b.cloned() == None) + .times(1) + .return_const(Ok(().into())); + forward_to_block::(17); + forward_to_next_block::(); + + assert_eq!( + *SequencersRights::::get(consts::CHAIN).get(&ALICE).unwrap(), + SequencerRights { read_rights: 1u128, cancel_rights: 2u128 } + ); + assert_eq!( + *SequencersRights::::get(consts::CHAIN).get(&BOB).unwrap(), + SequencerRights { read_rights: 1u128, cancel_rights: 2u128 } + ); + }) +} + +#[test] +#[serial] +fn test_trigger_maintanance_mode_when_processing_cancel_resolution_triggers_an_error() { + ExtBuilder::new() + .issue(ETH_RECIPIENT_ACCOUNT_MGX, ETH_TOKEN_ADDRESS_MGX, MILLION) + .execute_with_default_mocks(|| { + let trigger_maintanance_mode_mock = + MockMaintenanceStatusProviderApi::trigger_maintanance_mode_context(); + trigger_maintanance_mode_mock.expect().return_const(()); + + forward_to_block::(10); + + let l2_request_id = Rolldown::get_next_l2_request_id(Chain::Ethereum); + let cancel_resolution = L1UpdateBuilder::default() + .with_requests(vec![L1UpdateRequest::CancelResolution( + messages::CancelResolution { + requestId: Default::default(), + l2RequestId: l2_request_id, + cancelJustified: false, + timeStamp: sp_core::U256::from(1), + }, + )]) + .with_offset(1u128) + .build(); + + Rolldown::update_l2_from_l1_unsafe(RuntimeOrigin::signed(BOB), cancel_resolution) + .unwrap(); + + forward_to_block::(15); + forward_to_next_block::(); + + assert_event_emitted!(Event::RequestProcessedOnL2 { + chain: consts::CHAIN, + request_id: 1u128, + status: Err(L1RequestProcessingError::WrongCancelRequestId), + }); + }); +} + +#[test] +#[serial] +fn test_cancel_unexisting_request_fails() { + ExtBuilder::new() + .issue(ETH_RECIPIENT_ACCOUNT_MGX, ETH_TOKEN_ADDRESS_MGX, MILLION) + .execute_with_default_mocks(|| { + forward_to_block::(10); + assert_err!( + Rolldown::cancel_requests_from_l1( + RuntimeOrigin::signed(BOB), + consts::CHAIN, + 15u128.into() + ), + Error::::RequestDoesNotExist + ); + }); +} + +#[test] +#[serial] +fn test_cancel_removes_cancel_right() { + ExtBuilder::new() + .issue(ETH_RECIPIENT_ACCOUNT_MGX, ETH_TOKEN_ADDRESS_MGX, MILLION) + .execute_with_default_mocks(|| { + forward_to_block::(10); + + let slash_sequencer_mock = MockSequencerStakingProviderApi::slash_sequencer_context(); + slash_sequencer_mock.expect().return_const(Ok(().into())); + + let l2_request_id = Rolldown::get_next_l2_request_id(Chain::Ethereum); + let deposit_update = L1UpdateBuilder::default() + .with_requests(vec![L1UpdateRequest::Deposit(messages::Deposit::default())]) + .build(); + + let cancel_resolution = L1UpdateBuilder::default() + .with_requests(vec![L1UpdateRequest::CancelResolution( + messages::CancelResolution { + requestId: Default::default(), + l2RequestId: l2_request_id, + cancelJustified: true, + timeStamp: sp_core::U256::from(1), + }, + )]) + .with_offset(1u128) + .build(); + + assert_eq!( + *SequencersRights::::get(consts::CHAIN).get(&ALICE).unwrap(), + SequencerRights { read_rights: 1u128, cancel_rights: 2u128 } + ); + assert_eq!( + *SequencersRights::::get(consts::CHAIN).get(&BOB).unwrap(), + SequencerRights { read_rights: 1u128, cancel_rights: 2u128 } + ); + + Rolldown::update_l2_from_l1_unsafe(RuntimeOrigin::signed(ALICE), deposit_update) + .unwrap(); + + assert_eq!( + *SequencersRights::::get(consts::CHAIN).get(&ALICE).unwrap(), + SequencerRights { read_rights: 0u128, cancel_rights: 2u128 } + ); + assert_eq!( + *SequencersRights::::get(consts::CHAIN).get(&BOB).unwrap(), + SequencerRights { read_rights: 1u128, cancel_rights: 2u128 } + ); + + Rolldown::cancel_requests_from_l1( + RuntimeOrigin::signed(BOB), + consts::CHAIN, + 15u128.into(), + ) + .unwrap(); + + assert!(!PendingSequencerUpdates::::contains_key(15u128, Chain::Ethereum)); + assert_eq!( + *SequencersRights::::get(consts::CHAIN).get(&ALICE).unwrap(), + SequencerRights { read_rights: 0u128, cancel_rights: 2u128 } + ); + assert_eq!( + *SequencersRights::::get(consts::CHAIN).get(&BOB).unwrap(), + SequencerRights { read_rights: 1u128, cancel_rights: 1u128 } + ); + + forward_to_block::(11); + Rolldown::update_l2_from_l1_unsafe(RuntimeOrigin::signed(BOB), cancel_resolution) + .unwrap(); + assert_eq!( + *SequencersRights::::get(consts::CHAIN).get(&BOB).unwrap(), + SequencerRights { read_rights: 0u128, cancel_rights: 1u128 } + ); + + forward_to_block::(16); + forward_to_next_block::(); + assert_eq!( + *SequencersRights::::get(consts::CHAIN).get(&ALICE).unwrap(), + SequencerRights { read_rights: 1u128, cancel_rights: 2u128 } + ); + assert_eq!( + *SequencersRights::::get(consts::CHAIN).get(&BOB).unwrap(), + SequencerRights { read_rights: 1u128, cancel_rights: 2u128 } + ); + }); +} + +#[test] +#[serial] +fn cancel_request_as_council_executed_immadiately() { + ExtBuilder::new() + .issue(ETH_RECIPIENT_ACCOUNT_MGX, ETH_TOKEN_ADDRESS_MGX, MILLION) + .execute_with_default_mocks(|| { + forward_to_block::(10); + + let slash_sequencer_mock = MockSequencerStakingProviderApi::slash_sequencer_context(); + slash_sequencer_mock.expect().return_const(Ok(().into())); + + let deposit_update = L1UpdateBuilder::default() + .with_requests(vec![L1UpdateRequest::Deposit(messages::Deposit::default())]) + .build(); + + assert_eq!( + *SequencersRights::::get(consts::CHAIN).get(&ALICE).unwrap(), + SequencerRights { read_rights: 1u128, cancel_rights: 2u128 } + ); + Rolldown::update_l2_from_l1_unsafe(RuntimeOrigin::signed(ALICE), deposit_update) + .unwrap(); + assert_eq!( + *SequencersRights::::get(consts::CHAIN).get(&ALICE).unwrap(), + SequencerRights { read_rights: 0u128, cancel_rights: 2u128 } + ); + + Rolldown::force_cancel_requests_from_l1( + RuntimeOrigin::root(), + consts::CHAIN, + 15u128.into(), + ) + .unwrap(); + assert_eq!( + *SequencersRights::::get(consts::CHAIN).get(&ALICE).unwrap(), + SequencerRights { read_rights: 1u128, cancel_rights: 2u128 } + ); + }); +} + +#[test] +#[serial] +fn execute_a_lot_of_requests_in_following_blocks() { + ExtBuilder::new() + .issue(ALICE, ETH_TOKEN_ADDRESS_MGX, 0u128) + .execute_with_default_mocks(|| { + forward_to_block::(10); + + let requests_count = 25; + + let dummy_update = L1UpdateRequest::Deposit(messages::Deposit { + requestId: Default::default(), + depositRecipient: DummyAddressConverter::convert_back(CHARLIE), + tokenAddress: ETH_TOKEN_ADDRESS, + amount: sp_core::U256::from(MILLION), + timeStamp: sp_core::U256::from(1), + ferryTip: sp_core::U256::from(0), + }); + + let requests = vec![dummy_update; requests_count]; + + let deposit_update = L1UpdateBuilder::default().with_requests(requests).build(); + Rolldown::update_l2_from_l1_unsafe(RuntimeOrigin::signed(ALICE), deposit_update) + .unwrap(); + + forward_to_block::(14); + assert_eq!(LastProcessedRequestOnL2::::get(Chain::Ethereum), 0u128.into()); + assert_eq!(UpdatesExecutionQueueNextId::::get(), 0u128); + + forward_to_next_block::(); + assert_eq!( + LastProcessedRequestOnL2::::get(Chain::Ethereum), + Rolldown::get_max_requests_per_block().into() + ); + + forward_to_next_block::(); + assert_eq!( + LastProcessedRequestOnL2::::get(Chain::Ethereum), + (2u128 * Rolldown::get_max_requests_per_block()).into() + ); + + forward_to_next_block::(); + assert_eq!( + LastProcessedRequestOnL2::::get(Chain::Ethereum), + requests_count as u128 + ); + + forward_to_next_block::(); + assert_eq!( + LastProcessedRequestOnL2::::get(Chain::Ethereum), + requests_count as u128 + ); + }); +} + +#[test] +#[serial] +fn ignore_duplicated_requests_when_already_executed() { + ExtBuilder::new() + .issue(ALICE, ETH_TOKEN_ADDRESS_MGX, 0u128) + .execute_with_default_mocks(|| { + let dummy_request = L1UpdateRequest::Deposit(messages::Deposit { + requestId: Default::default(), + depositRecipient: DummyAddressConverter::convert_back(CHARLIE), + tokenAddress: ETH_TOKEN_ADDRESS, + amount: sp_core::U256::from(MILLION), + timeStamp: sp_core::U256::from(1), + ferryTip: sp_core::U256::from(0), + }); + + let first_update = + L1UpdateBuilder::default().with_requests(vec![dummy_request.clone(); 5]).build(); + let second_update = + L1UpdateBuilder::default().with_requests(vec![dummy_request; 6]).build(); + + forward_to_block::(10); + Rolldown::update_l2_from_l1_unsafe(RuntimeOrigin::signed(ALICE), first_update).unwrap(); + + forward_to_block::(11); + Rolldown::update_l2_from_l1_unsafe(RuntimeOrigin::signed(BOB), second_update).unwrap(); + + forward_to_block::(14); + assert_eq!(LastProcessedRequestOnL2::::get(Chain::Ethereum), 0u128.into()); + + forward_to_block::(15); + assert_eq!(LastProcessedRequestOnL2::::get(Chain::Ethereum), 5u128.into()); + + forward_to_next_block::(); + forward_to_next_block::(); + assert_eq!(LastProcessedRequestOnL2::::get(Chain::Ethereum), 6u128.into()); + }); +} + +#[test] +#[serial] +fn process_l1_reads_in_order() { + ExtBuilder::new() + .issue(ALICE, ETH_TOKEN_ADDRESS_MGX, 0u128) + .execute_with_default_mocks(|| { + let dummy_request = L1UpdateRequest::Deposit(messages::Deposit { + requestId: Default::default(), + depositRecipient: DummyAddressConverter::convert_back(CHARLIE), + tokenAddress: ETH_TOKEN_ADDRESS, + amount: sp_core::U256::from(MILLION), + timeStamp: sp_core::U256::from(1), + ferryTip: sp_core::U256::from(0), + }); + + let first_update = L1UpdateBuilder::default() + .with_requests(vec![dummy_request.clone(); 11]) + .build(); + let second_update = + L1UpdateBuilder::default().with_requests(vec![dummy_request; 20]).build(); + + forward_to_block::(10); + Rolldown::update_l2_from_l1_unsafe(RuntimeOrigin::signed(ALICE), first_update).unwrap(); + + forward_to_block::(11); + Rolldown::update_l2_from_l1_unsafe(RuntimeOrigin::signed(BOB), second_update).unwrap(); + + forward_to_block::(14); + assert_eq!(LastProcessedRequestOnL2::::get(Chain::Ethereum), 0u128.into()); + + forward_to_block::(15); + assert_eq!(LastProcessedRequestOnL2::::get(Chain::Ethereum), 10u128.into()); + + forward_to_next_block::(); + assert_eq!(LastProcessedRequestOnL2::::get(Chain::Ethereum), 11u128.into()); + + forward_to_next_block::(); + forward_to_next_block::(); + assert_eq!(LastProcessedRequestOnL2::::get(Chain::Ethereum), 20u128.into()); + }); +} + +#[test] +#[serial] +fn check_request_ids_starts_from_one() { + ExtBuilder::new().execute_with_default_mocks(|| { + let requests = vec![L1UpdateRequest::Deposit(Default::default())]; + + assert_err!( + Rolldown::update_l2_from_l1_unsafe( + RuntimeOrigin::signed(ALICE), + L1UpdateBuilder::new() + .with_requests(requests.clone()) + .with_offset(0u128) + .build() + ), + Error::::WrongRequestId + ); + + assert_err!( + Rolldown::update_l2_from_l1_unsafe( + RuntimeOrigin::signed(ALICE), + L1UpdateBuilder::new().with_requests(requests).with_offset(2u128).build() + ), + Error::::WrongRequestId + ); + }); +} + +#[test] +#[serial] +fn reject_consecutive_update_with_invalid_counters() { + ExtBuilder::new().execute_with_default_mocks(|| { + forward_to_block::(10); + + let deposit_update = L1UpdateBuilder::default() + .with_requests(vec![L1UpdateRequest::Deposit(Default::default())]) + .with_offset(100u128) + .build(); + + assert_err!( + Rolldown::update_l2_from_l1_unsafe(RuntimeOrigin::signed(ALICE), deposit_update), + Error::::WrongRequestId + ); + }); +} + +#[test] +#[serial] +fn reject_update_with_invalid_too_high_request_id() { + ExtBuilder::new().execute_with_default_mocks(|| { + forward_to_block::(10); + + let deposit_update = L1UpdateBuilder::default() + .with_requests(vec![L1UpdateRequest::Deposit(Default::default())]) + .with_offset(2u128) + .build(); + + assert_err!( + Rolldown::update_l2_from_l1_unsafe(RuntimeOrigin::signed(ALICE), deposit_update), + Error::::WrongRequestId + ); + }); +} + +#[test] +#[serial] +// changed to accept +// seq gets the rights BEFORE LastProcessedRequestOnL2 is updated +// a single request would be duplicated, extrinsic fail and break seq +fn accept_update_without_new_updates() { + ExtBuilder::new().execute_with_default_mocks(|| { + forward_to_block::(10); + + let deposit_update = L1UpdateBuilder::default() + .with_requests(vec![L1UpdateRequest::Deposit(Default::default())]) + .with_offset(1u128) + .build(); + + Rolldown::update_l2_from_l1_unsafe(RuntimeOrigin::signed(ALICE), deposit_update.clone()) + .unwrap(); + assert_eq!(LastProcessedRequestOnL2::::get(consts::CHAIN), 0u128.into()); + + forward_to_block::(16); + assert_eq!(LastProcessedRequestOnL2::::get(consts::CHAIN), 1u128.into()); + + assert_ok!(Rolldown::update_l2_from_l1_unsafe( + RuntimeOrigin::signed(ALICE), + deposit_update + )); + }); +} + +#[test] +#[serial] +fn reject_second_update_in_the_same_block() { + ExtBuilder::new().execute_with_default_mocks(|| { + forward_to_block::(10); + let deposit_update = L1UpdateBuilder::default() + .with_requests(vec![L1UpdateRequest::Deposit(Default::default())]) + .build(); + + Rolldown::update_l2_from_l1_unsafe(RuntimeOrigin::signed(ALICE), deposit_update.clone()) + .unwrap(); + assert_err!( + Rolldown::update_l2_from_l1_unsafe(RuntimeOrigin::signed(BOB), deposit_update), + Error::::MultipleUpdatesInSingleBlock + ) + }); +} + +#[test] +#[serial] +fn accept_consecutive_update_split_into_two() { + ExtBuilder::new() + .issue(ALICE, ETH_TOKEN_ADDRESS_MGX, 0u128) + .execute_with_default_mocks(|| { + forward_to_block::(10); + + // imagine that there are 20 request on L1 waiting to be processed + // they need to be split into 2 update_l2_from_l1_unsafe calls + + let dummy_update = L1UpdateRequest::Deposit(messages::Deposit { + requestId: Default::default(), + depositRecipient: DummyAddressConverter::convert_back(CHARLIE), + tokenAddress: ETH_TOKEN_ADDRESS, + amount: sp_core::U256::from(MILLION), + timeStamp: sp_core::U256::from(1), + ferryTip: sp_core::U256::from(0), + }); + + let first_update = L1UpdateBuilder::default() + .with_requests(vec![ + dummy_update.clone(); + (2 * Rolldown::get_max_requests_per_block()) as usize + ]) + .with_offset(1u128) + .build(); + + Rolldown::update_l2_from_l1_unsafe(RuntimeOrigin::signed(ALICE), first_update).unwrap(); + + forward_to_next_block::(); + assert_eq!(LastProcessedRequestOnL2::::get(Chain::Ethereum), 0); + + forward_to_block::(15); + assert_eq!(LastProcessedRequestOnL2::::get(Chain::Ethereum), 10); + + forward_to_next_block::(); + assert_eq!(LastProcessedRequestOnL2::::get(Chain::Ethereum), 20); + }); +} + +#[test] +#[serial] +fn execute_two_consecutive_incremental_requests() { + ExtBuilder::new() + .issue(ALICE, ETH_TOKEN_ADDRESS_MGX, 0u128) + .execute_with_default_mocks(|| { + let dummy_update = L1UpdateRequest::Deposit(messages::Deposit { + requestId: Default::default(), + depositRecipient: DummyAddressConverter::convert_back(CHARLIE), + tokenAddress: ETH_TOKEN_ADDRESS, + amount: sp_core::U256::from(MILLION), + timeStamp: sp_core::U256::from(1), + ferryTip: sp_core::U256::from(0), + }); + + let first_update = L1UpdateBuilder::default() + .with_requests(vec![dummy_update.clone()]) + .with_offset(1u128) + .build(); + + let second_update = L1UpdateBuilder::default() + .with_requests(vec![dummy_update.clone(), dummy_update.clone()]) + .with_offset(1u128) + .build(); + + forward_to_block::(10); + Rolldown::update_l2_from_l1_unsafe(RuntimeOrigin::signed(ALICE), first_update).unwrap(); + + forward_to_block::(11); + Rolldown::update_l2_from_l1_unsafe(RuntimeOrigin::signed(BOB), second_update).unwrap(); + + forward_to_block::(14); + assert_eq!(TokensOf::::free_balance(ETH_TOKEN_ADDRESS_MGX, &CHARLIE), 0_u128); + + forward_to_block::(15); + assert_eq!(TokensOf::::free_balance(ETH_TOKEN_ADDRESS_MGX, &CHARLIE), MILLION); + + forward_to_next_block::(); + forward_to_next_block::(); + assert_eq!( + TokensOf::::free_balance(ETH_TOKEN_ADDRESS_MGX, &CHARLIE), + 2 * MILLION + ); + + forward_to_next_block::(); + assert_eq!( + TokensOf::::free_balance(ETH_TOKEN_ADDRESS_MGX, &CHARLIE), + 2 * MILLION + ); + }); +} + +#[test] +fn test_conversion_address() { + let byte_address: [u8; 20] = DummyAddressConverter::convert_back(consts::CHARLIE); + assert_eq!(DummyAddressConverter::convert(byte_address), consts::CHARLIE); +} + +#[test] +#[serial] +fn test_withdraw() { + ExtBuilder::new() + .issue(ALICE, ETH_TOKEN_ADDRESS_MGX, MILLION) + .issue(ALICE, NativeCurrencyId::get(), MILLION) + .execute_with_default_mocks(|| { + assert_eq!(TokensOf::::total_issuance(ETH_TOKEN_ADDRESS_MGX), MILLION); + Rolldown::withdraw( + RuntimeOrigin::signed(ALICE), + consts::CHAIN, + ETH_RECIPIENT_ACCOUNT, + ETH_TOKEN_ADDRESS, + 1_000_000u128, + 0u128.into(), + ) + .unwrap(); + + let withdrawal_update = Withdrawal { + requestId: (Origin::L2, 1u128).into(), + withdrawalRecipient: ETH_RECIPIENT_ACCOUNT, + tokenAddress: ETH_TOKEN_ADDRESS, + amount: U256::from(1_000_000u128), + ferryTip: U256::from(0), + }; + // check iftokens were burned + assert_eq!(TokensOf::::free_balance(ETH_TOKEN_ADDRESS_MGX, &ALICE), 0_u128); + assert_eq!(TokensOf::::total_issuance(ETH_TOKEN_ADDRESS_MGX), 0_u128); + assert_eq!( + L2Requests::::get(Chain::Ethereum, RequestId::new(Origin::L2, 1u128)) + .unwrap() + .0, + L2Request::Withdrawal(withdrawal_update) + ); + assert_eq!(Rolldown::get_next_l2_request_id(Chain::Ethereum), 2); + + // check withdraw fee + let fee = <::WithdrawFee as Convert<_, _>>::convert(Chain::Ethereum); + assert_eq!( + TokensOf::::free_balance(NativeCurrencyId::get(), &ALICE), + MILLION - fee + ); + assert_eq!( + TokensOf::::free_balance( + NativeCurrencyId::get(), + &Rolldown::treasury_account_id() + ), + fee + ); + }); +} + +#[test] +#[serial] +fn test_withdraw_of_non_existing_token_returns_token_does_not_exist_error() { + ExtBuilder::new() + .issue(ALICE, ETH_TOKEN_ADDRESS_MGX, MILLION) + .execute_without_mocks([Mocks::GetL1AssetId], || { + let get_l1_asset_id_mock = MockAssetRegistryProviderApi::get_l1_asset_id_context(); + get_l1_asset_id_mock.expect().return_const(None); + + assert_err!( + Rolldown::withdraw( + RuntimeOrigin::signed(ALICE), + consts::CHAIN, + ETH_RECIPIENT_ACCOUNT, + hex!("0123456789012345678901234567890123456789"), + 1_000_000u128, + 0u128.into(), + ), + Error::::TokenDoesNotExist + ); + }); +} + +#[test] +#[serial] +fn error_on_withdraw_too_much() { + ExtBuilder::new() + .issue(ALICE, ETH_TOKEN_ADDRESS_MGX, MILLION) + .execute_with_default_mocks(|| { + assert_err!( + Rolldown::withdraw( + RuntimeOrigin::signed(ALICE), + consts::CHAIN, + ETH_RECIPIENT_ACCOUNT, + ETH_TOKEN_ADDRESS, + 10_000_000u128, + 0u128.into(), + ), + Error::::NotEnoughAssets + ); + }); +} + +#[test] +#[serial] +fn test_reproduce_bug_with_incremental_updates() { + ExtBuilder::new() + .issue(ALICE, ETH_TOKEN_ADDRESS_MGX, 10_000u128) + .issue(ALICE, NativeCurrencyId::get(), 10_000u128) + .execute_with_default_mocks(|| { + let first_update = L1UpdateBuilder::new() + .with_requests(vec![ + L1UpdateRequest::Deposit(messages::Deposit { + requestId: RequestId::new(Origin::L1, 1u128), + depositRecipient: DummyAddressConverter::convert_back(CHARLIE), + tokenAddress: ETH_TOKEN_ADDRESS, + amount: sp_core::U256::from(MILLION), + timeStamp: sp_core::U256::from(1), + ferryTip: sp_core::U256::from(0), + }), + L1UpdateRequest::Deposit(messages::Deposit { + requestId: RequestId::new(Origin::L1, 2u128), + depositRecipient: DummyAddressConverter::convert_back(CHARLIE), + tokenAddress: ETH_TOKEN_ADDRESS, + amount: sp_core::U256::from(MILLION), + timeStamp: sp_core::U256::from(1), + ferryTip: sp_core::U256::from(0), + }), + ]) + .with_offset(1u128) + .build(); + + let second_update = L1UpdateBuilder::new() + .with_requests(vec![L1UpdateRequest::Deposit(messages::Deposit { + requestId: RequestId::new(Origin::L1, 3u128), + depositRecipient: DummyAddressConverter::convert_back(CHARLIE), + tokenAddress: ETH_TOKEN_ADDRESS, + amount: sp_core::U256::from(MILLION), + timeStamp: sp_core::U256::from(1), + ferryTip: sp_core::U256::from(0), + })]) + .build(); + + forward_to_block::(10); + Rolldown::update_l2_from_l1_unsafe(RuntimeOrigin::signed(ALICE), first_update).unwrap(); + + forward_to_block::(20); + assert!(!L2Requests::::contains_key( + Chain::Ethereum, + RequestId::new(Origin::L2, 3u128) + )); + Rolldown::withdraw( + RuntimeOrigin::signed(ALICE), + consts::CHAIN, + ETH_RECIPIENT_ACCOUNT, + ETH_TOKEN_ADDRESS, + 10u128, + 0u128.into(), + ) + .unwrap(); + assert_eq!(Rolldown::get_last_processed_request_on_l2(Chain::Ethereum), 2_u128.into()); + let withdrawal_update = + L2Requests::::get(Chain::Ethereum, RequestId::new(Origin::L2, 1u128)); + assert!(matches!(withdrawal_update, Some((L2Request::Withdrawal(_), _)))); + + Rolldown::update_l2_from_l1_unsafe(RuntimeOrigin::signed(ALICE), second_update) + .unwrap(); + + forward_to_block::(40); + assert!(!L2Requests::::contains_key( + Chain::Ethereum, + RequestId::new(Origin::L2, 3u128) + )); + }); +} + +#[test] +#[serial] +fn test_new_sequencer_active() { + ExtBuilder::new_without_default_sequencers().build().execute_with(|| { + for i in 0..100 { + Rolldown::new_sequencer_active(consts::CHAIN, &i); + let read_rights: u128 = 1; + let cancel_rights: u128 = i.into(); + assert_eq!( + SequencersRights::::get(consts::CHAIN).into_values().count() as u128, + >::into(i) + 1 + ); + assert!(SequencersRights::::get(consts::CHAIN) + .iter() + .all(|(_, x)| x.read_rights == read_rights && x.cancel_rights == cancel_rights)); + + assert_eq!( + SequencersRights::::get(consts::CHAIN).into_values().count(), + (i + 1) as usize + ); + } + }); +} + +#[test] +#[serial] +fn test_sequencer_unstaking() { + ExtBuilder::new_without_default_sequencers().build().execute_with(|| { + let is_maintenance_mock = MockMaintenanceStatusProviderApi::is_maintenance_context(); + is_maintenance_mock.expect().return_const(false); + forward_to_block::(1); + let dispute_period_length = Rolldown::get_dispute_period(consts::CHAIN).unwrap(); + let now = frame_system::Pallet::::block_number().saturated_into::(); + + LastUpdateBySequencer::::insert((consts::CHAIN, ALICE), now); + forward_to_block::((now + dispute_period_length).saturated_into::()); + assert_err!( + Rolldown::sequencer_unstaking(consts::CHAIN, &ALICE), + Error::::SequencerLastUpdateStillInDisputePeriod + ); + forward_to_block::((now + dispute_period_length + 1).saturated_into::()); + assert_ok!(Rolldown::sequencer_unstaking(consts::CHAIN, &ALICE)); + assert_eq!(LastUpdateBySequencer::::get((consts::CHAIN, ALICE)), 0); + + AwaitingCancelResolution::::mutate(consts::CHAIN, |v| { + v.insert((ALICE, 0u128, DisputeRole::Canceler)) + }); + assert_err!( + Rolldown::sequencer_unstaking(consts::CHAIN, &ALICE), + Error::::SequencerAwaitingCancelResolution + ); + + AwaitingCancelResolution::::mutate(consts::CHAIN, |v| { + v.remove(&(ALICE, 0u128, DisputeRole::Canceler)) + }); + assert_ok!(Rolldown::sequencer_unstaking(consts::CHAIN, &ALICE)); + assert_eq!(AwaitingCancelResolution::::get(consts::CHAIN), BTreeSet::new()); + }); +} + +#[test] +#[serial] +fn test_last_update_by_sequencer_is_updated() { + ExtBuilder::new().execute_with_default_mocks(|| { + let block = 36; + forward_to_block::(block); + + assert_eq!(LastUpdateBySequencer::::get((consts::CHAIN, ALICE)), 0); + + let update = L1UpdateBuilder::default() + .with_requests(vec![L1UpdateRequest::Deposit(Default::default())]) + .build(); + Rolldown::update_l2_from_l1_unsafe(RuntimeOrigin::signed(ALICE), update).unwrap(); + + assert_eq!(LastUpdateBySequencer::::get((consts::CHAIN, ALICE)), block.into()); + }); +} + +#[test] +#[serial] +fn test_cancel_updates_awaiting_cancel_resolution() { + ExtBuilder::new() + .issue(ETH_RECIPIENT_ACCOUNT_MGX, ETH_TOKEN_ADDRESS_MGX, MILLION) + .execute_with_default_mocks(|| { + forward_to_block::(10); + + let deposit_update = L1UpdateBuilder::default() + .with_requests(vec![ + L1UpdateRequest::Deposit(Default::default()), + L1UpdateRequest::Deposit(Default::default()), + ]) + .build(); + + assert!(!PendingSequencerUpdates::::contains_key(15u128, Chain::Ethereum)); + + // Act + Rolldown::update_l2_from_l1_unsafe( + RuntimeOrigin::signed(ALICE), + deposit_update.clone(), + ) + .unwrap(); + assert!(PendingSequencerUpdates::::contains_key(15u128, Chain::Ethereum)); + + let l2_request_id = Rolldown::get_next_l2_request_id(Chain::Ethereum); + Rolldown::cancel_requests_from_l1( + RuntimeOrigin::signed(BOB), + consts::CHAIN, + 15u128.into(), + ) + .unwrap(); + + assert_event_emitted!(Event::L1ReadCanceled { + canceled_sequencer_update: 15u128, + chain: consts::CHAIN, + assigned_id: RequestId::new(Origin::L2, l2_request_id) + }); + + // Assert + assert_eq!( + AwaitingCancelResolution::::get(consts::CHAIN), + BTreeSet::from([ + (ALICE, 1, DisputeRole::Submitter), + (BOB, 1, DisputeRole::Canceler), + ]) + ); + }); +} + +#[test] +#[serial] +fn test_cancel_resolution_updates_awaiting_cancel_resolution() { + ExtBuilder::new() + .issue(ETH_RECIPIENT_ACCOUNT_MGX, ETH_TOKEN_ADDRESS_MGX, MILLION) + .execute_with_default_mocks(|| { + forward_to_block::(10); + + // Arrange + + let deposit_update = L1UpdateBuilder::default() + .with_requests(vec![L1UpdateRequest::Deposit(Default::default())]) + .build(); + + let l2_request_id = Rolldown::get_next_l2_request_id(Chain::Ethereum); + + let cancel_resolution = L1UpdateBuilder::default() + .with_requests(vec![L1UpdateRequest::CancelResolution( + messages::CancelResolution { + requestId: Default::default(), + l2RequestId: l2_request_id, + cancelJustified: true, + timeStamp: sp_core::U256::from(1), + }, + )]) + .with_offset(1u128) + .build(); + + // Act + Rolldown::update_l2_from_l1_unsafe(RuntimeOrigin::signed(ALICE), deposit_update) + .unwrap(); + forward_to_block::(11); + Rolldown::cancel_requests_from_l1(RuntimeOrigin::signed(BOB), consts::CHAIN, 15u128) + .unwrap(); + forward_to_block::(12); + Rolldown::update_l2_from_l1_unsafe(RuntimeOrigin::signed(BOB), cancel_resolution) + .unwrap(); + assert!(AwaitingCancelResolution::::get(consts::CHAIN).contains(&( + ALICE, + 1, + DisputeRole::Submitter + ))); + assert!(AwaitingCancelResolution::::get(consts::CHAIN).contains(&( + BOB, + 1, + DisputeRole::Canceler + ))); + forward_to_block::(16); + + let slash_sequencer_mock = MockSequencerStakingProviderApi::slash_sequencer_context(); + slash_sequencer_mock + .expect() + .withf(|chain, a, b| { + *chain == consts::CHAIN && *a == ALICE && b.cloned() == Some(BOB) + }) + .times(1) + .return_const(Ok(().into())); + forward_to_block::(17); + forward_to_next_block::(); + + assert_eq!(AwaitingCancelResolution::::get(consts::CHAIN), BTreeSet::new()); + assert_eq!(AwaitingCancelResolution::::get(consts::CHAIN), BTreeSet::new()); + }) +} + +#[test] +#[serial] +fn test_handle_sequencer_deactivations() { + ExtBuilder::new_without_default_sequencers().build().execute_with(|| { + let total_sequencers = 100; + for i in 0..total_sequencers { + Rolldown::new_sequencer_active(consts::CHAIN, &i); + } + + let n_max = 14; + let mut acc = 0; + for n in 1..n_max { + Rolldown::handle_sequencer_deactivations( + consts::CHAIN, + Vec::::from_iter(acc..(n + acc)), + ); + acc += n; + let read_rights: u128 = 1; + let cancel_rights: u128 = (total_sequencers - acc - 1).into(); + assert_eq!( + SequencersRights::::get(consts::CHAIN).into_values().count() as u128, + >::into(total_sequencers - acc) + ); + assert!(SequencersRights::::get(consts::CHAIN) + .iter() + .all(|(_, x)| x.read_rights == read_rights && x.cancel_rights == cancel_rights)); + assert_eq!( + SequencersRights::::get(consts::CHAIN).keys().count(), + (total_sequencers - acc) as usize + ); + } + }); +} + +#[test] +#[serial] +fn test_maintenance_mode_blocks_extrinsics() { + ExtBuilder::new().build().execute_with(|| { + let is_maintenance_mock = MockMaintenanceStatusProviderApi::is_maintenance_context(); + is_maintenance_mock.expect().return_const(true); + + assert_err!( + Rolldown::update_l2_from_l1_unsafe(RuntimeOrigin::signed(ALICE), Default::default()), + Error::::BlockedByMaintenanceMode + ); + assert_err!( + Rolldown::force_update_l2_from_l1(RuntimeOrigin::root(), Default::default()), + Error::::BlockedByMaintenanceMode + ); + assert_err!( + Rolldown::cancel_requests_from_l1( + RuntimeOrigin::signed(ALICE), + consts::CHAIN, + Default::default() + ), + Error::::BlockedByMaintenanceMode + ); + assert_err!( + Rolldown::withdraw( + RuntimeOrigin::signed(ALICE), + consts::CHAIN, + Default::default(), + Default::default(), + Default::default(), + Default::default(), + ), + Error::::BlockedByMaintenanceMode + ); + assert_err!( + Rolldown::force_cancel_requests_from_l1( + RuntimeOrigin::root(), + consts::CHAIN, + Default::default() + ), + Error::::BlockedByMaintenanceMode + ); + }); +} + +#[test] +#[serial] +fn test_single_sequencer_cannot_cancel_request_without_cancel_rights_in_same_block() { + ExtBuilder::new_without_default_sequencers() + .issue(ETH_RECIPIENT_ACCOUNT_MGX, ETH_TOKEN_ADDRESS_MGX, MILLION) + .execute_with_default_mocks(|| { + forward_to_block::(10); + Rolldown::new_sequencer_active(consts::CHAIN, &BOB); + + // Arrange + let slash_sequencer_mock = MockSequencerStakingProviderApi::slash_sequencer_context(); + slash_sequencer_mock.expect().return_const(Ok(().into())); + + let deposit_update = L1UpdateBuilder::default() + .with_requests(vec![ + L1UpdateRequest::Deposit(Default::default()), + L1UpdateRequest::Deposit(Default::default()), + ]) + .build(); + + assert_eq!( + *SequencersRights::::get(consts::CHAIN).get(&BOB).unwrap(), + SequencerRights { read_rights: 1u128, cancel_rights: 0u128 } + ); + + // Act + Rolldown::update_l2_from_l1_unsafe(RuntimeOrigin::signed(BOB), deposit_update).unwrap(); + + assert_eq!( + *SequencersRights::::get(consts::CHAIN).get(&BOB).unwrap(), + SequencerRights { read_rights: 0u128, cancel_rights: 0u128 } + ); + + assert_err!( + Rolldown::cancel_requests_from_l1( + RuntimeOrigin::signed(BOB), + consts::CHAIN, + 15u128.into() + ), + Error::::CancelRightsExhausted + ); + }); +} + +#[test] +#[serial] +fn test_single_sequencer_cannot_cancel_request_without_cancel_rights_in_next_block() { + ExtBuilder::new_without_default_sequencers() + .issue(ETH_RECIPIENT_ACCOUNT_MGX, ETH_TOKEN_ADDRESS_MGX, MILLION) + .execute_with_default_mocks(|| { + forward_to_block::(10); + Rolldown::new_sequencer_active(consts::CHAIN, &BOB); + + // Arrange + let slash_sequencer_mock = MockSequencerStakingProviderApi::slash_sequencer_context(); + slash_sequencer_mock.expect().return_const(Ok(().into())); + + let deposit_update = L1UpdateBuilder::default() + .with_requests(vec![ + L1UpdateRequest::Deposit(Default::default()), + L1UpdateRequest::Deposit(Default::default()), + ]) + .build(); + + assert_eq!( + *SequencersRights::::get(consts::CHAIN).get(&BOB).unwrap(), + SequencerRights { read_rights: 1u128, cancel_rights: 0u128 } + ); + + // Act + Rolldown::update_l2_from_l1_unsafe(RuntimeOrigin::signed(BOB), deposit_update).unwrap(); + + assert_eq!( + *SequencersRights::::get(consts::CHAIN).get(&BOB).unwrap(), + SequencerRights { read_rights: 0u128, cancel_rights: 0u128 } + ); + + forward_to_block::(11); + assert_err!( + Rolldown::cancel_requests_from_l1( + RuntimeOrigin::signed(BOB), + consts::CHAIN, + 15u128.into() + ), + Error::::CancelRightsExhausted + ); + }); +} + +#[test] +#[serial] +fn consider_awaiting_cancel_resolutions_and_cancel_disputes_when_assigning_initial_cancel_rights_to_sequencer( +) { + ExtBuilder::new() + .issue(ETH_RECIPIENT_ACCOUNT_MGX, ETH_TOKEN_ADDRESS_MGX, MILLION) + .execute_with_default_mocks(|| { + // Arrange + let slash_sequencer_mock = MockSequencerStakingProviderApi::slash_sequencer_context(); + slash_sequencer_mock + .expect() + .withf(|chain, a, b| *chain == consts::CHAIN && *a == ALICE && b.is_none()) + .times(2) + .return_const(Ok(().into())); + + // honest update + let honest_update = L1UpdateBuilder::default() + .with_requests(vec![L1UpdateRequest::Deposit(Default::default())]) + .build(); + + forward_to_block::(10); + Rolldown::update_l2_from_l1_unsafe(RuntimeOrigin::signed(BOB), honest_update.clone()) + .unwrap(); + Rolldown::cancel_requests_from_l1(RuntimeOrigin::signed(ALICE), consts::CHAIN, 15u128) + .unwrap(); + + forward_to_block::(11); + Rolldown::update_l2_from_l1_unsafe( + RuntimeOrigin::signed(CHARLIE), + honest_update.clone(), + ) + .unwrap(); + Rolldown::cancel_requests_from_l1(RuntimeOrigin::signed(ALICE), consts::CHAIN, 16u128) + .unwrap(); + + // lets pretned that alice misbehaved and got slashed, as a result her stake dropped below + // active sequencer threshold and she got immadietely removed from sequencers set + Rolldown::handle_sequencer_deactivations(consts::CHAIN, vec![ALICE]); + + // then lets pretned that alice provided more stake and got approved as active sequencer + Rolldown::new_sequencer_active(consts::CHAIN, &ALICE); + + // resolve previous cancel disputes + Rolldown::force_update_l2_from_l1( + RuntimeOrigin::root(), + L1UpdateBuilder::default() + .with_requests(vec![ + L1UpdateRequest::CancelResolution(messages::CancelResolution { + requestId: Default::default(), + l2RequestId: 1u128, + cancelJustified: false, + timeStamp: sp_core::U256::from(1), + }), + L1UpdateRequest::CancelResolution(messages::CancelResolution { + requestId: Default::default(), + l2RequestId: 2u128, + cancelJustified: false, + timeStamp: sp_core::U256::from(1), + }), + ]) + .build(), + ) + .unwrap(); + + forward_to_next_block::(); + forward_to_next_block::(); + assert_eq!( + *SequencersRights::::get(consts::CHAIN).get(&ALICE).unwrap(), + SequencerRights { read_rights: 1u128, cancel_rights: 2u128 } + ); + }); +} + +#[test] +#[serial] +fn consider_awaiting_l1_sequencer_update_in_dispute_period_when_assigning_initial_read_rights_to_sequencer( +) { + ExtBuilder::new() + .issue(ETH_RECIPIENT_ACCOUNT_MGX, ETH_TOKEN_ADDRESS_MGX, MILLION) + .execute_with_default_mocks(|| { + // Arrange + let slash_sequencer_mock = MockSequencerStakingProviderApi::slash_sequencer_context(); + slash_sequencer_mock + .expect() + .withf(|chain, a, b| *chain == consts::CHAIN && *a == ALICE && b.cloned() == None) + .times(1) + .return_const(Ok(().into())); + + let honest_update = L1UpdateBuilder::default() + .with_requests(vec![L1UpdateRequest::Deposit(Default::default())]) + .build(); + forward_to_block::(10); + Rolldown::update_l2_from_l1_unsafe(RuntimeOrigin::signed(BOB), honest_update.clone()) + .unwrap(); + + // accidently canceling honest update + forward_to_block::(11); + Rolldown::cancel_requests_from_l1(RuntimeOrigin::signed(ALICE), consts::CHAIN, 15u128) + .unwrap(); + + forward_to_block::(12); + let honest_update = L1UpdateBuilder::default() + .with_requests(vec![ + L1UpdateRequest::Deposit(Default::default()), + L1UpdateRequest::CancelResolution(messages::CancelResolution { + requestId: Default::default(), + l2RequestId: 1u128, + cancelJustified: false, + timeStamp: sp_core::U256::from(1), + }), + ]) + .build(); + Rolldown::update_l2_from_l1_unsafe(RuntimeOrigin::signed(CHARLIE), honest_update) + .unwrap(); + + forward_to_block::(15); + let honest_update = L1UpdateBuilder::default() + .with_requests(vec![ + L1UpdateRequest::Deposit(Default::default()), + L1UpdateRequest::CancelResolution(messages::CancelResolution { + requestId: Default::default(), + l2RequestId: 1u128, + cancelJustified: false, + timeStamp: sp_core::U256::from(1), + }), + L1UpdateRequest::Deposit(Default::default()), + ]) + .build(); + Rolldown::update_l2_from_l1_unsafe(RuntimeOrigin::signed(ALICE), honest_update) + .unwrap(); + + forward_to_block::(17); + // at this point alice will be slashed by cancel resolution provided by CHALIE in block 12 + Rolldown::handle_sequencer_deactivations(consts::CHAIN, vec![ALICE]); + // then lets pretned that alice provided more stake and got approved as active sequencer + Rolldown::new_sequencer_active(consts::CHAIN, &ALICE); + forward_to_next_block::(); + assert_eq!( + *SequencersRights::::get(consts::CHAIN).get(&ALICE).unwrap(), + SequencerRights { read_rights: 0u128, cancel_rights: 2u128 } + ); + + // at this point ALICE is sequencer again and her update provided at block 13 gets executed + forward_to_block::(20); + assert_eq!( + *SequencersRights::::get(consts::CHAIN).get(&ALICE).unwrap(), + SequencerRights { read_rights: 1u128, cancel_rights: 2u128 } + ); + }); +} + +#[test] +#[serial] +fn consider_awaiting_cancel_resolutions_and_cancel_disputes_when_assigning_initial_read_rights_to_sequencer( +) { + ExtBuilder::new() + .issue(ETH_RECIPIENT_ACCOUNT_MGX, ETH_TOKEN_ADDRESS_MGX, MILLION) + .execute_with_default_mocks(|| { + // Arrange + let slash_sequencer_mock = MockSequencerStakingProviderApi::slash_sequencer_context(); + slash_sequencer_mock.expect().return_const(Ok(().into())); + + // honest update + let honest_update = L1UpdateBuilder::default() + .with_requests(vec![L1UpdateRequest::Deposit(Default::default())]) + .build(); + + forward_to_block::(10); + Rolldown::update_l2_from_l1_unsafe(RuntimeOrigin::signed(BOB), honest_update.clone()) + .unwrap(); + Rolldown::cancel_requests_from_l1(RuntimeOrigin::signed(ALICE), consts::CHAIN, 15u128) + .unwrap(); + + forward_to_block::(15); + Rolldown::update_l2_from_l1_unsafe(RuntimeOrigin::signed(ALICE), honest_update.clone()) + .unwrap(); + // lets assume single person controls multiple sequencers (alice&charlie) and charlie intentionally cancels honest update + Rolldown::cancel_requests_from_l1( + RuntimeOrigin::signed(CHARLIE), + consts::CHAIN, + 20u128, + ) + .unwrap(); + + // and then CHARLIE provbides honest update - as a result ALICE will be slashed + Rolldown::update_l2_from_l1_unsafe( + RuntimeOrigin::signed(CHARLIE), + L1UpdateBuilder::default() + .with_requests(vec![ + L1UpdateRequest::Deposit(Default::default()), + L1UpdateRequest::CancelResolution(messages::CancelResolution { + requestId: Default::default(), + l2RequestId: 1u128, + cancelJustified: false, + timeStamp: sp_core::U256::from(1), + }), + ]) + .build(), + ) + .unwrap(); + + forward_to_block::(20); + forward_to_next_block::(); + // alice is slashed for her first malicious cancel but then she got slashed with honest update but that has not been yet processed + Rolldown::handle_sequencer_deactivations(consts::CHAIN, vec![ALICE]); + + Rolldown::update_l2_from_l1_unsafe( + RuntimeOrigin::signed(CHARLIE), + L1UpdateBuilder::default() + .with_requests(vec![L1UpdateRequest::CancelResolution( + messages::CancelResolution { + requestId: Default::default(), + l2RequestId: 2u128, + cancelJustified: false, + timeStamp: sp_core::U256::from(1), + }, + )]) + .with_offset(3u128) + .build(), + ) + .unwrap(); + + forward_to_block::(24); + // lets consider alice provided more stake and just got into the active set of sequencers + Rolldown::new_sequencer_active(consts::CHAIN, &ALICE); + + forward_to_block::(25); + forward_to_next_block::(); + forward_to_next_block::(); + assert_eq!( + *SequencersRights::::get(consts::CHAIN).get(&ALICE).unwrap(), + SequencerRights { read_rights: 1u128, cancel_rights: 2u128 } + ); + }); +} + +#[test] +#[serial] +fn test_merkle_proof_works() { + ExtBuilder::new() + .issue(ALICE, ETH_TOKEN_ADDRESS_MGX, MILLION) + .issue(ALICE, NativeCurrencyId::get(), MILLION) + .execute_with_default_mocks(|| { + for i in 0..500 { + Rolldown::withdraw( + RuntimeOrigin::signed(ALICE), + consts::CHAIN, + ETH_RECIPIENT_ACCOUNT, + ETH_TOKEN_ADDRESS, + i as u128, + 0u128.into(), + ) + .unwrap(); + } + + let range = (1u128, 300u128); + let root_hash = Pallet::::get_merkle_root(consts::CHAIN, range); + let proof_hashes = Pallet::::get_merkle_proof_for_tx(consts::CHAIN, range, 257); + Pallet::::verify_merkle_proof_for_tx( + consts::CHAIN, + range, + root_hash, + 257, + proof_hashes, + ); + }); +} + +#[test] +#[serial] +fn test_batch_is_created_automatically_when_l2requests_count_exceeds_merkle_root_automatic_batch_size( +) { + ExtBuilder::new() + .issue(ALICE, ETH_TOKEN_ADDRESS_MGX, MILLION) + .issue(ALICE, NativeCurrencyId::get(), MILLION) + .build() + .execute_with(|| { + let selected_sequencer_mock = + MockSequencerStakingProviderApi::selected_sequencer_context(); + selected_sequencer_mock.expect().return_const(Some(consts::ALICE)); + let get_l1_asset_id_mock = MockAssetRegistryProviderApi::get_l1_asset_id_context(); + get_l1_asset_id_mock.expect().return_const(crate::tests::ETH_TOKEN_ADDRESS_MGX); + let is_maintenance_mock = MockMaintenanceStatusProviderApi::is_maintenance_context(); + is_maintenance_mock.expect().return_const(false); + let _selected_sequencer_mock = + MockSequencerStakingProviderApi::selected_sequencer_context(); + + forward_to_block::(10); + assert_eq!(L2RequestsBatchLast::::get().get(&consts::CHAIN), None); + + for _ in 0..Rolldown::automatic_batch_size() - 1 { + Rolldown::withdraw( + RuntimeOrigin::signed(ALICE), + consts::CHAIN, + ETH_RECIPIENT_ACCOUNT, + ETH_TOKEN_ADDRESS, + 1000u128, + 0u128.into(), + ) + .unwrap(); + } + forward_to_block::(11); + assert_eq!(L2RequestsBatchLast::::get().get(&consts::CHAIN), None); + + Rolldown::withdraw( + RuntimeOrigin::signed(ALICE), + consts::CHAIN, + ETH_RECIPIENT_ACCOUNT, + ETH_TOKEN_ADDRESS, + 1000u128, + 0u128.into(), + ) + .unwrap(); + assert_eq!(L2RequestsBatchLast::::get().get(&consts::CHAIN), None); + + forward_to_block::(12); + assert_eq!( + L2RequestsBatchLast::::get().get(&consts::CHAIN), + Some(&(12u64, 1u128, (1, 10))) + ); + + for _ in 0..Rolldown::automatic_batch_size() - 1 { + Rolldown::withdraw( + RuntimeOrigin::signed(ALICE), + consts::CHAIN, + ETH_RECIPIENT_ACCOUNT, + ETH_TOKEN_ADDRESS, + 1000u128, + 0u128.into(), + ) + .unwrap(); + } + + forward_to_block::(13); + assert_eq!( + L2RequestsBatchLast::::get().get(&consts::CHAIN), + Some(&(12u64, 1u128, (1, 10))) + ); + + Rolldown::withdraw( + RuntimeOrigin::signed(ALICE), + consts::CHAIN, + ETH_RECIPIENT_ACCOUNT, + ETH_TOKEN_ADDRESS, + 1000u128, + 0u128.into(), + ) + .unwrap(); + + assert_eq!( + L2RequestsBatchLast::::get().get(&consts::CHAIN), + Some(&(12u64, 1u128, (1, 10))) + ); + forward_to_block::(14); + assert_eq!( + L2RequestsBatchLast::::get().get(&consts::CHAIN), + Some(&(14u64, 2u128, (11, 20))) + ); + }); +} + +#[test] +#[serial] +fn test_batch_is_created_automatically_when_merkle_root_automatic_batch_period_passes() { + ExtBuilder::new() + .issue(ALICE, ETH_TOKEN_ADDRESS_MGX, MILLION) + .issue(ALICE, NativeCurrencyId::get(), MILLION) + .build() + .execute_with(|| { + let get_l1_asset_id_mock = MockAssetRegistryProviderApi::get_l1_asset_id_context(); + get_l1_asset_id_mock.expect().return_const(crate::tests::ETH_TOKEN_ADDRESS_MGX); + let is_maintenance_mock = MockMaintenanceStatusProviderApi::is_maintenance_context(); + is_maintenance_mock.expect().return_const(false); + let selected_sequencer_mock = + MockSequencerStakingProviderApi::selected_sequencer_context(); + selected_sequencer_mock.expect().return_const(Some(consts::ALICE)); + + forward_to_block::(1); + assert_eq!(L2RequestsBatchLast::::get().get(&consts::CHAIN), None); + + Rolldown::withdraw( + RuntimeOrigin::signed(ALICE), + consts::CHAIN, + ETH_RECIPIENT_ACCOUNT, + ETH_TOKEN_ADDRESS, + 1000u128, + 0u128.into(), + ) + .unwrap(); + + forward_to_block::((Rolldown::automatic_batch_period() as u64) - 1u64); + assert_eq!(L2RequestsBatchLast::::get().get(&consts::CHAIN), None); + forward_to_block::(Rolldown::automatic_batch_period() as u64); + assert_eq!( + L2RequestsBatchLast::::get().get(&consts::CHAIN), + Some(&(25u64, 1u128, (1, 1))) + ); + + Rolldown::withdraw( + RuntimeOrigin::signed(ALICE), + consts::CHAIN, + ETH_RECIPIENT_ACCOUNT, + ETH_TOKEN_ADDRESS, + 1000u128, + 0u128.into(), + ) + .unwrap(); + + forward_to_block::((2 * Rolldown::automatic_batch_period() as u64) - 1u64); + assert_eq!( + L2RequestsBatchLast::::get().get(&consts::CHAIN), + Some(&(25u64, 1u128, (1, 1))) + ); + forward_to_block::(2 * Rolldown::automatic_batch_period() as u64); + assert_eq!( + L2RequestsBatchLast::::get().get(&consts::CHAIN), + Some(&(50u64, 2u128, (2, 2))) + ); + + forward_to_block::(10 * Rolldown::automatic_batch_period() as u64); + assert_eq!( + L2RequestsBatchLast::::get().get(&consts::CHAIN), + Some(&(50u64, 2u128, (2, 2))) + ); + }); +} + +#[test] +#[serial] +fn test_batch_is_created_automatically_whenever_new_request_is_created_and_time_from_last_batch_is_greater_than_configurable_period( +) { + ExtBuilder::new() + .issue(ALICE, ETH_TOKEN_ADDRESS_MGX, MILLION) + .issue(ALICE, NativeCurrencyId::get(), MILLION) + .build() + .execute_with(|| { + let get_l1_asset_id_mock = MockAssetRegistryProviderApi::get_l1_asset_id_context(); + get_l1_asset_id_mock.expect().return_const(crate::tests::ETH_TOKEN_ADDRESS_MGX); + let is_maintenance_mock = MockMaintenanceStatusProviderApi::is_maintenance_context(); + is_maintenance_mock.expect().return_const(false); + let selected_sequencer_mock = + MockSequencerStakingProviderApi::selected_sequencer_context(); + selected_sequencer_mock.expect().return_const(Some(consts::ALICE)); + + forward_to_block::(1); + assert_eq!(L2RequestsBatchLast::::get().get(&consts::CHAIN), None); + + forward_to_block::(Rolldown::automatic_batch_period() as u64); + assert_eq!(L2RequestsBatchLast::::get().get(&consts::CHAIN), None); + + forward_to_block::((Rolldown::automatic_batch_period() + 1u128) as u64); + assert_eq!(L2RequestsBatchLast::::get().get(&consts::CHAIN), None); + + Rolldown::withdraw( + RuntimeOrigin::signed(ALICE), + consts::CHAIN, + ETH_RECIPIENT_ACCOUNT, + ETH_TOKEN_ADDRESS, + 1000u128, + 0u128.into(), + ) + .unwrap(); + + forward_to_block::((Rolldown::automatic_batch_period() + 2u128) as u64); + assert_eq!( + L2RequestsBatchLast::::get().get(&consts::CHAIN), + Some(&((Rolldown::automatic_batch_period() + 2u128) as u64, 1u128, (1u128, 1u128))) + ); + assert_event_emitted!(Event::TxBatchCreated { + chain: consts::CHAIN, + source: BatchSource::PeriodReached, + assignee: ALICE, + batch_id: 1, + range: (1, 1), + }); + }); +} + +#[test] +#[serial] +fn test_period_based_batch_respects_sized_batches() { + ExtBuilder::new() + .issue(ALICE, ETH_TOKEN_ADDRESS_MGX, MILLION) + .issue(ALICE, NativeCurrencyId::get(), MILLION) + .build() + .execute_with(|| { + let selected_sequencer_mock = + MockSequencerStakingProviderApi::selected_sequencer_context(); + selected_sequencer_mock.expect().return_const(Some(consts::ALICE)); + let get_l1_asset_id_mock = MockAssetRegistryProviderApi::get_l1_asset_id_context(); + get_l1_asset_id_mock.expect().return_const(crate::tests::ETH_TOKEN_ADDRESS_MGX); + let is_maintenance_mock = MockMaintenanceStatusProviderApi::is_maintenance_context(); + is_maintenance_mock.expect().return_const(false); + let _selected_sequencer_mock = + MockSequencerStakingProviderApi::selected_sequencer_context(); + + forward_to_block::(10); + assert_eq!(L2RequestsBatchLast::::get().get(&consts::CHAIN), None); + + for _ in 0..Rolldown::automatic_batch_size() { + Rolldown::withdraw( + RuntimeOrigin::signed(ALICE), + consts::CHAIN, + ETH_RECIPIENT_ACCOUNT, + ETH_TOKEN_ADDRESS, + 1000u128, + 0u128.into(), + ) + .unwrap(); + } + forward_to_block::(11); + assert_eq!( + L2RequestsBatchLast::::get().get(&consts::CHAIN), + Some(&(11u64.into(), 1u128, (1, 10))) + ); + Rolldown::withdraw( + RuntimeOrigin::signed(ALICE), + consts::CHAIN, + ETH_RECIPIENT_ACCOUNT, + ETH_TOKEN_ADDRESS, + 1000u128, + 0u128.into(), + ) + .unwrap(); + + forward_to_block::(Rolldown::automatic_batch_period() as u64); + assert_eq!( + L2RequestsBatchLast::::get().get(&consts::CHAIN), + Some(&(11u64.into(), 1u128, (1, 10))) + ); + + forward_to_block::(11 + Rolldown::automatic_batch_period() as u64); + assert_eq!( + L2RequestsBatchLast::::get().get(&consts::CHAIN), + Some(&(36u64.into(), 2u128, (11, 11))) + ); + }); +} + +#[test] +#[serial] +fn test_create_manual_batch_works() { + ExtBuilder::new() + .issue(ALICE, ETH_TOKEN_ADDRESS_MGX, MILLION) + .issue(ALICE, NativeCurrencyId::get(), MILLION) + .execute_with_default_mocks(|| { + forward_to_block::(10); + + Rolldown::withdraw( + RuntimeOrigin::signed(ALICE), + consts::CHAIN, + ETH_RECIPIENT_ACCOUNT, + ETH_TOKEN_ADDRESS, + 1_000u128, + 0u128.into(), + ) + .unwrap(); + assert_ok!(Rolldown::create_batch(RuntimeOrigin::signed(ALICE), consts::CHAIN, None)); + assert_event_emitted!(Event::TxBatchCreated { + chain: consts::CHAIN, + source: BatchSource::Manual, + assignee: ALICE, + batch_id: 1, + range: (1, 1), + }); + + Rolldown::withdraw( + RuntimeOrigin::signed(ALICE), + consts::CHAIN, + ETH_RECIPIENT_ACCOUNT, + ETH_TOKEN_ADDRESS, + 1_000u128, + 0u128.into(), + ) + .unwrap(); + + assert_ok!(Rolldown::create_batch(RuntimeOrigin::signed(ALICE), consts::CHAIN, None)); + assert_event_emitted!(Event::TxBatchCreated { + chain: consts::CHAIN, + source: BatchSource::Manual, + assignee: ALICE, + batch_id: 2, + range: (2, 2), + }); + }) +} + +#[test] +#[serial] +fn test_size_of_manually_created_batch_is_limited() { + ExtBuilder::new() + .issue(ALICE, ETH_TOKEN_ADDRESS_MGX, MILLION) + .issue(ALICE, NativeCurrencyId::get(), MILLION) + .execute_with_default_mocks(|| { + forward_to_block::(10); + + for _ in (0..2 * Rolldown::automatic_batch_size()) { + Rolldown::withdraw( + RuntimeOrigin::signed(ALICE), + consts::CHAIN, + ETH_RECIPIENT_ACCOUNT, + ETH_TOKEN_ADDRESS, + 1_000u128, + 0u128.into(), + ) + .unwrap(); + } + + assert_ok!(Rolldown::create_batch(RuntimeOrigin::signed(ALICE), consts::CHAIN, None)); + + assert_eq!(L2RequestsBatchLast::::get().get(&consts::CHAIN).unwrap().2 .0, 1u128); + + assert_eq!( + L2RequestsBatchLast::::get().get(&consts::CHAIN).unwrap().2 .1, + Rolldown::automatic_batch_size() + ); + }) +} + +#[test] +#[serial] +fn test_create_manual_batch_fails_for_invalid_alias_account() { + ExtBuilder::new() + .issue(ALICE, ETH_TOKEN_ADDRESS_MGX, MILLION) + .issue(ALICE, NativeCurrencyId::get(), MILLION) + .issue(BOB, ETH_TOKEN_ADDRESS_MGX, MILLION) + .execute_with_default_mocks(|| { + let selected_sequencer_mock = + MockSequencerStakingProviderApi::is_active_sequencer_alias_context(); + selected_sequencer_mock.expect().return_const(false); + + forward_to_block::(10); + + Rolldown::withdraw( + RuntimeOrigin::signed(ALICE), + consts::CHAIN, + ETH_RECIPIENT_ACCOUNT, + ETH_TOKEN_ADDRESS, + 1_000u128, + 0u128.into(), + ) + .unwrap(); + + assert_err!( + Rolldown::create_batch(RuntimeOrigin::signed(BOB), consts::CHAIN, Some(ALICE)), + Error::::UnknownAliasAccount + ); + }) +} + +#[test] +#[serial] +fn test_create_manual_batch_work_for_alias_account() { + ExtBuilder::new() + .issue(ALICE, ETH_TOKEN_ADDRESS_MGX, MILLION) + .issue(ALICE, NativeCurrencyId::get(), MILLION) + .issue(BOB, ETH_TOKEN_ADDRESS_MGX, MILLION) + .execute_with_default_mocks(|| { + let selected_sequencer_mock = + MockSequencerStakingProviderApi::is_active_sequencer_alias_context(); + selected_sequencer_mock.expect().return_const(true); + + forward_to_block::(10); + + Rolldown::withdraw( + RuntimeOrigin::signed(ALICE), + consts::CHAIN, + ETH_RECIPIENT_ACCOUNT, + ETH_TOKEN_ADDRESS, + 1_000u128, + 0u128.into(), + ) + .unwrap(); + + Rolldown::create_batch(RuntimeOrigin::signed(BOB), consts::CHAIN, Some(ALICE)).unwrap(); + assert_event_emitted!(Event::TxBatchCreated { + chain: consts::CHAIN, + source: BatchSource::Manual, + assignee: ALICE, + batch_id: 1, + range: (1, 1), + }); + assert_eq!( + L2RequestsBatchLast::::get().get(&consts::CHAIN), + Some(&(10u64.into(), 1u128, (1, 1))) + ); + }) +} + +#[test] +#[serial] +fn test_merkle_proof_for_single_element_tree_is_empty() { + ExtBuilder::new() + .issue(ALICE, ETH_TOKEN_ADDRESS_MGX, MILLION) + .issue(ALICE, NativeCurrencyId::get(), MILLION) + .execute_with_default_mocks(|| { + Rolldown::withdraw( + RuntimeOrigin::signed(ALICE), + consts::CHAIN, + ETH_RECIPIENT_ACCOUNT, + ETH_TOKEN_ADDRESS, + 1, + 0u128.into(), + ) + .unwrap(); + + let range = (1u128, 1u128); + let root_hash = Pallet::::get_merkle_root(consts::CHAIN, range); + let proof_hashes = Pallet::::get_merkle_proof_for_tx(consts::CHAIN, range, 1); + Pallet::::verify_merkle_proof_for_tx( + consts::CHAIN, + range, + root_hash, + 1, + proof_hashes, + ); + }); +} + +#[test] +#[serial] +fn test_manual_batch_fee_update() { + ExtBuilder::new().execute_with_default_mocks(|| { + forward_to_block::(10); + let fee = 12345; + assert_eq!(ManualBatchExtraFee::::get(), 0); + Rolldown::set_manual_batch_extra_fee(RuntimeOrigin::root(), fee).unwrap(); + assert_eq!(ManualBatchExtraFee::::get(), fee); + assert_event_emitted!(Event::ManualBatchExtraFeeSet(fee)); + }); +} + +#[test] +#[serial] +fn do_not_allow_for_batches_when_there_are_no_pending_requests() { + ExtBuilder::new() + .issue(ALICE, ETH_TOKEN_ADDRESS_MGX, MILLION) + .issue(BOB, ETH_TOKEN_ADDRESS_MGX, MILLION) + .execute_with_default_mocks(|| { + let selected_sequencer_mock = + MockSequencerStakingProviderApi::is_active_sequencer_alias_context(); + selected_sequencer_mock.expect().return_const(true); + + forward_to_block::(10); + + assert_err!( + Rolldown::create_batch(RuntimeOrigin::signed(BOB), consts::CHAIN, None,), + Error::::EmptyBatch + ); + }) +} + +#[test] +#[serial] +fn do_not_allow_for_batches_when_there_are_no_pending_requests2() { + ExtBuilder::new() + .issue(ALICE, ETH_TOKEN_ADDRESS_MGX, MILLION) + .issue(ALICE, NativeCurrencyId::get(), MILLION) + .issue(BOB, ETH_TOKEN_ADDRESS_MGX, MILLION) + .execute_with_default_mocks(|| { + let selected_sequencer_mock = + MockSequencerStakingProviderApi::is_active_sequencer_alias_context(); + selected_sequencer_mock.expect().return_const(true); + + forward_to_block::(10); + + for _ in 0..10 { + Rolldown::withdraw( + RuntimeOrigin::signed(ALICE), + consts::CHAIN, + ETH_RECIPIENT_ACCOUNT, + ETH_TOKEN_ADDRESS, + 1_000u128, + 0u128.into(), + ) + .unwrap(); + } + + Rolldown::create_batch(RuntimeOrigin::signed(BOB), consts::CHAIN, None).unwrap(); + + assert_err!( + Rolldown::create_batch(RuntimeOrigin::signed(BOB), consts::CHAIN, None,), + Error::::EmptyBatch + ); + }) +} + +#[test] +#[serial] +fn manual_batches_not_allowed_in_maintanance_mode() { + ExtBuilder::new() + .issue(ALICE, ETH_TOKEN_ADDRESS_MGX, MILLION) + .issue(ALICE, NativeCurrencyId::get(), MILLION) + .issue(BOB, ETH_TOKEN_ADDRESS_MGX, MILLION) + .execute_without_mocks([Mocks::MaintenanceMode], || { + let is_maintenance_mock = MockMaintenanceStatusProviderApi::is_maintenance_context(); + is_maintenance_mock.expect().return_const(false); + forward_to_block::(10); + + Rolldown::withdraw( + RuntimeOrigin::signed(ALICE), + consts::CHAIN, + ETH_RECIPIENT_ACCOUNT, + ETH_TOKEN_ADDRESS, + 1_000u128, + 0u128.into(), + ) + .unwrap(); + is_maintenance_mock.checkpoint(); + + let is_maintenance_mock = MockMaintenanceStatusProviderApi::is_maintenance_context(); + is_maintenance_mock.expect().return_const(true); + + assert_err!( + Rolldown::create_batch(RuntimeOrigin::signed(BOB), consts::CHAIN, None,), + Error::::BlockedByMaintenanceMode + ); + }) +} + +#[test] +#[serial] +fn automatic_batches_triggered_by_period_blocked_maintenance_mode() { + ExtBuilder::new() + .issue(ALICE, ETH_TOKEN_ADDRESS_MGX, MILLION) + .issue(ALICE, NativeCurrencyId::get(), MILLION) + .issue(BOB, ETH_TOKEN_ADDRESS_MGX, MILLION) + .execute_without_mocks([Mocks::MaintenanceMode], || { + let is_maintenance_mock = MockMaintenanceStatusProviderApi::is_maintenance_context(); + is_maintenance_mock.expect().return_const(false); + + forward_to_block::(10); + + Rolldown::withdraw( + RuntimeOrigin::signed(ALICE), + consts::CHAIN, + ETH_RECIPIENT_ACCOUNT, + ETH_TOKEN_ADDRESS, + 1_000u128, + 0u128.into(), + ) + .unwrap(); + is_maintenance_mock.checkpoint(); + + let is_maintenance_mock = MockMaintenanceStatusProviderApi::is_maintenance_context(); + is_maintenance_mock.expect().return_const(true); + forward_to_block::(2 * Rolldown::automatic_batch_period() as u64); + + assert_eq!(L2RequestsBatchLast::::get().get(&consts::CHAIN), None); + }) +} + +#[test] +#[serial] +fn automatic_batches_triggered_by_pending_requests_blocked_maintenance_mode() { + ExtBuilder::new() + .issue(ALICE, ETH_TOKEN_ADDRESS_MGX, MILLION) + .issue(ALICE, NativeCurrencyId::get(), MILLION) + .issue(BOB, ETH_TOKEN_ADDRESS_MGX, MILLION) + .execute_without_mocks([Mocks::MaintenanceMode], || { + let is_maintenance_mock = MockMaintenanceStatusProviderApi::is_maintenance_context(); + is_maintenance_mock.expect().return_const(false); + + forward_to_block::(10); + + for _ in 0..Rolldown::automatic_batch_size() { + Rolldown::withdraw( + RuntimeOrigin::signed(ALICE), + consts::CHAIN, + ETH_RECIPIENT_ACCOUNT, + ETH_TOKEN_ADDRESS, + 1_000u128, + 0u128.into(), + ) + .unwrap(); + } + is_maintenance_mock.checkpoint(); + + let is_maintenance_mock = MockMaintenanceStatusProviderApi::is_maintenance_context(); + is_maintenance_mock.expect().return_const(true); + forward_to_block::(11); + assert_eq!(L2RequestsBatchLast::::get().get(&consts::CHAIN), None); + }) +} + +#[test] +#[serial] +fn test_withdrawals_are_not_allowed_in_maintanance_mode() { + ExtBuilder::new() + .issue(ALICE, ETH_TOKEN_ADDRESS_MGX, MILLION) + .issue(BOB, ETH_TOKEN_ADDRESS_MGX, MILLION) + .build() + .execute_with(|| { + let is_maintenance_mock = MockMaintenanceStatusProviderApi::is_maintenance_context(); + is_maintenance_mock.expect().return_const(true); + + assert_err!( + Rolldown::withdraw( + RuntimeOrigin::signed(ALICE), + consts::CHAIN, + ETH_RECIPIENT_ACCOUNT, + ETH_TOKEN_ADDRESS, + 1_000u128, + 0u128.into(), + ), + Error::::BlockedByMaintenanceMode + ); + }) +} + +#[test] +#[serial] +fn test_cancels_are_not_allowed_in_maintanance_mode() { + ExtBuilder::new() + .issue(ALICE, ETH_TOKEN_ADDRESS_MGX, MILLION) + .issue(BOB, ETH_TOKEN_ADDRESS_MGX, MILLION) + .execute_without_mocks([Mocks::MaintenanceMode], || { + let is_maintenance_mock = MockMaintenanceStatusProviderApi::is_maintenance_context(); + is_maintenance_mock.expect().return_const(false); + forward_to_block::(10); + let deposit_update = L1UpdateBuilder::default() + .with_requests(vec![ + L1UpdateRequest::Deposit(Default::default()), + L1UpdateRequest::Deposit(Default::default()), + ]) + .build(); + Rolldown::update_l2_from_l1_unsafe(RuntimeOrigin::signed(ALICE), deposit_update) + .unwrap(); + is_maintenance_mock.checkpoint(); + + let is_maintenance_mock = MockMaintenanceStatusProviderApi::is_maintenance_context(); + is_maintenance_mock.expect().return_const(true); + assert_err!( + Rolldown::cancel_requests_from_l1( + RuntimeOrigin::signed(BOB), + consts::CHAIN, + 15u128.into(), + ), + Error::::BlockedByMaintenanceMode + ); + }) +} + +#[test] +#[serial] +fn test_updates_are_not_allowed_in_maintanance_mode() { + ExtBuilder::new() + .issue(ALICE, ETH_TOKEN_ADDRESS_MGX, MILLION) + .issue(BOB, ETH_TOKEN_ADDRESS_MGX, MILLION) + .execute_without_mocks([Mocks::MaintenanceMode], || { + let is_maintenance_mock = MockMaintenanceStatusProviderApi::is_maintenance_context(); + is_maintenance_mock.expect().return_const(true); + + let deposit_update = L1UpdateBuilder::default() + .with_requests(vec![ + L1UpdateRequest::Deposit(Default::default()), + L1UpdateRequest::Deposit(Default::default()), + ]) + .build(); + assert_err!( + Rolldown::update_l2_from_l1_unsafe(RuntimeOrigin::signed(ALICE), deposit_update), + Error::::BlockedByMaintenanceMode + ); + }) +} + +#[test] +#[serial] +fn test_sequencer_updates_are_ignored_and_removed_in_maintanance_mode() { + ExtBuilder::new() + .issue(ALICE, ETH_TOKEN_ADDRESS_MGX, MILLION) + .issue(BOB, ETH_TOKEN_ADDRESS_MGX, MILLION) + .execute_without_mocks([Mocks::MaintenanceMode], || { + let is_selected_sequencer_mock = + MockSequencerStakingProviderApi::is_selected_sequencer_context(); + is_selected_sequencer_mock.expect().return_const(true); + let is_maintenance_mock = MockMaintenanceStatusProviderApi::is_maintenance_context(); + is_maintenance_mock.expect().return_const(false); + forward_to_block::(10); + + let deposit_update = L1UpdateBuilder::default() + .with_requests(vec![L1UpdateRequest::Deposit(messages::Deposit { + requestId: Default::default(), + depositRecipient: DummyAddressConverter::convert_back(CHARLIE), + tokenAddress: ETH_TOKEN_ADDRESS, + amount: sp_core::U256::from(MILLION), + timeStamp: sp_core::U256::from(1), + ferryTip: sp_core::U256::from(0), + })]) + .build(); + + Rolldown::update_l2_from_l1_unsafe(RuntimeOrigin::signed(ALICE), deposit_update) + .unwrap(); + is_maintenance_mock.checkpoint(); + + let is_maintenance_mock = MockMaintenanceStatusProviderApi::is_maintenance_context(); + is_maintenance_mock.expect().return_const(true); + assert!(PendingSequencerUpdates::::contains_key(15u128, Chain::Ethereum)); + + forward_to_block::(15); + + is_maintenance_mock.checkpoint(); + let is_maintenance_mock = MockMaintenanceStatusProviderApi::is_maintenance_context(); + is_maintenance_mock.expect().return_const(true); + forward_to_next_block::(); + forward_to_next_block::(); + assert!(!PendingSequencerUpdates::::contains_key(15u128, Chain::Ethereum)); + assert_event_emitted!(Event::L1ReadIgnoredBecauseOfMaintenanceMode { + chain: consts::CHAIN, + hash: H256::from(hex!( + "6b5cabff8b0f12e9fac3708cad00edba4cdb2b3b8a3ab935073a303c53333c2b" + )), + }); + }) +} + +#[test] +#[serial] +fn test_reqeust_scheduled_for_execution_are_not_execute_in_the_same_block() { + ExtBuilder::new() + .issue(ALICE, ETH_TOKEN_ADDRESS_MGX, MILLION) + .issue(BOB, ETH_TOKEN_ADDRESS_MGX, MILLION) + .execute_with_default_mocks(|| { + forward_to_block::(10); + + let deposit_update = L1UpdateBuilder::default() + .with_requests(vec![L1UpdateRequest::Deposit(messages::Deposit::default())]) + .build(); + Rolldown::update_l2_from_l1_unsafe(RuntimeOrigin::signed(ALICE), deposit_update) + .unwrap(); + assert_eq!(LastProcessedRequestOnL2::::get(Chain::Ethereum), 0u128.into()); + + forward_to_block::(15); + assert_event_emitted!(Event::L1ReadScheduledForExecution { + chain: consts::CHAIN, + hash: H256::from(hex!( + "75207958ce929568193284a176e012a8cf5058dc19d73dafee61a419eb667398" + )), + }); + + forward_to_next_block::(); + assert_eq!(LastProcessedRequestOnL2::::get(Chain::Ethereum), 1u128.into()); + assert_event_emitted!(Event::RequestProcessedOnL2 { + chain: consts::CHAIN, + request_id: 1u128, + status: Ok(()) + }); + }) +} + +#[test] +#[serial] +fn test_sequencer_updates_that_went_though_dispute_period_are_not_executed_in_maintenance_mode() { + ExtBuilder::new() + .issue(ALICE, ETH_TOKEN_ADDRESS_MGX, MILLION) + .issue(BOB, ETH_TOKEN_ADDRESS_MGX, MILLION) + .execute_without_mocks([Mocks::MaintenanceMode], || { + let is_maintenance_mock = MockMaintenanceStatusProviderApi::is_maintenance_context(); + is_maintenance_mock.expect().return_const(false); + forward_to_block::(10); + + let deposit_update = L1UpdateBuilder::default() + .with_requests(vec![L1UpdateRequest::Deposit(messages::Deposit::default()); 11]) + .build(); + let update_hash = deposit_update.abi_encode_hash(); + Rolldown::update_l2_from_l1_unsafe(RuntimeOrigin::signed(ALICE), deposit_update) + .unwrap(); + assert_eq!(LastProcessedRequestOnL2::::get(Chain::Ethereum), 0u128.into()); + + forward_to_block::(15); + assert_eq!(LastProcessedRequestOnL2::::get(Chain::Ethereum), 10u128.into()); + assert_event_emitted!(Event::L1ReadScheduledForExecution { + chain: consts::CHAIN, + hash: update_hash, + }); + assert_eq!(LastProcessedRequestOnL2::::get(Chain::Ethereum), 10u128.into()); + + is_maintenance_mock.checkpoint(); + let is_maintenance_mock = MockMaintenanceStatusProviderApi::is_maintenance_context(); + is_maintenance_mock.expect().return_const(true); + forward_to_block::(20); + + is_maintenance_mock.checkpoint(); + let is_maintenance_mock = MockMaintenanceStatusProviderApi::is_maintenance_context(); + is_maintenance_mock.expect().return_const(false); + forward_to_block::(100); + assert_eq!(LastProcessedRequestOnL2::::get(Chain::Ethereum), 10u128.into()); + }) +} + +#[test] +#[serial] +fn test_sequencer_updates_that_went_though_dispute_period_are_not_scheduled_for_execution_in_maintanance_mode( +) { + ExtBuilder::new() + .issue(ALICE, ETH_TOKEN_ADDRESS_MGX, MILLION) + .issue(BOB, ETH_TOKEN_ADDRESS_MGX, MILLION) + .execute_without_mocks([Mocks::MaintenanceMode], || { + let is_maintenance_mock = MockMaintenanceStatusProviderApi::is_maintenance_context(); + is_maintenance_mock.expect().return_const(false); + forward_to_block::(10); + + let deposit_update = L1UpdateBuilder::default() + .with_requests(vec![L1UpdateRequest::Deposit(messages::Deposit::default())]) + .build(); + Rolldown::update_l2_from_l1_unsafe(RuntimeOrigin::signed(ALICE), deposit_update) + .unwrap(); + assert_eq!(LastProcessedRequestOnL2::::get(Chain::Ethereum), 0u128.into()); + forward_to_block::(14); + is_maintenance_mock.checkpoint(); + let is_maintenance_mock = MockMaintenanceStatusProviderApi::is_maintenance_context(); + is_maintenance_mock.expect().return_const(true); + + forward_to_block::(15); + assert_eq!(LastProcessedRequestOnL2::::get(Chain::Ethereum), 0u128.into()); + assert_event_emitted!(Event::L1ReadIgnoredBecauseOfMaintenanceMode { + chain: consts::CHAIN, + hash: H256::from(hex!( + "75207958ce929568193284a176e012a8cf5058dc19d73dafee61a419eb667398" + )), + }); + + forward_to_block::(50); + assert_eq!(LastProcessedRequestOnL2::::get(Chain::Ethereum), 0u128.into()); + }) +} + +#[test] +#[serial] +fn test_sequencer_can_submit_update_with_remaining_elements() { + ExtBuilder::new() + .issue(ALICE, ETH_TOKEN_ADDRESS_MGX, MILLION) + .issue(BOB, ETH_TOKEN_ADDRESS_MGX, MILLION) + .execute_without_mocks([Mocks::MaintenanceMode], || { + let is_maintenance_mock = MockMaintenanceStatusProviderApi::is_maintenance_context(); + is_maintenance_mock.expect().return_const(false); + forward_to_block::(10); + + let requests = vec![L1UpdateRequest::Deposit(messages::Deposit::default()); 11]; + let deposit_update = L1UpdateBuilder::default().with_requests(requests.clone()).build(); + Rolldown::update_l2_from_l1_unsafe( + RuntimeOrigin::signed(ALICE), + deposit_update.clone(), + ) + .unwrap(); + assert_eq!(LastProcessedRequestOnL2::::get(Chain::Ethereum), 0u128.into()); + + forward_to_block::(15); + assert_event_emitted!(Event::L1ReadScheduledForExecution { + chain: consts::CHAIN, + hash: deposit_update.abi_encode_hash() + }); + assert_eq!(LastProcessedRequestOnL2::::get(Chain::Ethereum), 10u128.into()); + + is_maintenance_mock.checkpoint(); + let is_maintenance_mock = MockMaintenanceStatusProviderApi::is_maintenance_context(); + is_maintenance_mock.expect().return_const(true); + + forward_to_next_block::(); + + is_maintenance_mock.checkpoint(); + let is_maintenance_mock = MockMaintenanceStatusProviderApi::is_maintenance_context(); + is_maintenance_mock.expect().return_const(false); + + forward_to_block::(20); + let deposit_update = L1UpdateBuilder::default() + .with_requests(requests.into_iter().skip(10).collect()) + .with_offset(11) + .build(); + Rolldown::update_l2_from_l1_unsafe(RuntimeOrigin::signed(ALICE), deposit_update) + .unwrap(); + assert_eq!(LastProcessedRequestOnL2::::get(Chain::Ethereum), 10u128.into()); + forward_to_block::(25); + assert_eq!(LastProcessedRequestOnL2::::get(Chain::Ethereum), 11u128.into()); + }) +} + +#[test] +#[serial] +fn test_sequencer_rights_are_returned_when_maintanance_mode_is_triggered_and_pending_requests_are_ignored( +) { + ExtBuilder::new() + .issue(ALICE, ETH_TOKEN_ADDRESS_MGX, MILLION) + .issue(BOB, ETH_TOKEN_ADDRESS_MGX, MILLION) + .execute_without_mocks([Mocks::MaintenanceMode], || { + let is_maintenance_mock = MockMaintenanceStatusProviderApi::is_maintenance_context(); + is_maintenance_mock.expect().return_const(false); + forward_to_block::(10); + + let deposit_update = L1UpdateBuilder::default() + .with_requests(vec![L1UpdateRequest::Deposit(messages::Deposit::default())]) + .build(); + Rolldown::update_l2_from_l1_unsafe(RuntimeOrigin::signed(ALICE), deposit_update) + .unwrap(); + assert_eq!(LastProcessedRequestOnL2::::get(Chain::Ethereum), 0u128.into()); + is_maintenance_mock.checkpoint(); + let is_maintenance_mock = MockMaintenanceStatusProviderApi::is_maintenance_context(); + is_maintenance_mock.expect().return_const(true); + + forward_to_block::(14); + assert_eq!( + SequencersRights::::get(consts::CHAIN).get(&ALICE).unwrap().read_rights, + 0 + ); + + forward_to_block::(15); + assert_eq!( + SequencersRights::::get(consts::CHAIN).get(&ALICE).unwrap().read_rights, + 1 + ); + + forward_to_block::(20); + assert_eq!(Rolldown::get_last_processed_request_on_l2(Chain::Ethereum), 0_u128.into()); + }) +} + +#[test] +#[serial] +fn test_sequencer_rights_are_returned_when_maintanance_mode_is_turned_off_before_dispute_period() { + ExtBuilder::new() + .issue(ALICE, ETH_TOKEN_ADDRESS_MGX, MILLION) + .issue(BOB, ETH_TOKEN_ADDRESS_MGX, MILLION) + .execute_without_mocks([Mocks::MaintenanceMode], || { + let is_maintenance_mock = MockMaintenanceStatusProviderApi::is_maintenance_context(); + is_maintenance_mock.expect().return_const(false); + forward_to_block::(10); + + let deposit_update = L1UpdateBuilder::default() + .with_requests(vec![L1UpdateRequest::Deposit(messages::Deposit::default())]) + .build(); + Rolldown::update_l2_from_l1_unsafe(RuntimeOrigin::signed(ALICE), deposit_update) + .unwrap(); + assert_eq!(LastProcessedRequestOnL2::::get(Chain::Ethereum), 0u128.into()); + is_maintenance_mock.checkpoint(); + let is_maintenance_mock = MockMaintenanceStatusProviderApi::is_maintenance_context(); + is_maintenance_mock.expect().return_const(true); + + forward_to_block::(14); + assert_eq!( + SequencersRights::::get(consts::CHAIN).get(&ALICE).unwrap().read_rights, + 0 + ); + + is_maintenance_mock.checkpoint(); + let is_maintenance_mock = MockMaintenanceStatusProviderApi::is_maintenance_context(); + is_maintenance_mock.expect().return_const(false); + + forward_to_block::(15); + assert_eq!( + SequencersRights::::get(consts::CHAIN).get(&ALICE).unwrap().read_rights, + 1 + ); + + forward_to_block::(20); + assert_eq!(Rolldown::get_last_processed_request_on_l2(Chain::Ethereum), 0_u128.into()); + }) +} + +#[test] +#[serial] +fn test_force_create_batch_can_only_be_called_as_sudo() { + ExtBuilder::new() + .issue(ALICE, ETH_TOKEN_ADDRESS_MGX, MILLION) + .execute_with_default_mocks(|| { + forward_to_block::(10); + assert_noop!( + Rolldown::force_create_batch( + RuntimeOrigin::signed(ALICE), + consts::CHAIN, + (10, 20), + ALICE + ), + BadOrigin + ); + }) +} + +#[test] +#[serial] +fn test_force_create_batch_fails_for_invalid_range() { + ExtBuilder::new() + .issue(ALICE, ETH_TOKEN_ADDRESS_MGX, MILLION) + .issue(ALICE, NativeCurrencyId::get(), MILLION) + .execute_with_default_mocks(|| { + forward_to_block::(10); + + assert_err!( + Rolldown::force_create_batch(RuntimeOrigin::root(), consts::CHAIN, (10, 20), ALICE), + Error::::InvalidRange + ); + + Rolldown::withdraw( + RuntimeOrigin::signed(ALICE), + consts::CHAIN, + ETH_RECIPIENT_ACCOUNT, + ETH_TOKEN_ADDRESS, + 1_000u128, + 0u128.into(), + ) + .unwrap(); + + assert_err!( + Rolldown::force_create_batch(RuntimeOrigin::root(), consts::CHAIN, (1, 10), ALICE), + Error::::InvalidRange + ); + + assert_err!( + Rolldown::force_create_batch(RuntimeOrigin::root(), consts::CHAIN, (0, 1), ALICE), + Error::::InvalidRange + ); + }) +} + +#[test] +#[serial] +fn test_force_create_batch_succeeds_for_valid_range() { + ExtBuilder::new() + .issue(ALICE, ETH_TOKEN_ADDRESS_MGX, MILLION) + .issue(ALICE, NativeCurrencyId::get(), MILLION) + .execute_with_default_mocks(|| { + forward_to_block::(10); + + Rolldown::withdraw( + RuntimeOrigin::signed(ALICE), + consts::CHAIN, + ETH_RECIPIENT_ACCOUNT, + ETH_TOKEN_ADDRESS, + 1_000u128, + 0u128.into(), + ) + .unwrap(); + + Rolldown::force_create_batch(RuntimeOrigin::root(), consts::CHAIN, (1, 1), ALICE) + .unwrap(); + + assert_event_emitted!(Event::TxBatchCreated { + chain: consts::CHAIN, + source: BatchSource::Manual, + assignee: ALICE, + batch_id: 1, + range: (1, 1), + }); + }) +} + +#[test] +#[serial] +fn test_deposit_ferry_with_wrong_hash_fails() { + ExtBuilder::new() + .issue(ALICE, ETH_TOKEN_ADDRESS_MGX, MILLION) + .execute_with_default_mocks(|| { + forward_to_block::(10); + + let deposit = messages::Deposit { + requestId: Default::default(), + depositRecipient: DummyAddressConverter::convert_back(CHARLIE), + tokenAddress: ETH_TOKEN_ADDRESS, + amount: sp_core::U256::from(MILLION), + timeStamp: sp_core::U256::from(1), + ferryTip: sp_core::U256::from(0), + }; + + assert_err!( + Rolldown::ferry_deposit( + RuntimeOrigin::signed(ALICE), + consts::CHAIN, + deposit.requestId, + deposit.depositRecipient, + deposit.tokenAddress, + deposit.amount.try_into().unwrap(), + deposit.timeStamp.try_into().unwrap(), + deposit.ferryTip.try_into().unwrap(), + H256::zero() + ), + Error::::FerryHashMismatch + ); + + forward_to_block::(14); + }) +} + +#[test] +#[serial] +fn test_deposit_ferry_without_tip() { + ExtBuilder::new() + .issue(ALICE, ETH_TOKEN_ADDRESS_MGX, MILLION) + .execute_with_default_mocks(|| { + forward_to_block::(10); + + let deposit = messages::Deposit { + requestId: RequestId::new(Origin::L1, 1u128), + depositRecipient: DummyAddressConverter::convert_back(CHARLIE), + tokenAddress: ETH_TOKEN_ADDRESS, + amount: sp_core::U256::from(MILLION), + timeStamp: sp_core::U256::from(1), + ferryTip: sp_core::U256::from(0), + }; + + let alice_balance_before = + TokensOf::::free_balance(ETH_TOKEN_ADDRESS_MGX, &ALICE); + let charlie_balance_before = + TokensOf::::free_balance(ETH_TOKEN_ADDRESS_MGX, &CHARLIE); + + let update = + L1UpdateBuilder::default().with_requests(vec![deposit.clone().into()]).build(); + + Rolldown::ferry_deposit( + RuntimeOrigin::signed(ALICE), + consts::CHAIN, + deposit.requestId, + deposit.depositRecipient, + deposit.tokenAddress, + deposit.amount.saturated_into::(), + deposit.timeStamp.saturated_into::(), + deposit.ferryTip.saturated_into::(), + deposit.abi_encode_hash(), + ) + .unwrap(); + + let alice_balance_after = TokensOf::::free_balance(ETH_TOKEN_ADDRESS_MGX, &ALICE); + let charlie_balance_after = + TokensOf::::free_balance(ETH_TOKEN_ADDRESS_MGX, &CHARLIE); + let ferried_amount = deposit.amount - deposit.ferryTip; + + assert_eq!( + alice_balance_before - alice_balance_after, + ferried_amount.try_into().unwrap() + ); + assert_eq!( + charlie_balance_after - charlie_balance_before, + ferried_amount.try_into().unwrap() + ); + + Rolldown::update_l2_from_l1_unsafe(RuntimeOrigin::signed(ALICE), update).unwrap(); + + forward_to_block::(16); + assert_eq!( + TokensOf::::free_balance(ETH_TOKEN_ADDRESS_MGX, &CHARLIE), + charlie_balance_after + ); + assert_eq!( + TokensOf::::free_balance(ETH_TOKEN_ADDRESS_MGX, &ALICE), + alice_balance_before + deposit.ferryTip.saturated_into::() + ); + }) +} + +#[test] +#[serial] +fn test_deposit_ferry_with_tip() { + ExtBuilder::new() + .issue(ALICE, ETH_TOKEN_ADDRESS_MGX, MILLION) + .execute_with_default_mocks(|| { + forward_to_block::(10); + + let deposit = messages::Deposit { + requestId: RequestId::new(Origin::L1, 1u128), + depositRecipient: DummyAddressConverter::convert_back(CHARLIE), + tokenAddress: ETH_TOKEN_ADDRESS, + amount: sp_core::U256::from(MILLION), + timeStamp: sp_core::U256::from(1), + ferryTip: sp_core::U256::from(10 * THOUSAND), + }; + + let alice_balance_before = + TokensOf::::free_balance(ETH_TOKEN_ADDRESS_MGX, &ALICE); + let charlie_balance_before = + TokensOf::::free_balance(ETH_TOKEN_ADDRESS_MGX, &CHARLIE); + + let update = + L1UpdateBuilder::default().with_requests(vec![deposit.clone().into()]).build(); + + Rolldown::ferry_deposit( + RuntimeOrigin::signed(ALICE), + consts::CHAIN, + deposit.requestId, + deposit.depositRecipient, + deposit.tokenAddress, + deposit.amount.saturated_into::(), + deposit.timeStamp.saturated_into::(), + deposit.ferryTip.saturated_into::(), + deposit.abi_encode_hash(), + ) + .unwrap(); + + let alice_balance_after = TokensOf::::free_balance(ETH_TOKEN_ADDRESS_MGX, &ALICE); + let charlie_balance_after = + TokensOf::::free_balance(ETH_TOKEN_ADDRESS_MGX, &CHARLIE); + let ferried_amount = deposit.amount - deposit.ferryTip; + + assert_eq!( + alice_balance_before - alice_balance_after, + ferried_amount.try_into().unwrap() + ); + assert_eq!( + charlie_balance_after - charlie_balance_before, + ferried_amount.try_into().unwrap() + ); + + Rolldown::update_l2_from_l1_unsafe(RuntimeOrigin::signed(ALICE), update).unwrap(); + + forward_to_block::(16); + assert_eq!( + TokensOf::::free_balance(ETH_TOKEN_ADDRESS_MGX, &CHARLIE), + charlie_balance_after + ); + assert_eq!( + TokensOf::::free_balance(ETH_TOKEN_ADDRESS_MGX, &ALICE), + alice_balance_before + deposit.ferryTip.saturated_into::() + ); + }) +} + +#[test] +#[serial] +fn test_ferry_deposit_that_fails() { + ExtBuilder::new() + .issue(ALICE, ETH_TOKEN_ADDRESS_MGX, u128::MAX) + .execute_with_default_mocks(|| { + forward_to_block::(10); + + let deposit = messages::Deposit { + requestId: RequestId::new(Origin::L1, 1u128), + depositRecipient: DummyAddressConverter::convert_back(CHARLIE), + tokenAddress: ETH_TOKEN_ADDRESS, + amount: sp_core::U256::from(u128::MAX), + timeStamp: sp_core::U256::from(1), + ferryTip: sp_core::U256::from(1u128), + }; + + let alice_balance_before = + TokensOf::::free_balance(ETH_TOKEN_ADDRESS_MGX, &ALICE); + let charlie_balance_before = + TokensOf::::free_balance(ETH_TOKEN_ADDRESS_MGX, &CHARLIE); + + let update = + L1UpdateBuilder::default().with_requests(vec![deposit.clone().into()]).build(); + + Rolldown::ferry_deposit( + RuntimeOrigin::signed(ALICE), + consts::CHAIN, + deposit.requestId, + deposit.depositRecipient, + deposit.tokenAddress, + deposit.amount.saturated_into::(), + deposit.timeStamp.saturated_into::(), + deposit.ferryTip.saturated_into::(), + deposit.abi_encode_hash(), + ) + .unwrap(); + + let alice_balance_after = TokensOf::::free_balance(ETH_TOKEN_ADDRESS_MGX, &ALICE); + let charlie_balance_after = + TokensOf::::free_balance(ETH_TOKEN_ADDRESS_MGX, &CHARLIE); + let ferried_amount = deposit.amount - deposit.ferryTip; + + assert_eq!( + alice_balance_before - alice_balance_after, + ferried_amount.try_into().unwrap() + ); + assert_eq!( + charlie_balance_after - charlie_balance_before, + ferried_amount.try_into().unwrap() + ); + + Rolldown::update_l2_from_l1_unsafe(RuntimeOrigin::signed(ALICE), update).unwrap(); + + forward_to_block::(16); + + assert_event_emitted!(Event::RequestProcessedOnL2 { + chain: messages::Chain::Ethereum, + request_id: 1u128, + status: Err(L1RequestProcessingError::MintError), + }); + + assert_err!( + Rolldown::refund_failed_deposit( + RuntimeOrigin::signed(CHARLIE), + consts::CHAIN, + deposit.requestId.id + ), + Error::::NotEligibleForRefund + ); + + Rolldown::refund_failed_deposit( + RuntimeOrigin::signed(ALICE), + consts::CHAIN, + deposit.requestId.id, + ) + .unwrap(); + + assert_event_emitted!(Event::DepositRefundCreated { + refunded_request_id: RequestId::new(Origin::L1, 1u128), + chain: Chain::Ethereum, + ferry: Some(ALICE), + }); + }) +} + +#[test] +#[serial] +fn test_reproduce_bug_with_sequencer_being_able_to_get_more_cancel_rights_than_he_should() { + ExtBuilder::new() + .issue(ETH_RECIPIENT_ACCOUNT_MGX, ETH_TOKEN_ADDRESS_MGX, MILLION) + .execute_with_default_mocks(|| { + // Arrange + let slash_sequencer_mock = MockSequencerStakingProviderApi::slash_sequencer_context(); + slash_sequencer_mock.expect().times(1).return_const(Ok(().into())); + + let honest_update = L1UpdateBuilder::default() + .with_requests(vec![L1UpdateRequest::Deposit(Default::default())]) + .build(); + forward_to_block::(10); + Rolldown::update_l2_from_l1_unsafe(RuntimeOrigin::signed(ALICE), honest_update.clone()) + .unwrap(); + + // accidently canceling honest update + forward_to_block::(11); + Rolldown::cancel_requests_from_l1(RuntimeOrigin::signed(BOB), consts::CHAIN, 15u128) + .unwrap(); + + forward_to_block::(15); + let cancel_resolution = L1UpdateBuilder::default() + .with_requests(vec![ + // L1UpdateRequest::Deposit(Default::default()), + L1UpdateRequest::CancelResolution(messages::CancelResolution { + requestId: Default::default(), + l2RequestId: 1u128, + cancelJustified: false, + timeStamp: sp_core::U256::from(1), + }), + L1UpdateRequest::Deposit(Default::default()), + ]) + .build(); + + Rolldown::handle_sequencer_deactivations(consts::CHAIN, vec![ALICE, BOB, CHARLIE]); + Rolldown::new_sequencer_active(consts::CHAIN, &BOB); + Rolldown::new_sequencer_active(consts::CHAIN, &CHARLIE); + + assert_eq!( + *SequencersRights::::get(consts::CHAIN).get(&BOB).unwrap(), + SequencerRights { read_rights: 1u128, cancel_rights: 0u128 } + ); + assert_eq!( + *SequencersRights::::get(consts::CHAIN).get(&CHARLIE).unwrap(), + SequencerRights { read_rights: 1u128, cancel_rights: 1u128 } + ); + + Rolldown::update_l2_from_l1_unsafe(RuntimeOrigin::signed(CHARLIE), cancel_resolution) + .unwrap(); + + forward_to_block::(21); + assert_eq!( + *SequencersRights::::get(consts::CHAIN).get(&BOB).unwrap(), + SequencerRights { read_rights: 1u128, cancel_rights: 1u128 } + ); + assert_eq!( + *SequencersRights::::get(consts::CHAIN).get(&CHARLIE).unwrap(), + SequencerRights { read_rights: 1u128, cancel_rights: 1u128 } + ); + }); +} + +#[test] +#[serial] +fn ferry_already_executed_deposit_fails() { + ExtBuilder::new() + .issue(ALICE, ETH_TOKEN_ADDRESS_MGX, MILLION) + .execute_with_default_mocks(|| { + forward_to_block::(10); + let deposit = messages::Deposit { + requestId: RequestId::new(Origin::L1, 1u128), + depositRecipient: DummyAddressConverter::convert_back(CHARLIE), + tokenAddress: ETH_TOKEN_ADDRESS, + amount: sp_core::U256::from(MILLION), + timeStamp: sp_core::U256::from(1), + ferryTip: sp_core::U256::from(0), + }; + let update = + L1UpdateBuilder::default().with_requests(vec![deposit.clone().into()]).build(); + + Rolldown::update_l2_from_l1_unsafe(RuntimeOrigin::signed(ALICE), update).unwrap(); + assert_eq!(LastProcessedRequestOnL2::::get(Chain::Ethereum), 0u128); + forward_to_block::(16); + assert_eq!(LastProcessedRequestOnL2::::get(Chain::Ethereum), 1u128); + + assert_err!( + Rolldown::ferry_deposit_unsafe( + RuntimeOrigin::signed(ALICE), + consts::CHAIN, + deposit.requestId, + deposit.depositRecipient, + deposit.tokenAddress, + deposit.amount.try_into().unwrap(), + deposit.timeStamp.try_into().unwrap(), + deposit.ferryTip.try_into().unwrap(), + ), + Error::::AlreadyExecuted + ); + }); +} + +#[test] +#[serial] +fn reject_update_for_unknown_chain_id() { + ExtBuilder::new() + .issue(ALICE, ETH_TOKEN_ADDRESS_MGX, 0u128) + .execute_with_default_mocks(|| { + forward_to_block::(10); + let update = L1UpdateBuilder::default() + .with_requests(vec![L1UpdateRequest::Deposit(messages::Deposit { + requestId: Default::default(), + depositRecipient: DummyAddressConverter::convert_back(CHARLIE), + tokenAddress: ETH_TOKEN_ADDRESS, + amount: sp_core::U256::from(MILLION), + timeStamp: sp_core::U256::from(1), + ferryTip: sp_core::U256::from(0), + })]) + .build_for_chain(Chain::Arbitrum); + assert_err!( + Rolldown::update_l2_from_l1_unsafe(RuntimeOrigin::signed(ALICE), update.clone()), + Error::::UninitializedChainId + ); + }); +} + +#[test] +#[serial] +fn set_dispute_period() { + ExtBuilder::new() + .issue(ALICE, ETH_TOKEN_ADDRESS_MGX, 0u128) + .execute_with_default_mocks(|| { + forward_to_block::(10); + + let dispute_period = Rolldown::get_dispute_period(Chain::Ethereum).unwrap(); + Rolldown::set_dispute_period( + RuntimeOrigin::root(), + Chain::Ethereum, + dispute_period + 1u128, + ) + .unwrap(); + + assert_event_emitted!(Event::DisputePeriodSet { + chain: messages::Chain::Ethereum, + dispute_period_length: dispute_period + 1u128 + }); + + Rolldown::set_dispute_period(RuntimeOrigin::root(), Chain::Arbitrum, 1234).unwrap(); + assert_event_emitted!(Event::DisputePeriodSet { + chain: messages::Chain::Arbitrum, + dispute_period_length: 1234 + }); + }); +} diff --git a/gasp-node/pallets/rolldown/src/weight_utils.rs b/gasp-node/pallets/rolldown/src/weight_utils.rs new file mode 100644 index 000000000..44ca7d71f --- /dev/null +++ b/gasp-node/pallets/rolldown/src/weight_utils.rs @@ -0,0 +1,30 @@ +use sp_runtime::traits::Saturating; + +/// accounts for cost of reading huge L1Update from runtime storage (rocksdb) +pub const fn get_read_scalling_factor(size: usize) -> u128 { + const BASE_READ_COST: u128 = 25; + let approximated_cost = match size { + 0..=50 => 25u128, + 51..=100 => 45u128, + 101..=500 => 210u128, + 501..=1000 => 400u128, + 1001..=5000 => 1800u128, + _ => 2800u128, + }; + approximated_cost.saturating_div(BASE_READ_COST).saturating_add(1u128) +} + +/// accounts for cost of writing huge L1Update from runtime storage (rocksdb) +pub const fn get_write_scalling_factor(size: usize) -> u128 { + const BASE_WRITE_COST: u128 = 100; + + let approximated_cost = match size { + 0..=50 => 25u128, + 51..=100 => 150u128, + 101..=500 => 700u128, + 501..=1000 => 1050u128, + 1001..=5000 => 5000u128, + _ => 9000u128, + }; + approximated_cost.saturating_div(BASE_WRITE_COST).saturating_add(1u128) +} diff --git a/gasp-node/pallets/rolldown/src/weights.rs b/gasp-node/pallets/rolldown/src/weights.rs new file mode 100644 index 000000000..6bbf88b7b --- /dev/null +++ b/gasp-node/pallets/rolldown/src/weights.rs @@ -0,0 +1,337 @@ +// This file is part of Mangata. + +// Copyright (C) 2020-2022 Mangata Foundation. +// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 + +// This program 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. + +// This program 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 this program. If not, see . + +//! Autogenerated weights for pallet_rolldown +//! +//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 32.0.0 +//! DATE: 2024-10-29, STEPS: `2`, REPEAT: 2, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! EXECUTION: , WASM-EXECUTION: Compiled, CHAIN: Some("rollup-local"), DB CACHE: 1024 + +// Executed Command: +// /home/striker/work/mangata-ws/mangata-node/scripts/..//target/release/rollup-node +// benchmark +// pallet +// --chain +// rollup-local +// --execution +// wasm +// --wasm-execution +// compiled +// --pallet +// pallet_rolldown +// --extrinsic +// * +// --steps +// 2 +// --repeat +// 2 +// --output +// ./benchmarks/pallet_rolldown_weights.rs +// --template +// ./templates/module-weight-template.hbs + +#![cfg_attr(rustfmt, rustfmt_skip)] +#![allow(unused_parens)] +#![allow(unused_imports)] +#![allow(clippy::unnecessary_cast)] + +use frame_support::{traits::Get, weights::{Weight, constants::RocksDbWeight}}; +use sp_std::marker::PhantomData; + +/// Weight functions needed for pallet_rolldown. +pub trait WeightInfo { + fn set_manual_batch_extra_fee() -> Weight; + fn create_batch() -> Weight; + fn force_create_batch() -> Weight; + fn update_l2_from_l1(x: u32, ) -> Weight; + fn update_l2_from_l1_unsafe(x: u32, ) -> Weight; + fn force_update_l2_from_l1(x: u32, ) -> Weight; + fn cancel_requests_from_l1() -> Weight; + fn force_cancel_requests_from_l1() -> Weight; + fn withdraw() -> Weight; + fn refund_failed_deposit() -> Weight; + fn ferry_deposit() -> Weight; + fn ferry_deposit_unsafe() -> Weight; + fn process_deposit() -> Weight; + fn process_cancel_resolution() -> Weight; + fn load_next_update_from_execution_queue() -> Weight{ + (Weight::from_parts(22_558_000, 0)) + .saturating_add(RocksDbWeight::get().writes(1 as u64)) + } + fn schedule_request_for_execution_if_dispute_period_has_passsed() -> Weight{ + (Weight::from_parts(22_558_000, 0)) + .saturating_add(RocksDbWeight::get().writes(1 as u64)) + } + fn maybe_create_batch() -> Weight{ + (Weight::from_parts(22_558_000, 0)) + .saturating_add(RocksDbWeight::get().writes(1 as u64)) + } + fn execute_requests_from_execute_queue() -> Weight{ + (Weight::from_parts(22_558_000, 0)) + .saturating_add(RocksDbWeight::get().writes(1 as u64)) + } +} + +// For backwards compatibility and tests +impl WeightInfo for () { + // Storage: `Rolldown::ManualBatchExtraFee` (r:0 w:1) + // Proof: `Rolldown::ManualBatchExtraFee` (`max_values`: Some(1), `max_size`: Some(16), added: 511, mode: `MaxEncodedLen`) + fn set_manual_batch_extra_fee() -> Weight { + (Weight::from_parts(22_558_000, 0)) + .saturating_add(RocksDbWeight::get().writes(1 as u64)) + } + // Storage: `Maintenance::MaintenanceStatus` (r:1 w:0) + // Proof: `Maintenance::MaintenanceStatus` (`max_values`: Some(1), `max_size`: Some(2), added: 497, mode: `MaxEncodedLen`) + // Storage: `SequencerStaking::AliasAccount` (r:1 w:0) + // Proof: `SequencerStaking::AliasAccount` (`max_values`: None, `max_size`: Some(57), added: 2532, mode: `MaxEncodedLen`) + // Storage: `Rolldown::ManualBatchExtraFee` (r:1 w:0) + // Proof: `Rolldown::ManualBatchExtraFee` (`max_values`: Some(1), `max_size`: Some(16), added: 511, mode: `MaxEncodedLen`) + // Storage: `Tokens::Accounts` (r:2 w:2) + // Proof: `Tokens::Accounts` (`max_values`: None, `max_size`: Some(96), added: 2571, mode: `MaxEncodedLen`) + // Storage: `Rolldown::L2RequestsBatchLast` (r:1 w:1) + // Proof: `Rolldown::L2RequestsBatchLast` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + // Storage: `Rolldown::L2OriginRequestId` (r:1 w:0) + // Proof: `Rolldown::L2OriginRequestId` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + // Storage: `Rolldown::L2Requests` (r:1 w:0) + // Proof: `Rolldown::L2Requests` (`max_values`: None, `max_size`: None, mode: `Measured`) + // Storage: `Rolldown::L2RequestsBatch` (r:0 w:1) + // Proof: `Rolldown::L2RequestsBatch` (`max_values`: None, `max_size`: Some(89), added: 2564, mode: `MaxEncodedLen`) + fn create_batch() -> Weight { + (Weight::from_parts(58_178_000, 0)) + .saturating_add(RocksDbWeight::get().reads(8 as u64)) + .saturating_add(RocksDbWeight::get().writes(4 as u64)) + } + // Storage: `Maintenance::MaintenanceStatus` (r:1 w:0) + // Proof: `Maintenance::MaintenanceStatus` (`max_values`: Some(1), `max_size`: Some(2), added: 497, mode: `MaxEncodedLen`) + // Storage: `Rolldown::L2Requests` (r:1 w:0) + // Proof: `Rolldown::L2Requests` (`max_values`: None, `max_size`: None, mode: `Measured`) + // Storage: `Rolldown::L2RequestsBatchLast` (r:1 w:1) + // Proof: `Rolldown::L2RequestsBatchLast` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + // Storage: `Rolldown::L2RequestsBatch` (r:0 w:1) + // Proof: `Rolldown::L2RequestsBatch` (`max_values`: None, `max_size`: Some(89), added: 2564, mode: `MaxEncodedLen`) + fn force_create_batch() -> Weight { + (Weight::from_parts(28_495_000, 0)) + .saturating_add(RocksDbWeight::get().reads(3 as u64)) + .saturating_add(RocksDbWeight::get().writes(2 as u64)) + } + // Storage: `Maintenance::MaintenanceStatus` (r:1 w:0) + // Proof: `Maintenance::MaintenanceStatus` (`max_values`: Some(1), `max_size`: Some(2), added: 497, mode: `MaxEncodedLen`) + // Storage: `SequencerStaking::SelectedSequencer` (r:1 w:0) + // Proof: `SequencerStaking::SelectedSequencer` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + // Storage: `Rolldown::LastProcessedRequestOnL2` (r:1 w:0) + // Proof: `Rolldown::LastProcessedRequestOnL2` (`max_values`: None, `max_size`: Some(33), added: 2508, mode: `MaxEncodedLen`) + // Storage: `Rolldown::SequencersRights` (r:1 w:1) + // Proof: `Rolldown::SequencersRights` (`max_values`: None, `max_size`: None, mode: `Measured`) + // Storage: `Rolldown::PendingSequencerUpdates` (r:1 w:1) + // Proof: `Rolldown::PendingSequencerUpdates` (`max_values`: None, `max_size`: None, mode: `Measured`) + // Storage: `SequencerStaking::CurrentRound` (r:1 w:0) + // Proof: `SequencerStaking::CurrentRound` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) + // Storage: `SequencerStaking::AwardedPts` (r:1 w:1) + // Proof: `SequencerStaking::AwardedPts` (`max_values`: None, `max_size`: Some(44), added: 2519, mode: `MaxEncodedLen`) + // Storage: `SequencerStaking::Points` (r:1 w:1) + // Proof: `SequencerStaking::Points` (`max_values`: None, `max_size`: Some(16), added: 2491, mode: `MaxEncodedLen`) + // Storage: `Rolldown::LastUpdateBySequencer` (r:0 w:1) + // Proof: `Rolldown::LastUpdateBySequencer` (`max_values`: None, `max_size`: Some(53), added: 2528, mode: `MaxEncodedLen`) + fn update_l2_from_l1(x: u32, ) -> Weight { + (Weight::from_parts(53_903_308, 0)) + // Standard Error: 63_247 + .saturating_add((Weight::from_parts(1_544_095, 0)).saturating_mul(x as u64)) + .saturating_add(RocksDbWeight::get().reads(8 as u64)) + .saturating_add(RocksDbWeight::get().writes(5 as u64)) + } + // Storage: `Maintenance::MaintenanceStatus` (r:1 w:0) + // Proof: `Maintenance::MaintenanceStatus` (`max_values`: Some(1), `max_size`: Some(2), added: 497, mode: `MaxEncodedLen`) + // Storage: `SequencerStaking::SelectedSequencer` (r:1 w:0) + // Proof: `SequencerStaking::SelectedSequencer` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + // Storage: `Rolldown::LastProcessedRequestOnL2` (r:1 w:0) + // Proof: `Rolldown::LastProcessedRequestOnL2` (`max_values`: None, `max_size`: Some(33), added: 2508, mode: `MaxEncodedLen`) + // Storage: `Rolldown::SequencersRights` (r:1 w:1) + // Proof: `Rolldown::SequencersRights` (`max_values`: None, `max_size`: None, mode: `Measured`) + // Storage: `Rolldown::PendingSequencerUpdates` (r:1 w:1) + // Proof: `Rolldown::PendingSequencerUpdates` (`max_values`: None, `max_size`: None, mode: `Measured`) + // Storage: `SequencerStaking::CurrentRound` (r:1 w:0) + // Proof: `SequencerStaking::CurrentRound` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) + // Storage: `SequencerStaking::AwardedPts` (r:1 w:1) + // Proof: `SequencerStaking::AwardedPts` (`max_values`: None, `max_size`: Some(44), added: 2519, mode: `MaxEncodedLen`) + // Storage: `SequencerStaking::Points` (r:1 w:1) + // Proof: `SequencerStaking::Points` (`max_values`: None, `max_size`: Some(16), added: 2491, mode: `MaxEncodedLen`) + // Storage: `Rolldown::LastUpdateBySequencer` (r:0 w:1) + // Proof: `Rolldown::LastUpdateBySequencer` (`max_values`: None, `max_size`: Some(53), added: 2528, mode: `MaxEncodedLen`) + fn update_l2_from_l1_unsafe(x: u32, ) -> Weight { + (Weight::from_parts(53_063_762, 0)) + // Standard Error: 117_584 + .saturating_add((Weight::from_parts(1_073_368, 0)).saturating_mul(x as u64)) + .saturating_add(RocksDbWeight::get().reads(8 as u64)) + .saturating_add(RocksDbWeight::get().writes(5 as u64)) + } + // Storage: `Maintenance::MaintenanceStatus` (r:1 w:0) + // Proof: `Maintenance::MaintenanceStatus` (`max_values`: Some(1), `max_size`: Some(2), added: 497, mode: `MaxEncodedLen`) + // Storage: `Rolldown::LastProcessedRequestOnL2` (r:1 w:0) + // Proof: `Rolldown::LastProcessedRequestOnL2` (`max_values`: None, `max_size`: Some(33), added: 2508, mode: `MaxEncodedLen`) + // Storage: `Rolldown::MaxAcceptedRequestIdOnl2` (r:1 w:1) + // Proof: `Rolldown::MaxAcceptedRequestIdOnl2` (`max_values`: None, `max_size`: Some(33), added: 2508, mode: `MaxEncodedLen`) + // Storage: `Rolldown::LastScheduledUpdateIdInExecutionQueue` (r:1 w:1) + // Proof: `Rolldown::LastScheduledUpdateIdInExecutionQueue` (`max_values`: Some(1), `max_size`: Some(16), added: 511, mode: `MaxEncodedLen`) + // Storage: `Rolldown::UpdatesExecutionQueue` (r:0 w:1) + // Proof: `Rolldown::UpdatesExecutionQueue` (`max_values`: None, `max_size`: None, mode: `Measured`) + fn force_update_l2_from_l1(x: u32, ) -> Weight { + (Weight::from_parts(20_052_424, 0)) + // Standard Error: 16_144 + .saturating_add((Weight::from_parts(362_787, 0)).saturating_mul(x as u64)) + .saturating_add(RocksDbWeight::get().reads(4 as u64)) + .saturating_add(RocksDbWeight::get().writes(3 as u64)) + } + // Storage: `Maintenance::MaintenanceStatus` (r:1 w:0) + // Proof: `Maintenance::MaintenanceStatus` (`max_values`: Some(1), `max_size`: Some(2), added: 497, mode: `MaxEncodedLen`) + // Storage: `Rolldown::SequencersRights` (r:1 w:1) + // Proof: `Rolldown::SequencersRights` (`max_values`: None, `max_size`: None, mode: `Measured`) + // Storage: `Rolldown::PendingSequencerUpdates` (r:1 w:1) + // Proof: `Rolldown::PendingSequencerUpdates` (`max_values`: None, `max_size`: None, mode: `Measured`) + // Storage: `Rolldown::L2OriginRequestId` (r:1 w:1) + // Proof: `Rolldown::L2OriginRequestId` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + // Storage: `Rolldown::AwaitingCancelResolution` (r:1 w:1) + // Proof: `Rolldown::AwaitingCancelResolution` (`max_values`: None, `max_size`: None, mode: `Measured`) + // Storage: `Rolldown::L2Requests` (r:0 w:1) + // Proof: `Rolldown::L2Requests` (`max_values`: None, `max_size`: None, mode: `Measured`) + fn cancel_requests_from_l1() -> Weight { + (Weight::from_parts(39_181_000, 0)) + .saturating_add(RocksDbWeight::get().reads(5 as u64)) + .saturating_add(RocksDbWeight::get().writes(5 as u64)) + } + // Storage: `Maintenance::MaintenanceStatus` (r:1 w:0) + // Proof: `Maintenance::MaintenanceStatus` (`max_values`: Some(1), `max_size`: Some(2), added: 497, mode: `MaxEncodedLen`) + // Storage: `Rolldown::PendingSequencerUpdates` (r:1 w:1) + // Proof: `Rolldown::PendingSequencerUpdates` (`max_values`: None, `max_size`: None, mode: `Measured`) + // Storage: `SequencerStaking::ActiveSequencers` (r:1 w:0) + // Proof: `SequencerStaking::ActiveSequencers` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + // Storage: `Rolldown::SequencersRights` (r:1 w:1) + // Proof: `Rolldown::SequencersRights` (`max_values`: None, `max_size`: None, mode: `Measured`) + fn force_cancel_requests_from_l1() -> Weight { + (Weight::from_parts(26_120_000, 0)) + .saturating_add(RocksDbWeight::get().reads(4 as u64)) + .saturating_add(RocksDbWeight::get().writes(2 as u64)) + } + // Storage: `Maintenance::MaintenanceStatus` (r:1 w:0) + // Proof: `Maintenance::MaintenanceStatus` (`max_values`: Some(1), `max_size`: Some(2), added: 497, mode: `MaxEncodedLen`) + // Storage: `AssetRegistry::L1AssetToId` (r:1 w:0) + // Proof: `AssetRegistry::L1AssetToId` (`max_values`: None, `max_size`: Some(41), added: 2516, mode: `MaxEncodedLen`) + // Storage: `Tokens::Accounts` (r:3 w:3) + // Proof: `Tokens::Accounts` (`max_values`: None, `max_size`: Some(96), added: 2571, mode: `MaxEncodedLen`) + // Storage: `System::Account` (r:1 w:1) + // Proof: `System::Account` (`max_values`: None, `max_size`: Some(52), added: 2527, mode: `MaxEncodedLen`) + // Storage: `Tokens::TotalIssuance` (r:1 w:1) + // Proof: `Tokens::TotalIssuance` (`max_values`: None, `max_size`: Some(28), added: 2503, mode: `MaxEncodedLen`) + // Storage: `Rolldown::L2OriginRequestId` (r:1 w:1) + // Proof: `Rolldown::L2OriginRequestId` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + // Storage: `Rolldown::TotalNumberOfWithdrawals` (r:1 w:1) + // Proof: `Rolldown::TotalNumberOfWithdrawals` (`max_values`: Some(1), `max_size`: Some(16), added: 511, mode: `MaxEncodedLen`) + // Storage: `Rolldown::L2Requests` (r:0 w:1) + // Proof: `Rolldown::L2Requests` (`max_values`: None, `max_size`: None, mode: `Measured`) + fn withdraw() -> Weight { + (Weight::from_parts(75_988_000, 0)) + .saturating_add(RocksDbWeight::get().reads(9 as u64)) + .saturating_add(RocksDbWeight::get().writes(8 as u64)) + } + // Storage: `Rolldown::FailedL1Deposits` (r:1 w:1) + // Proof: `Rolldown::FailedL1Deposits` (`max_values`: None, `max_size`: Some(85), added: 2560, mode: `MaxEncodedLen`) + // Storage: `Rolldown::FerriedDeposits` (r:1 w:0) + // Proof: `Rolldown::FerriedDeposits` (`max_values`: None, `max_size`: Some(69), added: 2544, mode: `MaxEncodedLen`) + // Storage: `Rolldown::L2OriginRequestId` (r:1 w:1) + // Proof: `Rolldown::L2OriginRequestId` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + // Storage: `Rolldown::L2Requests` (r:0 w:1) + // Proof: `Rolldown::L2Requests` (`max_values`: None, `max_size`: None, mode: `Measured`) + fn refund_failed_deposit() -> Weight { + (Weight::from_parts(27_309_000, 0)) + .saturating_add(RocksDbWeight::get().reads(3 as u64)) + .saturating_add(RocksDbWeight::get().writes(3 as u64)) + } + // Storage: `Rolldown::LastProcessedRequestOnL2` (r:1 w:0) + // Proof: `Rolldown::LastProcessedRequestOnL2` (`max_values`: None, `max_size`: Some(33), added: 2508, mode: `MaxEncodedLen`) + // Storage: `AssetRegistry::L1AssetToId` (r:1 w:0) + // Proof: `AssetRegistry::L1AssetToId` (`max_values`: None, `max_size`: Some(41), added: 2516, mode: `MaxEncodedLen`) + // Storage: `Tokens::Accounts` (r:2 w:2) + // Proof: `Tokens::Accounts` (`max_values`: None, `max_size`: Some(96), added: 2571, mode: `MaxEncodedLen`) + // Storage: `System::Account` (r:1 w:1) + // Proof: `System::Account` (`max_values`: None, `max_size`: Some(52), added: 2527, mode: `MaxEncodedLen`) + // Storage: `Rolldown::FerriedDeposits` (r:0 w:1) + // Proof: `Rolldown::FerriedDeposits` (`max_values`: None, `max_size`: Some(69), added: 2544, mode: `MaxEncodedLen`) + fn ferry_deposit() -> Weight { + (Weight::from_parts(56_991_000, 0)) + .saturating_add(RocksDbWeight::get().reads(5 as u64)) + .saturating_add(RocksDbWeight::get().writes(4 as u64)) + } + // Storage: `Rolldown::LastProcessedRequestOnL2` (r:1 w:0) + // Proof: `Rolldown::LastProcessedRequestOnL2` (`max_values`: None, `max_size`: Some(33), added: 2508, mode: `MaxEncodedLen`) + // Storage: `AssetRegistry::L1AssetToId` (r:1 w:0) + // Proof: `AssetRegistry::L1AssetToId` (`max_values`: None, `max_size`: Some(41), added: 2516, mode: `MaxEncodedLen`) + // Storage: `Tokens::Accounts` (r:2 w:2) + // Proof: `Tokens::Accounts` (`max_values`: None, `max_size`: Some(96), added: 2571, mode: `MaxEncodedLen`) + // Storage: `System::Account` (r:1 w:1) + // Proof: `System::Account` (`max_values`: None, `max_size`: Some(52), added: 2527, mode: `MaxEncodedLen`) + // Storage: `Rolldown::FerriedDeposits` (r:0 w:1) + // Proof: `Rolldown::FerriedDeposits` (`max_values`: None, `max_size`: Some(69), added: 2544, mode: `MaxEncodedLen`) + fn ferry_deposit_unsafe() -> Weight { + (Weight::from_parts(62_927_000, 0)) + .saturating_add(RocksDbWeight::get().reads(5 as u64)) + .saturating_add(RocksDbWeight::get().writes(4 as u64)) + } + // Storage: `AssetRegistry::L1AssetToId` (r:1 w:0) + // Proof: `AssetRegistry::L1AssetToId` (`max_values`: None, `max_size`: Some(41), added: 2516, mode: `MaxEncodedLen`) + // Storage: `Rolldown::FerriedDeposits` (r:1 w:0) + // Proof: `Rolldown::FerriedDeposits` (`max_values`: None, `max_size`: Some(69), added: 2544, mode: `MaxEncodedLen`) + // Storage: `Tokens::NextCurrencyId` (r:1 w:0) + // Proof: `Tokens::NextCurrencyId` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) + // Storage: `Tokens::Accounts` (r:1 w:1) + // Proof: `Tokens::Accounts` (`max_values`: None, `max_size`: Some(96), added: 2571, mode: `MaxEncodedLen`) + // Storage: `Tokens::TotalIssuance` (r:1 w:1) + // Proof: `Tokens::TotalIssuance` (`max_values`: None, `max_size`: Some(28), added: 2503, mode: `MaxEncodedLen`) + // Storage: `System::Account` (r:1 w:1) + // Proof: `System::Account` (`max_values`: None, `max_size`: Some(52), added: 2527, mode: `MaxEncodedLen`) + fn process_deposit() -> Weight { + (Weight::from_parts(48_680_000, 0)) + .saturating_add(RocksDbWeight::get().reads(6 as u64)) + .saturating_add(RocksDbWeight::get().writes(3 as u64)) + } + // Storage: `Rolldown::L2Requests` (r:1 w:0) + // Proof: `Rolldown::L2Requests` (`max_values`: None, `max_size`: None, mode: `Measured`) + // Storage: `SequencerStaking::ActiveSequencers` (r:1 w:1) + // Proof: `SequencerStaking::ActiveSequencers` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + // Storage: `Rolldown::SequencersRights` (r:1 w:1) + // Proof: `Rolldown::SequencersRights` (`max_values`: None, `max_size`: None, mode: `Measured`) + // Storage: `Rolldown::AwaitingCancelResolution` (r:1 w:1) + // Proof: `Rolldown::AwaitingCancelResolution` (`max_values`: None, `max_size`: None, mode: `Measured`) + // Storage: `SequencerStaking::SequencerStake` (r:1 w:1) + // Proof: `SequencerStaking::SequencerStake` (`max_values`: None, `max_size`: Some(53), added: 2528, mode: `MaxEncodedLen`) + // Storage: `SequencerStaking::SlashFineAmount` (r:1 w:0) + // Proof: `SequencerStaking::SlashFineAmount` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + // Storage: `Tokens::Accounts` (r:2 w:2) + // Proof: `Tokens::Accounts` (`max_values`: None, `max_size`: Some(96), added: 2571, mode: `MaxEncodedLen`) + // Storage: `Tokens::TotalIssuance` (r:1 w:1) + // Proof: `Tokens::TotalIssuance` (`max_values`: None, `max_size`: Some(28), added: 2503, mode: `MaxEncodedLen`) + // Storage: `SequencerStaking::MinimalStakeAmount` (r:1 w:0) + // Proof: `SequencerStaking::MinimalStakeAmount` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + // Storage: `SequencerStaking::NextSequencerIndex` (r:1 w:1) + // Proof: `SequencerStaking::NextSequencerIndex` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + // Storage: `SequencerStaking::SelectedSequencer` (r:1 w:1) + // Proof: `SequencerStaking::SelectedSequencer` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + fn process_cancel_resolution() -> Weight { + (Weight::from_parts(97_360_000, 0)) + .saturating_add(RocksDbWeight::get().reads(12 as u64)) + .saturating_add(RocksDbWeight::get().writes(9 as u64)) + } +} diff --git a/gasp-node/pallets/sequencer-staking/Cargo.toml b/gasp-node/pallets/sequencer-staking/Cargo.toml new file mode 100644 index 000000000..3d669d2bd --- /dev/null +++ b/gasp-node/pallets/sequencer-staking/Cargo.toml @@ -0,0 +1,62 @@ +[package] +authors = ["Mangata team"] +edition = "2018" +name = "pallet-sequencer-staking" +version = "0.1.0" + +[package.metadata.docs.rs] +targets = ["x86_64-unknown-linux-gnu"] + +[dependencies] +codec = { workspace = true, default-features = false } +hex = { workspace = true, default-features = false } +hex-literal = { workspace = true, default-features = false } +log = { workspace = true, default-features = false } +scale-info = { workspace = true, default-features = false, features = ["derive"] } + +frame-benchmarking = { workspace = true, default-features = false } +frame-support = { workspace = true, default-features = false } +frame-system = { workspace = true, default-features = false } +mangata-support = { workspace = true, default-features = false } +mangata-types = { workspace = true, default-features = false } +sp-core = { workspace = true, default-features = false } +sp-runtime = { workspace = true, default-features = false } +sp-std = { workspace = true, default-features = false } + +orml-tokens = { workspace = true, default-features = false } + +[dev-dependencies] +env_logger.workspace = true +lazy_static.workspace = true +serial_test.workspace = true +mockall.workspace = true + +sp-io = { workspace = true, default-features = false } +orml-traits = { workspace = true, default-features = false } + +pallet-issuance = { path = "../issuance", default-features = false } + +[features] +default = ["std"] +enable-trading = [] +std = [ + "codec/std", + "frame-benchmarking/std", + "frame-support/std", + "frame-system/std", + "hex/std", + "mangata-support/std", + "mangata-types/std", + "orml-tokens/std", + "sp-core/std", + "sp-runtime/std", + "sp-std/std", + "log/std", + "scale-info/std", +] + +runtime-benchmarks = [ + "frame-benchmarking/runtime-benchmarks", +] + +try-runtime = ["frame-support/try-runtime", "frame-system/try-runtime", "orml-tokens/try-runtime", "sp-runtime/try-runtime"] diff --git a/gasp-node/pallets/sequencer-staking/src/lib.rs b/gasp-node/pallets/sequencer-staking/src/lib.rs new file mode 100644 index 000000000..d4bda257b --- /dev/null +++ b/gasp-node/pallets/sequencer-staking/src/lib.rs @@ -0,0 +1,869 @@ +#![allow(non_camel_case_types)] +#![cfg_attr(not(feature = "std"), no_std)] + +use frame_support::{ + ensure, + pallet_prelude::*, + traits::{Currency, ExistenceRequirement, Get, ReservableCurrency, StorageVersion}, + transactional, +}; +use frame_system::{ensure_signed, pallet_prelude::*}; +use sp_std::collections::btree_map::BTreeMap; + +use sp_std::{collections::btree_set::BTreeSet, convert::TryInto, prelude::*}; + +pub use mangata_support::traits::{ + ComputeIssuance, GetIssuance, RolldownProviderTrait, SequencerStakingProviderTrait, + SequencerStakingRewardsTrait, +}; +use sp_runtime::{ + traits::{CheckedAdd, One, Zero}, + Saturating, +}; +pub use sp_runtime::{BoundedBTreeMap, BoundedVec, Perbill}; + +type AccountIdOf = ::AccountId; +type SequencerIndex = u32; +type RoundIndex = u32; +type RewardPoint = u32; +type RoundRefCount = RoundIndex; + +pub type BalanceOf = + <::Currency as Currency<::AccountId>>::Balance; +pub type ChainIdOf = ::ChainId; + +pub(crate) const LOG_TARGET: &'static str = "sequencer-staking"; + +// syntactic sugar for logging. +#[macro_export] +macro_rules! log { + ($level:tt, $patter:expr $(, $values:expr)* $(,)?) => { + log::$level!( + target: crate::LOG_TARGET, + concat!("[{:?}] 💸 ", $patter), >::block_number() $(, $values)* + ) + }; +} + +#[cfg(test)] +mod tests; + +#[cfg(test)] +mod mock; + +pub use pallet::*; + +#[derive(Eq, PartialEq, Encode, Decode, TypeInfo, Debug, Clone)] +pub enum PayoutRounds { + All, + Partial(Vec), +} + +#[derive(Eq, PartialEq, RuntimeDebug, Clone, Encode, Decode, MaxEncodedLen, TypeInfo)] +pub enum StakeAction { + StakeOnly, + StakeAndJoinActiveSet, +} + +#[frame_support::pallet] +pub mod pallet { + + use super::*; + + const STORAGE_VERSION: StorageVersion = StorageVersion::new(0); + + #[pallet::pallet] + #[pallet::storage_version(STORAGE_VERSION)] + pub struct Pallet(PhantomData); + + #[pallet::genesis_config] + pub struct GenesisConfig { + pub minimal_stake_amount: BalanceOf, + pub slash_fine_amount: BalanceOf, + pub sequencers_stake: Vec<(T::AccountId, T::ChainId, BalanceOf)>, + } + + impl Default for GenesisConfig { + fn default() -> Self { + GenesisConfig { + minimal_stake_amount: Default::default(), + slash_fine_amount: Default::default(), + sequencers_stake: vec![], + } + } + } + + #[pallet::genesis_build] + impl BuildGenesisConfig for GenesisConfig { + // Since this pallet is to be configured above session in construct_runtime! + // We can't check that these sequencers are eligible + // We can have that check in initialize_genesis_eligible_sequencers + // but for now it is not implemented. This is fine and doesn't really + // break anything. However, if these sequencer join the eligible set and + // then leave then ofcourse they will be removed from the active set too. + fn build(&self) { + MinimalStakeAmount::::put(self.minimal_stake_amount); + SlashFineAmount::::put(self.slash_fine_amount); + + for (sender, chain, stake_amount) in self.sequencers_stake.iter() { + assert!(!Pallet::::is_active_sequencer(*chain, &sender)); + assert!(stake_amount >= &MinimalStakeAmount::::get()); + + >::insert((sender, &chain), stake_amount); + + ActiveSequencers::::try_mutate(|active_sequencers| { + active_sequencers.entry(*chain).or_default().try_push(sender.clone()) + }) + .expect("setting active sequencers is successfull"); + + Pallet::::announce_sequencer_joined_active_set(*chain, sender.clone()); + + // add full rights to sequencer (create whole entry in SequencersRights @ rolldown) + // add +1 cancel right to all other sequencers (non active are deleted from SequencersRights @ rolldown) + assert!(T::Currency::reserve(&sender, *stake_amount).is_ok()); + } + + for chain in + self.sequencers_stake.iter().map(|(_, chain, _)| chain).collect::>() + { + if let Some(seq) = ActiveSequencers::::get() + .get(chain) + .and_then(|sequencers| sequencers.first()) + { + SelectedSequencer::::mutate(|selected| selected.insert(*chain, seq.clone())); + } + } + + >::put(0); + } + } + + // The pallet needs to be configured above session in construct_runtime! + // to work correctly with the on_initialize hook and the NextSequencerIndex updates + // This requirement may have changed due later edits (we now are doing all processing in on_finalize) + #[pallet::hooks] + impl Hooks> for Pallet { + fn on_initialize(n: BlockNumberFor) -> Weight { + T::DbWeight::get().reads_writes(4, 3) + } + + fn on_finalize(n: BlockNumberFor) -> () { + let active_sequencers = ActiveSequencers::::get(); + // let active_chains = active_sequencers.keys().collect()::>(); + if !(n % T::BlocksForSequencerUpdate::get().into()).is_zero() { + return + } + + NextSequencerIndex::::mutate(|idxs| { + for (chain, set) in active_sequencers.iter() { + idxs.entry(*chain) + .and_modify(|v| { + if *v > set.len() as u32 { + *v = SequencerIndex::zero(); + } + }) + .or_insert(SequencerIndex::zero()); + } + idxs.retain(|chain, _seq| active_sequencers.contains_key(chain)); + }); + + SelectedSequencer::::mutate(|selected| { + for (chain, set) in active_sequencers.iter() { + if let Some(index) = NextSequencerIndex::::get().get(chain) { + if let Some(selected_seq) = set.iter().skip(*index as usize).next() { + selected.insert(*chain, selected_seq.clone()); + } + } + } + selected.retain(|chain, _seq| active_sequencers.contains_key(chain)); + }); + + NextSequencerIndex::::mutate(|indexes_set| { + indexes_set.iter_mut().for_each(|(_chain, idx)| { + *idx = idx.saturating_add(One::one()); + }) + }); + } + } + + #[pallet::storage] + #[pallet::getter(fn get_sequencer_stake)] + pub type SequencerStake = + StorageMap<_, Blake2_128Concat, (AccountIdOf, T::ChainId), BalanceOf, ValueQuery>; + + #[pallet::storage] + pub type AliasAccount = + StorageMap<_, Blake2_128Concat, (AccountIdOf, T::ChainId), AccountIdOf, OptionQuery>; + + #[pallet::storage] + pub type AliasAccountInUse = + StorageMap<_, Blake2_128Concat, AccountIdOf, (), OptionQuery>; + + // #[pallet::storage] + // #[pallet::unbounded] + // #[pallet::getter(fn get_eligible_to_be_sequencers)] + // pub type EligibleToBeSequencers = + // StorageValue<_, BTreeMap, RoundRefCount>, ValueQuery>; + + #[pallet::storage] + #[pallet::unbounded] + #[pallet::getter(fn get_active_sequencers)] + pub type ActiveSequencers = StorageValue< + _, + BTreeMap, T::MaxSequencers>>, + ValueQuery, + >; + + #[pallet::storage] + #[pallet::getter(fn get_selected_sequencer)] + #[pallet::unbounded] + pub type SelectedSequencer = + StorageValue<_, BTreeMap, ValueQuery>; + + #[pallet::storage] + #[pallet::getter(fn get_current_round)] + pub type CurrentRound = StorageValue<_, RoundIndex, ValueQuery>; + + #[pallet::storage] + #[pallet::unbounded] + #[pallet::getter(fn get_next_sequencer_index)] + pub type NextSequencerIndex = + StorageValue<_, BTreeMap, ValueQuery>; + + // #[pallet::storage] + // #[pallet::unbounded] + // #[pallet::getter(fn get_round_collators)] + // pub type RoundCollators = + // StorageMap<_, Blake2_128Concat, RoundIndex, Vec, ValueQuery>; + + #[pallet::storage] + #[pallet::unbounded] + #[pallet::getter(fn get_slash_fine_amount)] + pub type SlashFineAmount = StorageValue<_, BalanceOf, ValueQuery>; + + #[pallet::storage] + #[pallet::unbounded] + #[pallet::getter(fn get_minimal_stake_amount)] + pub type MinimalStakeAmount = StorageValue<_, BalanceOf, ValueQuery>; + + #[pallet::storage] + #[pallet::getter(fn get_round_collator_reward_info)] + /// Stores information about rewards per each session + pub type RoundSequencerRewardInfo = StorageDoubleMap< + _, + Blake2_128Concat, + T::AccountId, + Twox64Concat, + RoundIndex, + BalanceOf, + OptionQuery, + >; + + #[pallet::storage] + #[pallet::getter(fn points)] + /// Total points awarded to collators for block production in the round + pub type Points = StorageMap<_, Twox64Concat, RoundIndex, RewardPoint, ValueQuery>; + + #[pallet::storage] + #[pallet::getter(fn awarded_pts)] + /// Points for each collator per round + pub type AwardedPts = StorageDoubleMap< + _, + Twox64Concat, + RoundIndex, + Twox64Concat, + T::AccountId, + RewardPoint, + ValueQuery, + >; + + #[pallet::event] + #[pallet::generate_deposit(pub(super) fn deposit_event)] + pub enum Event { + SequencersRemovedFromActiveSet(T::ChainId, Vec), + SequencerJoinedActiveSet(T::ChainId, T::AccountId), + StakeProvided { + chain: T::ChainId, + added_stake: BalanceOf, + total_stake: BalanceOf, + }, + StakeRemoved { + chain: T::ChainId, + removed_stake: BalanceOf, + }, + /// Notify about reward periods that has been paid (sequencer, payout rounds, any rewards left) + SequencerRewardsDistributed(T::AccountId, PayoutRounds), + /// Paid the account the balance as liquid rewards + Rewarded(RoundIndex, T::AccountId, BalanceOf), + } + + #[pallet::error] + /// Errors + pub enum Error { + OperationFailed, + MathOverflow, + SequencerIsNotInActiveSet, + SequencerAlreadyInActiveSet, + CantUnstakeWhileInActiveSet, + // NotEligibleToBeSequencer, + NotEnoughSequencerStake, + MaxSequencersLimitReached, + TestUnstakingError, + UnknownChainId, + NoStakeToUnStake, + AddressInUse, + AliasAccountIsActiveSequencer, + SequencerAccountIsActiveSequencerAlias, + SequencerRoundRewardsDNE, + } + + #[pallet::config] + pub trait Config: frame_system::Config { + type RuntimeEvent: From> + IsType<::RuntimeEvent>; + /// The currency type + type Currency: ReservableCurrency; + #[pallet::constant] + type MinimumSequencers: Get; + type RolldownProvider: RolldownProviderTrait; + type ChainId: Parameter + + Member + + Default + + TypeInfo + + MaybeSerializeDeserialize + + MaxEncodedLen + + PartialOrd + + codec::Decode + + codec::Encode + + Ord + + Copy; + #[pallet::constant] + type NoOfPastSessionsForEligibility: Get; + #[pallet::constant] + // TODO: rename 'PerChain' + type MaxSequencers: Get; + #[pallet::constant] + type BlocksForSequencerUpdate: Get; + #[pallet::constant] + type CancellerRewardPercentage: Get; + #[pallet::constant] + type DefaultPayoutLimit: Get; + /// The account id that holds the sequencer issuance + type SequencerIssuanceVault: Get; + /// Number of rounds after which block authors are rewarded + #[pallet::constant] + type RewardPaymentDelay: Get; + /// The module used for computing and getting issuance + type Issuance: ComputeIssuance + GetIssuance>; + } + + #[pallet::call] + impl Pallet { + #[pallet::call_index(0)] + #[pallet::weight(T::DbWeight::get().reads_writes(5, 5.saturating_add(T::MaxSequencers::get().into())).saturating_add(Weight::from_parts(40_000_000, 0)))] + /// provides stake for the purpose of becoming sequencers + /// + /// - `chain` - chain for which to assign stake_amount + /// - `stake_amont` - amount of stake + /// - `alias_account` - optional parameter, alias account is eligible to create manual bataches + /// of updates in pallet-rolldown. Alias account can not be set to another + /// active sequencer or to some account that is already used as + /// alias_account for another sequencer + /// - `stake_action` - determines what are candidate expectations regarding joining active set, + /// * 'StakeOnly' - sequencer only provides stake, but does not join active set. + /// * 'StakeAndJoinActiveSet' - sequencer provides stake and joins active set. Fails if + /// candidate didnt join active set or if candidate is already in active set. + /// Candiate can also choose to call `rejoin_active_sequencers` later when there are free seats to + /// join active set + pub fn provide_sequencer_stake( + origin: OriginFor, + chain: T::ChainId, + stake_amount: BalanceOf, + alias_account: Option, + stake_action: StakeAction, + sender: T::AccountId, + ) -> DispatchResultWithPostInfo { + let _ = ensure_root(origin)?; + + ensure!( + !AliasAccountInUse::::contains_key(sender.clone()), + Error::::SequencerAccountIsActiveSequencerAlias + ); + + let sequencer_stake = >::try_mutate( + (&sender, &chain), + |stake| -> Result, Error> { + *stake = stake.checked_add(&stake_amount).ok_or(Error::::MathOverflow)?; + Ok(*stake) + }, + )?; + + if let StakeAction::StakeAndJoinActiveSet = stake_action { + ensure!( + !Self::is_active_sequencer(chain, &sender), + Error::::SequencerAlreadyInActiveSet + ); + ensure!( + sequencer_stake >= MinimalStakeAmount::::get(), + Error::::NotEnoughSequencerStake + ); + ActiveSequencers::::try_mutate(|active_sequencers| { + active_sequencers + .entry(chain) + .or_default() + .try_push(sender.clone()) + .or(Err(Error::::MaxSequencersLimitReached)) + })?; + Self::announce_sequencer_joined_active_set(chain, sender.clone()); + }; + + if let Some(alias_account) = alias_account { + ensure!( + !AliasAccountInUse::::contains_key(alias_account.clone()), + Error::::AddressInUse + ); + ensure!( + !Self::is_active_sequencer(chain, &alias_account), + Error::::AliasAccountIsActiveSequencer + ); + + if let Some(prev_alias) = AliasAccount::::take((sender.clone(), chain)) { + AliasAccountInUse::::remove(prev_alias); + } + + AliasAccount::::insert((sender.clone(), chain), alias_account.clone()); + AliasAccountInUse::::insert(alias_account.clone(), ()); + } + + // add full rights to sequencer (create whole entry in SequencersRights @ rolldown) + // add +1 cancel right to all other sequencers (non active are deleted from SequencersRights @ rolldown) + T::Currency::reserve(&sender, stake_amount)?; + + Self::deposit_event(Event::StakeProvided { + chain, + added_stake: stake_amount, + total_stake: sequencer_stake, + }); + + Ok(().into()) + } + + #[pallet::call_index(1)] + #[pallet::weight(T::DbWeight::get().reads_writes(2, 3.saturating_add(T::MaxSequencers::get().into())).saturating_add(Weight::from_parts(40_000_000, 0)))] + pub fn leave_active_sequencers( + origin: OriginFor, + chain: T::ChainId, + ) -> DispatchResultWithPostInfo { + let sender = ensure_signed(origin)?; + ensure!( + Self::is_active_sequencer(chain, &sender), + Error::::SequencerIsNotInActiveSet + ); + + Self::remove_sequencers_from_active_set(chain, sp_std::iter::once(sender).collect()); + + Ok(().into()) + } + + #[pallet::call_index(2)] + #[pallet::weight(T::DbWeight::get().reads_writes(3, 3.saturating_add(T::MaxSequencers::get().into())).saturating_add(Weight::from_parts(40_000_000, 0)))] + pub fn rejoin_active_sequencers( + origin: OriginFor, + chain: T::ChainId, + sender: T::AccountId, + ) -> DispatchResultWithPostInfo { + let _ = ensure_root(origin)?; + ensure!( + !Self::is_active_sequencer(chain, &sender), + Error::::SequencerAlreadyInActiveSet + ); + ensure!( + ActiveSequencers::::get().get(&chain).unwrap_or(&Default::default()).len() < + T::MaxSequencers::get() as usize, + Error::::MaxSequencersLimitReached + ); + ensure!( + SequencerStake::::get((&sender, &chain)) >= MinimalStakeAmount::::get(), + Error::::NotEnoughSequencerStake + ); + + ActiveSequencers::::try_mutate(|active_sequencers| { + active_sequencers.entry(chain).or_default().try_push(sender.clone()) + }) + .or(Err(Error::::MaxSequencersLimitReached))?; + + Pallet::::deposit_event(Event::SequencerJoinedActiveSet(chain, sender.clone())); + Self::announce_sequencer_joined_active_set(chain, sender.clone()); + + Ok(().into()) + } + + #[pallet::call_index(3)] + #[pallet::weight(T::DbWeight::get().reads_writes(6, 6).saturating_add(Weight::from_parts(40_000_000, 0)))] + pub fn unstake(origin: OriginFor, chain: T::ChainId) -> DispatchResultWithPostInfo { + let sender = ensure_signed(origin)?; + ensure!( + !Self::is_active_sequencer(chain, &sender), + Error::::CantUnstakeWhileInActiveSet + ); + let sequencer_stake = SequencerStake::::get((&sender, &chain)); + ensure!(!sequencer_stake.is_zero(), Error::::NoStakeToUnStake); + + T::RolldownProvider::sequencer_unstaking(chain, &sender)?; + + let unreserve_remaining = T::Currency::unreserve(&sender, sequencer_stake); + if !unreserve_remaining.is_zero() { + log!(error, "unstake unreserve_remaining is non-zero - sender {:?}, sequencer {:?}, unreserve_remaining {:?}", &sender, sequencer_stake, unreserve_remaining); + } + + SequencerStake::::remove((&sender, &chain)); + + Self::deposit_event(Event::StakeRemoved { chain, removed_stake: sequencer_stake }); + Ok(().into()) + } + + #[pallet::call_index(4)] + #[pallet::weight(T::DbWeight::get().reads_writes(2.saturating_add(T::MaxSequencers::get().into()), 5.saturating_add(T::MaxSequencers::get().into())).saturating_add(Weight::from_parts(40_000_000, 0)))] + pub fn set_sequencer_configuration( + origin: OriginFor, + chain: T::ChainId, + minimal_stake_amount: BalanceOf, + slash_fine_amount: BalanceOf, + ) -> DispatchResultWithPostInfo { + let _ = ensure_root(origin)?; + + >::put(minimal_stake_amount); + >::put(slash_fine_amount); + + let active_sequencers = ActiveSequencers::::get(); + let deactivating_sequencers = active_sequencers + .get(&chain) + .ok_or(Error::::UnknownChainId)? + .clone() + .into_inner() + .into_iter() + .filter(|s| SequencerStake::::get((&s, &chain)) < minimal_stake_amount) + .collect::<_>(); + + Pallet::::remove_sequencers_from_active_set(chain, deactivating_sequencers); + + Ok(().into()) + } + + #[pallet::call_index(5)] + #[pallet::weight(T::DbWeight::get().reads_writes(2.saturating_add(T::MaxSequencers::get().into()), 5.saturating_add(T::MaxSequencers::get().into())).saturating_add(Weight::from_parts(40_000_000, 0)))] + /// Allows to configure alias_account for active sequencer. This extrinisic can only be called + /// by active sequencer + /// - `chain` - + /// - `alias_account` - optional parameter, alias account is eligible to create manual bataches + /// of updates in pallet-rolldown. Alias account can not be set to another + /// active sequencer or to some account that is already used as + /// alias_account for another sequencer + pub fn set_updater_account_for_sequencer( + origin: OriginFor, + chain: T::ChainId, + alias_account: Option, + ) -> DispatchResultWithPostInfo { + let sequencer = ensure_signed(origin)?; + + ensure!( + Self::is_active_sequencer(chain, &sequencer), + Error::::SequencerIsNotInActiveSet + ); + + if let Some(alias) = alias_account { + ensure!( + !Self::is_active_sequencer(chain, &alias), + Error::::AliasAccountIsActiveSequencer + ); + ensure!( + !AliasAccountInUse::::contains_key(alias.clone()), + Error::::AddressInUse + ); + AliasAccount::::insert((sequencer.clone(), chain), alias.clone()); + AliasAccountInUse::::insert(alias, ()); + } else { + if let Some(alias) = AliasAccount::::take((sequencer, chain)) { + AliasAccountInUse::::remove(alias); + } + } + + Ok(().into()) + } + + /// This extrinsic should be used to distribute rewards for sequencer. + /// + /// params: + /// - sequencer - account id + /// - number_of_sessions - number of rewards periods that should be processed within extrinsic. + #[pallet::call_index(6)] + #[pallet::weight(number_of_sessions.unwrap_or(T::DefaultPayoutLimit::get()) * (T::DbWeight::get().reads_writes(3, 3).saturating_add(Weight::from_parts(40_000_000, 0))))] + #[transactional] + pub fn payout_sequencer_rewards( + origin: OriginFor, + sequencer: T::AccountId, + number_of_sessions: Option, + ) -> DispatchResultWithPostInfo { + let _caller = ensure_signed(origin)?; + Self::do_payout_sequencer_rewards(sequencer, number_of_sessions) + } + } +} + +impl Pallet { + fn announce_sequencer_joined_active_set(chain: T::ChainId, sequencer: T::AccountId) { + T::RolldownProvider::new_sequencer_active(chain, &sequencer); + Pallet::::deposit_event(Event::::SequencerJoinedActiveSet(chain, sequencer)); + } + + fn announce_sequencers_removed_from_active_set( + chain: T::ChainId, + sequencers: Vec, + ) { + T::RolldownProvider::handle_sequencer_deactivations(chain, sequencers.clone()); + Pallet::::deposit_event(Event::::SequencersRemovedFromActiveSet(chain, sequencers)); + } + + fn maybe_remove_sequencer_from_active_set(chain: T::ChainId, sequencer: T::AccountId) { + if >::get((sequencer.clone(), chain)) < MinimalStakeAmount::::get() && + Self::is_active_sequencer(chain, &sequencer) + { + Self::remove_sequencers_from_active_set(chain, [sequencer].iter().cloned().collect()); + } + } + + // Caller should check if the sequencer is infact in the active set + fn remove_sequencers_from_active_set( + chain: T::ChainId, + deactivating_sequencers: BTreeSet, + ) { + if deactivating_sequencers.is_empty() { + return + } + + let active_seqs = ActiveSequencers::::get(); + let seqs_per_chain = active_seqs.get(&chain).cloned().unwrap_or_default(); + let active_set: &[AccountIdOf] = seqs_per_chain.as_ref(); + + NextSequencerIndex::::mutate(|idxs| { + if let Some(next_pos) = idxs.get_mut(&chain) { + let shift = deactivating_sequencers + .iter() + .filter_map(|seq| active_set.iter().position(|elem| elem == seq)) + .into_iter() + .filter(|pos| *pos < *next_pos as usize) + .count(); + if shift > 0 { + *next_pos = next_pos.saturating_sub(shift as u32); + } + } + }); + + ActiveSequencers::::mutate(|active_set| { + if let Some(set) = active_set.get_mut(&chain) { + set.retain(|elem| !deactivating_sequencers.contains(elem)) + } + }); + + SelectedSequencer::::mutate(|selected| { + if matches!( + selected.get(&chain), + Some(elem) if deactivating_sequencers.contains(elem)) + { + selected.remove(&chain); + } + }); + + Self::announce_sequencers_removed_from_active_set( + chain, + deactivating_sequencers.iter().cloned().collect(), + ); + } + + #[cfg(test)] + fn set_active_sequencers( + iter: impl IntoIterator, + ) -> Result<(), Error> { + ActiveSequencers::::kill(); + ActiveSequencers::::try_mutate(|active_sequencers| { + for (chain, seq) in iter.into_iter() { + active_sequencers + .entry(chain) + .or_default() + .try_push(seq) + .or(Err(Error::::MaxSequencersLimitReached))?; + } + Ok::<_, Error>(()) + })?; + Ok(()) + } + + fn do_payout_sequencer_rewards( + sequencer: T::AccountId, + number_of_sessions: Option, + ) -> DispatchResultWithPostInfo { + let mut rounds = Vec::::new(); + + let limit = number_of_sessions.unwrap_or(T::DefaultPayoutLimit::get()); + for (id, (round, reward)) in + RoundSequencerRewardInfo::::iter_prefix(sequencer.clone()).enumerate() + { + if (id as u32) >= limit { + break + } + + Self::payout_reward(round, sequencer.clone(), reward)?; + + RoundSequencerRewardInfo::::remove(sequencer.clone(), round); + rounds.push(round); + } + + ensure!(!rounds.is_empty(), Error::::SequencerRoundRewardsDNE); + + if let Some(_) = RoundSequencerRewardInfo::::iter_prefix(sequencer.clone()).next() { + Self::deposit_event(Event::SequencerRewardsDistributed( + sequencer, + PayoutRounds::Partial(rounds), + )); + } else { + Self::deposit_event(Event::SequencerRewardsDistributed(sequencer, PayoutRounds::All)); + } + + Ok(().into()) + } + + pub fn payout_reward(round: RoundIndex, to: T::AccountId, amt: BalanceOf) -> DispatchResult { + let _ = ::Currency::transfer( + &::SequencerIssuanceVault::get(), + &to, + amt, + ExistenceRequirement::AllowDeath, + )?; + Self::deposit_event(Event::Rewarded(round, to.clone(), amt)); + Ok(()) + } +} + +impl SequencerStakingProviderTrait, BalanceOf, ChainIdOf> + for Pallet +{ + fn is_active_sequencer( + chain: ::ChainId, + sequencer: &AccountIdOf, + ) -> bool { + matches!( + ActiveSequencers::::get().get(&chain), Some(set) if set.contains(&sequencer) + ) + } + + fn is_active_sequencer_alias( + chain: ::ChainId, + sequencer_account: &AccountIdOf, + alias_account: &AccountIdOf, + ) -> bool { + matches!( + AliasAccount::::get((sequencer_account, chain)), + Some(alias) if alias == *alias_account + ) + } + + fn is_selected_sequencer(chain: ChainIdOf, sequencer: &AccountIdOf) -> bool { + matches!( + SelectedSequencer::::get().get(&chain), + Some(selected) if selected == sequencer + ) + } + + fn slash_sequencer( + chain: ChainIdOf, + to_be_slashed: &T::AccountId, + maybe_to_reward: Option<&T::AccountId>, + ) -> DispatchResult { + // Use slashed amount partially to reward canceler, partially to vault to pay for l1 fees + let maybe_leaving_sequencer = to_be_slashed.clone(); + >::mutate((to_be_slashed, chain), |stake| -> DispatchResult { + let slash_fine_amount = SlashFineAmount::::get(); + let slash_fine_amount_actual = (*stake).min(slash_fine_amount); + *stake = stake.saturating_sub(slash_fine_amount_actual); + let mut burned_amount = slash_fine_amount_actual; + if let Some(to_reward) = maybe_to_reward { + let mut repatriate_amount = T::CancellerRewardPercentage::get() * slash_fine_amount; // this raw * is safe since result is a fraction of input + repatriate_amount = repatriate_amount.min(slash_fine_amount_actual); + burned_amount = slash_fine_amount_actual.saturating_sub(repatriate_amount); + let _ = T::Currency::repatriate_reserved( + to_be_slashed, + to_reward, + repatriate_amount, + frame_support::traits::BalanceStatus::Free, + ); + } + let _ = T::Currency::slash_reserved(to_be_slashed, burned_amount); + Ok(()) + }); + + Self::maybe_remove_sequencer_from_active_set(chain, maybe_leaving_sequencer); + + Ok(().into()) + } + + fn selected_sequencer(chain: ChainIdOf) -> Option> { + SelectedSequencer::::get().get(&chain).cloned() + } +} + +impl SequencerStakingRewardsTrait, RoundIndex> for Pallet { + fn note_update_author(author: &AccountIdOf) { + let now = >::get(); + let score_plus_20 = >::get(now, &author).saturating_add(20); + >::insert(now, author, score_plus_20); + >::mutate(now, |x| *x = x.saturating_add(20)); + } + fn pay_sequencers(round: RoundIndex) { + >::put(round); + // payout is now - duration rounds ago => now - duration > 0 else return early + let duration = T::RewardPaymentDelay::get(); + if round < duration { + return + } + let round_to_payout = round.saturating_sub(duration); + let total = >::take(round_to_payout); + if total.is_zero() { + return + } + let total_issuance = + T::Issuance::get_sequencer_issuance(round_to_payout).unwrap_or(Zero::zero()); + + for (author, pts) in >::drain_prefix(round_to_payout) { + let author_issuance_perbill = Perbill::from_rational(pts, total); + let author_rewards = author_issuance_perbill.mul_floor(total_issuance); + + RoundSequencerRewardInfo::::insert(author, round_to_payout, author_rewards); + } + } +} + +/// Simple ensure origin struct to filter for the active sequencer accounts. +pub struct EnsureActiveSequencer(sp_std::marker::PhantomData); +impl EnsureOrigin<::RuntimeOrigin> + for EnsureActiveSequencer +{ + type Success = T::AccountId; + fn try_origin(o: T::RuntimeOrigin) -> Result { + o.into().and_then(|o| match o { + frame_system::RawOrigin::Signed(ref who) + if as SequencerStakingProviderTrait< + AccountIdOf, + BalanceOf, + ChainIdOf, + >>::is_active_sequencer(todo!(), &who) => + Ok(who.clone()), + r => Err(T::RuntimeOrigin::from(r)), + }) + } + + #[cfg(feature = "runtime-benchmarks")] + fn try_successful_origin() -> Result { + unimplemented!(); + // let founder = Founder::::get().ok_or(())?; + // Ok(T::RuntimeOrigin::from(frame_system::RawOrigin::Signed(founder))) + } +} diff --git a/gasp-node/pallets/sequencer-staking/src/mock.rs b/gasp-node/pallets/sequencer-staking/src/mock.rs new file mode 100644 index 000000000..447e08968 --- /dev/null +++ b/gasp-node/pallets/sequencer-staking/src/mock.rs @@ -0,0 +1,283 @@ +// Copyright (C) 2020 Mangata team +use super::*; + +use crate as sequencer_staking; +use core::convert::TryFrom; +use frame_support::{ + construct_runtime, derive_impl, parameter_types, + traits::{tokens::fungible::Mutate, Nothing}, + PalletId, +}; +use mangata_support::traits::{ComputeIssuance, GetIssuance}; + +use orml_traits::parameter_type_with_key; +use sp_runtime::{ + traits::AccountIdConversion, BuildStorage, Perbill, Percent, Permill, Saturating, +}; + +pub(crate) type AccountId = u64; +pub(crate) type Amount = i128; +pub(crate) type Balance = u128; +pub(crate) type TokenId = u32; +pub(crate) type ChainId = u32; + +pub mod consts { + pub const ALICE: u64 = 2; + pub const BOB: u64 = 3; + pub const CHARLIE: u64 = 4; + pub const DAVE: u64 = 5; + pub const EVE: u64 = 6; + + pub const NATIVE_TOKEN_ID: super::TokenId = 0; + pub const DEFAULT_CHAIN_ID: u32 = 1; + + pub const TOKENS_ENDOWED: super::Balance = 10_000; + pub const MINIMUM_STAKE: super::Balance = 1000; + pub const SLASH_AMOUNT: super::Balance = 100; +} + +type Block = frame_system::mocking::MockBlock; + +construct_runtime!( + pub enum Test { + System: frame_system, + Tokens: orml_tokens, + SequencerStaking: sequencer_staking + } +); + +#[derive_impl(frame_system::config_preludes::TestDefaultConfig as frame_system::DefaultConfig)] +impl frame_system::Config for Test { + type Block = Block; +} + +parameter_types! { + pub const HistoryLimit: u32 = 10u32; + + pub const LiquidityMiningIssuanceVaultId: PalletId = PalletId(*b"py/lqmiv"); + pub LiquidityMiningIssuanceVault: AccountId = LiquidityMiningIssuanceVaultId::get().into_account_truncating(); + pub const StakingIssuanceVaultId: PalletId = PalletId(*b"py/stkiv"); + pub StakingIssuanceVault: AccountId = StakingIssuanceVaultId::get().into_account_truncating(); + pub const SequencerIssuanceVaultId: PalletId = PalletId(*b"py/seqiv"); + pub SequencerIssuanceVault: AccountId = SequencerIssuanceVaultId::get().into_account_truncating(); + + pub const TotalCrowdloanAllocation: Balance = 0; + pub const LinearIssuanceAmount: Balance = 4_000_000_000; + pub const LinearIssuanceBlocks: u32 = 10_000u32; + pub const LiquidityMiningSplit: Perbill = Perbill::from_parts(555555556); + pub const StakingSplit: Perbill = Perbill::from_parts(344444444); + pub const SequencerSplit: Perbill = Perbill::from_parts(100000000); + pub const ImmediateTGEReleasePercent: Percent = Percent::from_percent(20); + pub const TGEReleasePeriod: u32 = 5_256_000u32; // 2 years + pub const TGEReleaseBegin: u32 = 100_800u32; // Two weeks into chain start + pub const BlocksPerRound: u32 = 5; + pub const TargetTge:u128 = 2_000_000_000u128; +} + +pub struct MockIssuance; +impl ComputeIssuance for MockIssuance { + fn initialize() {} + fn compute_issuance(_n: u32) { + let issuance = Self::get_sequencer_issuance(_n).unwrap(); + + let _ = TokensOf::::mint_into(&SequencerIssuanceVault::get(), issuance.into()); + } +} + +impl GetIssuance for MockIssuance { + fn get_all_issuance(_n: u32) -> Option<(Balance, Balance, Balance)> { + unimplemented!() + } + fn get_liquidity_mining_issuance(_n: u32) -> Option { + unimplemented!() + } + fn get_staking_issuance(_n: u32) -> Option { + unimplemented!() + } + fn get_sequencer_issuance(_n: u32) -> Option { + let to_be_issued: Balance = + LinearIssuanceAmount::get() - TargetTge::get() - TotalCrowdloanAllocation::get(); + let linear_issuance_sessions: u32 = LinearIssuanceBlocks::get() / BlocksPerRound::get(); + let linear_issuance_per_session = to_be_issued / linear_issuance_sessions as Balance; + let issuance = SequencerSplit::get() * linear_issuance_per_session; + Some(issuance) + } +} + +parameter_type_with_key! { + pub ExistentialDeposits: |currency_id: TokenId| -> Balance { + match currency_id { + _ => 0, + } + }; +} + +impl orml_tokens::Config for Test { + type RuntimeEvent = RuntimeEvent; + type Balance = Balance; + type Amount = Amount; + type CurrencyId = TokenId; + type WeightInfo = (); + type ExistentialDeposits = ExistentialDeposits; + type MaxLocks = (); + type DustRemovalWhitelist = (); + type MaxReserves = (); + type ReserveIdentifier = [u8; 8]; + type CurrencyHooks = (); + type NontransferableTokens = Nothing; + type NontransferableTokensAllowList = Nothing; +} + +parameter_types! { + pub const CancellerRewardPercentage: Permill = Permill::from_percent(20); + pub const NativeTokenId: TokenId = 0; + pub const DefaultPayoutLimit: u32 = 15; + pub const RewardPaymentDelay: u32 = 2; +} + +impl sequencer_staking::Config for Test { + type RuntimeEvent = RuntimeEvent; + type Currency = orml_tokens::CurrencyAdapter; + type MinimumSequencers = frame_support::traits::ConstU32<2>; + type RolldownProvider = MockRolldownProviderApi; + type NoOfPastSessionsForEligibility = frame_support::traits::ConstU32<2>; + type MaxSequencers = frame_support::traits::ConstU32<11>; + type BlocksForSequencerUpdate = frame_support::traits::ConstU32<2>; + type CancellerRewardPercentage = CancellerRewardPercentage; + type ChainId = ChainId; + type DefaultPayoutLimit = DefaultPayoutLimit; + type SequencerIssuanceVault = SequencerIssuanceVault; + type RewardPaymentDelay = RewardPaymentDelay; + type Issuance = MockIssuance; +} + +mockall::mock! { + pub RolldownProviderApi {} + + impl RolldownProviderTrait for RolldownProviderApi { + fn new_sequencer_active(chain: ChainId, sequencer: &AccountId); + fn sequencer_unstaking(chain: ChainId, sequencer: &AccountId)->DispatchResult; + fn handle_sequencer_deactivations(chain: ChainId, deactivated_sequencers: Vec); + } +} + +pub struct ExtBuilder { + ext: sp_io::TestExternalities, +} + +impl ExtBuilder { + pub fn new() -> Self { + let mut t = frame_system::GenesisConfig::::default() + .build_storage() + .expect("Frame system builds valid default genesis config"); + + orml_tokens::GenesisConfig:: { + tokens_endowment: vec![ + (consts::ALICE, consts::NATIVE_TOKEN_ID, consts::TOKENS_ENDOWED), + (consts::BOB, consts::NATIVE_TOKEN_ID, consts::TOKENS_ENDOWED), + (consts::CHARLIE, consts::NATIVE_TOKEN_ID, consts::TOKENS_ENDOWED), + (consts::DAVE, consts::NATIVE_TOKEN_ID, consts::TOKENS_ENDOWED), + ], + created_tokens_for_staking: Default::default(), + } + .assimilate_storage(&mut t) + .expect("Tokens storage can be assimilated"); + + sequencer_staking::GenesisConfig:: { + minimal_stake_amount: consts::MINIMUM_STAKE, + slash_fine_amount: consts::SLASH_AMOUNT, + sequencers_stake: vec![ + (consts::ALICE, consts::DEFAULT_CHAIN_ID, consts::MINIMUM_STAKE), + (consts::BOB, consts::DEFAULT_CHAIN_ID, consts::MINIMUM_STAKE), + ], + } + .assimilate_storage(&mut t) + .expect("SequencerStaking storage can be assimilated"); + + let mut ext = sp_io::TestExternalities::new(t); + + Self { ext } + } + + fn create_if_does_not_exists(&mut self, token_id: TokenId) { + self.ext.execute_with(|| { + while token_id >= Tokens::next_asset_id() { + Tokens::create(RuntimeOrigin::root(), 0, 0).unwrap(); + } + }); + } + + pub fn issue(mut self, who: AccountId, token_id: TokenId, balance: Balance) -> Self { + self.create_if_does_not_exists(token_id); + self.ext + .execute_with(|| Tokens::mint(RuntimeOrigin::root(), token_id, who, balance).unwrap()); + return self + } + + pub fn build(self) -> sp_io::TestExternalities { + self.ext + } +} + +#[macro_use] +macro_rules! set_default_mocks { + () => { + let new_sequencer_active_mock = MockRolldownProviderApi::new_sequencer_active_context(); + new_sequencer_active_mock.expect().times(2).returning(|_, _| ()); + }; +} +pub(crate) use set_default_mocks; + +pub fn forward_to_block(n: BlockNumberFor) +where + T: frame_system::Config, + T: sequencer_staking::Config, +{ + while frame_system::Pallet::::block_number() < n { + sequencer_staking::Pallet::::on_finalize(frame_system::Pallet::::block_number()); + frame_system::Pallet::::on_finalize(frame_system::Pallet::::block_number()); + let new_block_number = + frame_system::Pallet::::block_number().saturating_add(1u32.into()); + frame_system::Pallet::::set_block_number(new_block_number); + + frame_system::Pallet::::on_initialize(new_block_number); + sequencer_staking::Pallet::::on_initialize(new_block_number); + } +} + +pub(crate) fn events() -> Vec> { + System::events() + .into_iter() + .map(|r| r.event) + .filter_map( + |e| if let RuntimeEvent::SequencerStaking(inner) = e { Some(inner) } else { None }, + ) + .collect::>() +} + +#[macro_export] +macro_rules! assert_eq_events { + ($events:expr) => { + match &$events { + e => similar_asserts::assert_eq!(*e, $crate::mock::events()), + } + }; +} + +#[macro_export] +macro_rules! assert_event_emitted { + ($event:expr) => { + match &$event { + e => { + assert!( + $crate::mock::events().iter().find(|x| *x == e).is_some(), + "Event {:?} was not found in events: \n {:?}", + e, + crate::mock::events() + ); + }, + } + }; +} + +pub type TokensOf = ::Currency; diff --git a/gasp-node/pallets/sequencer-staking/src/tests.rs b/gasp-node/pallets/sequencer-staking/src/tests.rs new file mode 100644 index 000000000..d10b48e1f --- /dev/null +++ b/gasp-node/pallets/sequencer-staking/src/tests.rs @@ -0,0 +1,1226 @@ +use crate::{ + mock::{consts::*, *}, + *, +}; +use core::{convert::TryFrom, future::pending, str::CharIndices}; +use frame_support::{assert_err, assert_ok}; +use mockall::predicate::eq; +use serial_test::serial; + +#[test] +#[serial] +fn test_genesis_build() { + let new_sequencer_active_mock = MockRolldownProviderApi::new_sequencer_active_context(); + new_sequencer_active_mock.expect().times(2).return_const(()); + ExtBuilder::new().build().execute_with(|| { + assert_eq!(SequencerStake::::get(&(ALICE, consts::DEFAULT_CHAIN_ID)), MINIMUM_STAKE); + assert_eq!(SequencerStake::::get(&(BOB, consts::DEFAULT_CHAIN_ID)), MINIMUM_STAKE); + + assert!(SequencerStaking::is_active_sequencer(consts::DEFAULT_CHAIN_ID, &ALICE)); + assert!(SequencerStaking::is_active_sequencer(consts::DEFAULT_CHAIN_ID, &BOB)); + + assert_eq!(TokensOf::::total_balance(&ALICE), TOKENS_ENDOWED); + assert_eq!(TokensOf::::reserved_balance(&ALICE), MINIMUM_STAKE); + assert_eq!(TokensOf::::total_balance(&BOB), TOKENS_ENDOWED); + assert_eq!(TokensOf::::reserved_balance(&BOB), MINIMUM_STAKE); + + assert_eq!(CurrentRound::::get(), 0); + }); +} + +#[test] +#[serial] +fn test_provide_sequencer_stake_works_and_activates() { + set_default_mocks!(); + ExtBuilder::new().build().execute_with(|| { + forward_to_block::(10); + + let new_sequencer_active_mock = MockRolldownProviderApi::new_sequencer_active_context(); + new_sequencer_active_mock.expect().times(1).return_const(()); + + assert_eq!(TokensOf::::total_balance(&CHARLIE), TOKENS_ENDOWED); + assert_eq!(TokensOf::::reserved_balance(&CHARLIE), 0); + assert_eq!(SequencerStake::::get(&(CHARLIE, consts::DEFAULT_CHAIN_ID)), 0); + assert!(!SequencerStaking::is_active_sequencer(consts::DEFAULT_CHAIN_ID, &CHARLIE)); + assert_ok!(SequencerStaking::provide_sequencer_stake( + RuntimeOrigin::root(), + consts::DEFAULT_CHAIN_ID, + MINIMUM_STAKE, + None, + StakeAction::StakeAndJoinActiveSet, + CHARLIE + )); + assert_eq!( + SequencerStake::::get(&(CHARLIE, consts::DEFAULT_CHAIN_ID)), + MINIMUM_STAKE + ); + assert!(SequencerStaking::is_active_sequencer(consts::DEFAULT_CHAIN_ID, &CHARLIE)); + assert_eq!(TokensOf::::total_balance(&CHARLIE), TOKENS_ENDOWED); + assert_eq!(TokensOf::::reserved_balance(&CHARLIE), MINIMUM_STAKE); + }); +} + +#[test] +#[serial] +fn test_provide_sequencer_stake_works_and_does_not_activate_due_to_insufficient_stake() { + set_default_mocks!(); + ExtBuilder::new().build().execute_with(|| { + forward_to_block::(10); + + let new_sequencer_active_mock = MockRolldownProviderApi::new_sequencer_active_context(); + new_sequencer_active_mock.expect().times(0).return_const(()); + + assert_eq!(TokensOf::::total_balance(&CHARLIE), TOKENS_ENDOWED); + assert_eq!(TokensOf::::reserved_balance(&CHARLIE), 0); + assert_eq!(SequencerStake::::get(&(CHARLIE, consts::DEFAULT_CHAIN_ID)), 0); + assert!(!SequencerStaking::is_active_sequencer(consts::DEFAULT_CHAIN_ID, &CHARLIE)); + + assert_err!( + SequencerStaking::provide_sequencer_stake( + RuntimeOrigin::root(), + consts::DEFAULT_CHAIN_ID, + MINIMUM_STAKE - 1, + None, + StakeAction::StakeAndJoinActiveSet, + CHARLIE + ), + Error::::NotEnoughSequencerStake + ); + }); +} + +#[test] +#[serial] +fn test_provide_sequencer_stake_works_and_does_not_activate_due_to_max_seq_bound() { + set_default_mocks!(); + ExtBuilder::new().build().execute_with(|| { + forward_to_block::(10); + + let new_sequencer_active_mock = MockRolldownProviderApi::new_sequencer_active_context(); + new_sequencer_active_mock.expect().times(0).return_const(()); + + SequencerStaking::set_active_sequencers( + (20u64..31u64).map(|i| (consts::DEFAULT_CHAIN_ID, i)), + ) + .unwrap(); + + assert_eq!(TokensOf::::total_balance(&CHARLIE), TOKENS_ENDOWED); + assert_eq!(TokensOf::::reserved_balance(&CHARLIE), 0); + assert_eq!(SequencerStake::::get(&(CHARLIE, consts::DEFAULT_CHAIN_ID)), 0); + assert!(!SequencerStaking::is_active_sequencer(consts::DEFAULT_CHAIN_ID, &CHARLIE)); + assert_err!( + SequencerStaking::provide_sequencer_stake( + RuntimeOrigin::root(), + consts::DEFAULT_CHAIN_ID, + MINIMUM_STAKE, + None, + StakeAction::StakeAndJoinActiveSet, + CHARLIE + ), + Error::::MaxSequencersLimitReached + ); + }); +} + +#[test] +#[serial] +fn test_leave_active_sequencer_set() { + set_default_mocks!(); + ExtBuilder::new().build().execute_with(|| { + forward_to_block::(10); + + let handle_sequencer_deactivations_mock = + MockRolldownProviderApi::handle_sequencer_deactivations_context(); + handle_sequencer_deactivations_mock.expect().times(1).return_const(()); + + assert_err!( + SequencerStaking::leave_active_sequencers( + RuntimeOrigin::signed(CHARLIE), + consts::DEFAULT_CHAIN_ID + ), + Error::::SequencerIsNotInActiveSet + ); + + assert!(SequencerStaking::is_active_sequencer(consts::DEFAULT_CHAIN_ID, &ALICE)); + assert_ok!(SequencerStaking::leave_active_sequencers( + RuntimeOrigin::signed(ALICE), + consts::DEFAULT_CHAIN_ID + )); + assert!(!SequencerStaking::is_active_sequencer(consts::DEFAULT_CHAIN_ID, &ALICE)); + }); +} + +#[test] +#[serial] +fn test_rejoin_active_sequencer_works() { + set_default_mocks!(); + ExtBuilder::new().build().execute_with(|| { + forward_to_block::(10); + + assert_err!( + SequencerStaking::rejoin_active_sequencers( + RuntimeOrigin::root(), + consts::DEFAULT_CHAIN_ID, + ALICE + ), + Error::::SequencerAlreadyInActiveSet + ); + + assert_ok!(SequencerStaking::provide_sequencer_stake( + RuntimeOrigin::root(), + consts::DEFAULT_CHAIN_ID, + MINIMUM_STAKE - 1, + None, + StakeAction::StakeOnly, + CHARLIE + )); + assert_err!( + SequencerStaking::rejoin_active_sequencers( + RuntimeOrigin::root(), + consts::DEFAULT_CHAIN_ID, + CHARLIE + ), + Error::::NotEnoughSequencerStake + ); + + SequencerStaking::set_active_sequencers( + (20u64..31u64).map(|i| (consts::DEFAULT_CHAIN_ID, i)), + ) + .unwrap(); + + assert_ok!(SequencerStaking::provide_sequencer_stake( + RuntimeOrigin::root(), + consts::DEFAULT_CHAIN_ID, + 1, + None, + StakeAction::StakeOnly, + CHARLIE + )); + + SequencerStaking::set_active_sequencers( + (20u64..30u64).map(|i| (consts::DEFAULT_CHAIN_ID, i)), + ) + .unwrap(); + + let new_sequencer_active_mock = MockRolldownProviderApi::new_sequencer_active_context(); + new_sequencer_active_mock.expect().times(1).return_const(()); + assert!(!SequencerStaking::is_active_sequencer(consts::DEFAULT_CHAIN_ID, &CHARLIE)); + assert_ok!(SequencerStaking::rejoin_active_sequencers( + RuntimeOrigin::root(), + consts::DEFAULT_CHAIN_ID, + CHARLIE + )); + assert!(SequencerStaking::is_active_sequencer(consts::DEFAULT_CHAIN_ID, &CHARLIE)); + }); +} + +#[test] +#[serial] +fn test_can_not_join_set_if_full() { + set_default_mocks!(); + ExtBuilder::new().build().execute_with(|| { + forward_to_block::(10); + ActiveSequencers::::kill(); + let seq_limit = <::MaxSequencers as Get>::get() as AccountId; + let new_sequencer_active_mock = MockRolldownProviderApi::new_sequencer_active_context(); + new_sequencer_active_mock.expect().times(seq_limit as usize).return_const(()); + + for seq in 0u64..seq_limit { + Tokens::mint(RuntimeOrigin::root(), NATIVE_TOKEN_ID, seq, MINIMUM_STAKE).unwrap(); + assert_ok!(SequencerStaking::provide_sequencer_stake( + RuntimeOrigin::root(), + consts::DEFAULT_CHAIN_ID, + MINIMUM_STAKE, + None, + StakeAction::StakeAndJoinActiveSet, + seq + )); + assert!(SequencerStaking::is_active_sequencer(consts::DEFAULT_CHAIN_ID, &seq)); + } + + Tokens::mint(RuntimeOrigin::root(), NATIVE_TOKEN_ID, seq_limit, MINIMUM_STAKE).unwrap(); + assert_ok!(SequencerStaking::provide_sequencer_stake( + RuntimeOrigin::root(), + consts::DEFAULT_CHAIN_ID, + MINIMUM_STAKE, + None, + StakeAction::StakeOnly, + seq_limit + )); + assert!(!SequencerStaking::is_active_sequencer(consts::DEFAULT_CHAIN_ID, &seq_limit)); + assert_err!( + SequencerStaking::rejoin_active_sequencers( + RuntimeOrigin::root(), + consts::DEFAULT_CHAIN_ID, + seq_limit + ), + Error::::MaxSequencersLimitReached + ); + assert!(!SequencerStaking::is_active_sequencer(consts::DEFAULT_CHAIN_ID, &seq_limit)); + }); +} + +#[test] +#[serial] +fn test_provide_stake_fails_on_sequencers_limit_reached() { + set_default_mocks!(); + ExtBuilder::new().build().execute_with(|| { + forward_to_block::(10); + + assert_err!( + SequencerStaking::rejoin_active_sequencers( + RuntimeOrigin::root(), + consts::DEFAULT_CHAIN_ID, + ALICE + ), + Error::::SequencerAlreadyInActiveSet + ); + + assert_ok!(SequencerStaking::provide_sequencer_stake( + RuntimeOrigin::root(), + consts::DEFAULT_CHAIN_ID, + MINIMUM_STAKE - 1, + None, + StakeAction::StakeOnly, + CHARLIE + )); + assert_err!( + SequencerStaking::rejoin_active_sequencers( + RuntimeOrigin::root(), + consts::DEFAULT_CHAIN_ID, + CHARLIE + ), + Error::::NotEnoughSequencerStake + ); + + SequencerStaking::set_active_sequencers( + (20u64..31u64).map(|i| (consts::DEFAULT_CHAIN_ID, i)), + ) + .unwrap(); + + assert_err!( + SequencerStaking::provide_sequencer_stake( + RuntimeOrigin::root(), + consts::DEFAULT_CHAIN_ID, + 1, + None, + StakeAction::StakeAndJoinActiveSet, + CHARLIE + ), + Error::::MaxSequencersLimitReached + ); + + assert_ok!(SequencerStaking::provide_sequencer_stake( + RuntimeOrigin::root(), + consts::DEFAULT_CHAIN_ID, + 1, + None, + StakeAction::StakeOnly, + CHARLIE + )); + + SequencerStaking::set_active_sequencers( + (20u64..30u64).map(|i| (consts::DEFAULT_CHAIN_ID, i)), + ) + .unwrap(); + + let new_sequencer_active_mock = MockRolldownProviderApi::new_sequencer_active_context(); + new_sequencer_active_mock.expect().times(1).return_const(()); + assert!(!SequencerStaking::is_active_sequencer(consts::DEFAULT_CHAIN_ID, &CHARLIE)); + assert_ok!(SequencerStaking::rejoin_active_sequencers( + RuntimeOrigin::root(), + consts::DEFAULT_CHAIN_ID, + CHARLIE + )); + assert!(SequencerStaking::is_active_sequencer(consts::DEFAULT_CHAIN_ID, &CHARLIE)); + }); +} + +#[test] +#[serial] +fn test_sequencer_unstaking() { + set_default_mocks!(); + ExtBuilder::new().build().execute_with(|| { + forward_to_block::(10); + + assert_err!( + SequencerStaking::unstake(RuntimeOrigin::signed(ALICE), consts::DEFAULT_CHAIN_ID), + Error::::CantUnstakeWhileInActiveSet + ); + + let sequencer_unstaking_mock = MockRolldownProviderApi::sequencer_unstaking_context(); + sequencer_unstaking_mock.expect().times(1).return_const(Ok(())); + let handle_sequencer_deactivations_mock = + MockRolldownProviderApi::handle_sequencer_deactivations_context(); + handle_sequencer_deactivations_mock.expect().times(1).return_const(()); + assert_ok!(SequencerStaking::leave_active_sequencers( + RuntimeOrigin::signed(ALICE), + consts::DEFAULT_CHAIN_ID + )); + + assert_eq!(TokensOf::::total_balance(&ALICE), TOKENS_ENDOWED); + assert_eq!(TokensOf::::reserved_balance(&ALICE), MINIMUM_STAKE); + assert_eq!(SequencerStake::::get(&(ALICE, consts::DEFAULT_CHAIN_ID)), MINIMUM_STAKE); + assert_ok!(SequencerStaking::unstake( + RuntimeOrigin::signed(ALICE), + consts::DEFAULT_CHAIN_ID + )); + assert_eq!(SequencerStake::::get(&(ALICE, consts::DEFAULT_CHAIN_ID)), 0); + assert_eq!(TokensOf::::total_balance(&ALICE), TOKENS_ENDOWED); + assert_eq!(TokensOf::::reserved_balance(&ALICE), 0); + }); +} + +#[test] +#[serial] +fn test_set_sequencer_configuration() { + set_default_mocks!(); + ExtBuilder::new().build().execute_with(|| { + forward_to_block::(10); + + let new_sequencer_active_mock = MockRolldownProviderApi::new_sequencer_active_context(); + new_sequencer_active_mock.expect().times(1).return_const(()); + + assert_ok!(SequencerStaking::provide_sequencer_stake( + RuntimeOrigin::root(), + consts::DEFAULT_CHAIN_ID, + MINIMUM_STAKE + 1, + None, + StakeAction::StakeAndJoinActiveSet, + CHARLIE + )); + + let handle_sequencer_deactivations_mock = + MockRolldownProviderApi::handle_sequencer_deactivations_context(); + handle_sequencer_deactivations_mock.expect().times(1).return_const(()); + + assert_ok!(SequencerStaking::set_sequencer_configuration( + RuntimeOrigin::root(), + consts::DEFAULT_CHAIN_ID, + MINIMUM_STAKE + 1, + SLASH_AMOUNT - 1 + )); + assert_eq!( + ActiveSequencers::::get().get(&consts::DEFAULT_CHAIN_ID).unwrap().len(), + 1 + ); + assert_eq!( + ActiveSequencers::::get() + .get(&consts::DEFAULT_CHAIN_ID) + .unwrap() + .iter() + .next(), + Some(&CHARLIE) + ); + assert_eq!(MinimalStakeAmount::::get(), MINIMUM_STAKE + 1); + assert_eq!(SlashFineAmount::::get(), SLASH_AMOUNT - 1); + }); +} + +#[test] +#[serial] +fn test_slash_sequencer() { + set_default_mocks!(); + ExtBuilder::new().build().execute_with(|| { + forward_to_block::(10); + + let handle_sequencer_deactivations_mock = + MockRolldownProviderApi::handle_sequencer_deactivations_context(); + handle_sequencer_deactivations_mock.expect().times(1).return_const(()); + + assert_eq!(TokensOf::::total_balance(&ALICE), TOKENS_ENDOWED); + assert_eq!(TokensOf::::reserved_balance(&ALICE), MINIMUM_STAKE); + assert_eq!(TokensOf::::total_balance(&EVE), 0); + assert_eq!(TokensOf::::reserved_balance(&EVE), 0); + assert_eq!(TokensOf::::total_issuance(), TOKENS_ENDOWED * 4); + + assert_ok!(SequencerStaking::slash_sequencer(consts::DEFAULT_CHAIN_ID, &ALICE, Some(&EVE))); + + assert_eq!(TokensOf::::total_balance(&ALICE), TOKENS_ENDOWED - SLASH_AMOUNT); + assert_eq!(TokensOf::::reserved_balance(&ALICE), MINIMUM_STAKE - SLASH_AMOUNT); + assert_eq!( + TokensOf::::total_balance(&EVE), + 0 + CancellerRewardPercentage::get() * SLASH_AMOUNT + ); + assert_eq!(TokensOf::::reserved_balance(&EVE), 0); + assert_eq!( + TokensOf::::total_issuance(), + TOKENS_ENDOWED * 4 - (SLASH_AMOUNT - CancellerRewardPercentage::get() * SLASH_AMOUNT) + ); + + let handle_sequencer_deactivations_mock = + MockRolldownProviderApi::handle_sequencer_deactivations_context(); + handle_sequencer_deactivations_mock.expect().times(1).return_const(()); + + let total_issuance_0 = TokensOf::::total_issuance(); + assert_eq!(TokensOf::::total_balance(&BOB), TOKENS_ENDOWED); + assert_eq!(TokensOf::::reserved_balance(&BOB), MINIMUM_STAKE); + assert_ok!(SequencerStaking::slash_sequencer(consts::DEFAULT_CHAIN_ID, &BOB, None)); + + assert_eq!(TokensOf::::total_balance(&BOB), TOKENS_ENDOWED - SLASH_AMOUNT); + assert_eq!(TokensOf::::reserved_balance(&BOB), MINIMUM_STAKE - SLASH_AMOUNT); + assert_eq!( + >::get((BOB, DEFAULT_CHAIN_ID)), + MINIMUM_STAKE - SLASH_AMOUNT + ); + assert_eq!(total_issuance_0 - TokensOf::::total_issuance(), SLASH_AMOUNT); + }); +} + +#[test] +#[serial] +fn test_slash_sequencer_when_stake_less_than_repatriated_amount() { + set_default_mocks!(); + ExtBuilder::new().build().execute_with(|| { + forward_to_block::(10); + + let amount = 10; + assert_ok!(SequencerStaking::provide_sequencer_stake( + RuntimeOrigin::root(), + consts::DEFAULT_CHAIN_ID, + amount, + None, + StakeAction::StakeOnly, + CHARLIE + )); + + assert_eq!(TokensOf::::total_balance(&CHARLIE), TOKENS_ENDOWED); + assert_eq!(TokensOf::::reserved_balance(&CHARLIE), amount); + assert_eq!(TokensOf::::total_balance(&EVE), 0); + assert_eq!(TokensOf::::reserved_balance(&EVE), 0); + assert_eq!(TokensOf::::total_issuance(), TOKENS_ENDOWED * 4); + + assert_ok!(SequencerStaking::slash_sequencer( + consts::DEFAULT_CHAIN_ID, + &CHARLIE, + Some(&EVE) + )); + + let repatriated_amount = 10; + let amount_slashed = 10; + assert_eq!(TokensOf::::total_balance(&CHARLIE), TOKENS_ENDOWED - amount_slashed); + assert_eq!(TokensOf::::reserved_balance(&CHARLIE), amount - amount_slashed); + assert_eq!(TokensOf::::total_balance(&EVE), 0 + repatriated_amount); + assert_eq!(TokensOf::::reserved_balance(&EVE), 0); + assert_eq!( + TokensOf::::total_issuance(), + TOKENS_ENDOWED * 4 - (amount_slashed - repatriated_amount) + ); + + let amount = 10; + assert_ok!(SequencerStaking::provide_sequencer_stake( + RuntimeOrigin::root(), + consts::DEFAULT_CHAIN_ID, + amount, + None, + StakeAction::StakeOnly, + DAVE + )); + + let total_issuance_0 = TokensOf::::total_issuance(); + assert_eq!(TokensOf::::total_balance(&DAVE), TOKENS_ENDOWED); + assert_eq!(TokensOf::::reserved_balance(&DAVE), amount); + + assert_ok!(SequencerStaking::slash_sequencer(consts::DEFAULT_CHAIN_ID, &DAVE, None)); + + let repatriated_amount = 0; + let amount_slashed = 10; + assert_eq!(TokensOf::::total_balance(&DAVE), TOKENS_ENDOWED - amount_slashed); + assert_eq!(TokensOf::::reserved_balance(&DAVE), amount - amount_slashed); + assert_eq!(total_issuance_0 - TokensOf::::total_issuance(), amount_slashed); + }); +} + +#[test] +#[serial] +fn test_slash_sequencer_when_stake_less_than_stake_but_greater_than_repatriated_amount() { + set_default_mocks!(); + ExtBuilder::new().build().execute_with(|| { + forward_to_block::(10); + + let amount = 50; + assert_ok!(SequencerStaking::provide_sequencer_stake( + RuntimeOrigin::root(), + consts::DEFAULT_CHAIN_ID, + amount, + None, + StakeAction::StakeOnly, + CHARLIE + )); + + assert_eq!(TokensOf::::total_balance(&CHARLIE), TOKENS_ENDOWED); + assert_eq!(TokensOf::::reserved_balance(&CHARLIE), amount); + assert_eq!(TokensOf::::total_balance(&EVE), 0); + assert_eq!(TokensOf::::reserved_balance(&EVE), 0); + assert_eq!(TokensOf::::total_issuance(), TOKENS_ENDOWED * 4); + + assert_ok!(SequencerStaking::slash_sequencer( + consts::DEFAULT_CHAIN_ID, + &CHARLIE, + Some(&EVE) + )); + + let repatriated_amount = 20; + let amount_slashed = 50; + assert_eq!(TokensOf::::total_balance(&CHARLIE), TOKENS_ENDOWED - amount_slashed); + assert_eq!(TokensOf::::reserved_balance(&CHARLIE), amount - amount_slashed); + assert_eq!(TokensOf::::total_balance(&EVE), 0 + repatriated_amount); + assert_eq!(TokensOf::::reserved_balance(&EVE), 0); + assert_eq!( + TokensOf::::total_issuance(), + TOKENS_ENDOWED * 4 - (amount_slashed - repatriated_amount) + ); + + let amount = 50; + assert_ok!(SequencerStaking::provide_sequencer_stake( + RuntimeOrigin::root(), + consts::DEFAULT_CHAIN_ID, + amount, + None, + StakeAction::StakeOnly, + DAVE + )); + + let total_issuance_0 = TokensOf::::total_issuance(); + assert_eq!(TokensOf::::total_balance(&DAVE), TOKENS_ENDOWED); + assert_eq!(TokensOf::::reserved_balance(&DAVE), amount); + + assert_ok!(SequencerStaking::slash_sequencer(consts::DEFAULT_CHAIN_ID, &DAVE, None)); + + let repatriated_amount = 0; + let amount_slashed = 50; + assert_eq!(TokensOf::::total_balance(&DAVE), TOKENS_ENDOWED - amount_slashed); + assert_eq!(TokensOf::::reserved_balance(&DAVE), amount - amount_slashed); + assert_eq!(total_issuance_0 - TokensOf::::total_issuance(), amount_slashed); + }); +} + +#[test] +#[serial] +fn test_maybe_remove_sequencers_from_active_set_works() { + set_default_mocks!(); + ExtBuilder::new().build().execute_with(|| { + forward_to_block::(10); + + SequencerStaking::set_active_sequencers( + [(consts::DEFAULT_CHAIN_ID, ALICE), (consts::DEFAULT_CHAIN_ID, BOB)] + .iter() + .cloned(), + ) + .unwrap(); + + let handle_sequencer_deactivations_mock = + MockRolldownProviderApi::handle_sequencer_deactivations_context(); + handle_sequencer_deactivations_mock + .expect() + .with(eq(consts::DEFAULT_CHAIN_ID), eq(vec![BOB])) + .times(1) + .return_const(()); + + assert_ok!(SequencerStaking::slash_sequencer(consts::DEFAULT_CHAIN_ID, &BOB, None)); + + SequencerStaking::maybe_remove_sequencer_from_active_set(consts::DEFAULT_CHAIN_ID, ALICE); + SequencerStaking::maybe_remove_sequencer_from_active_set(consts::DEFAULT_CHAIN_ID, BOB); + SequencerStaking::maybe_remove_sequencer_from_active_set(consts::DEFAULT_CHAIN_ID, CHARLIE); + }); +} + +#[test] +#[serial] +fn test_remove_sequencers_works_correctly() { + set_default_mocks!(); + ExtBuilder::new().build().execute_with(|| { + forward_to_block::(10); + + let handle_sequencer_deactivations_mock = + MockRolldownProviderApi::handle_sequencer_deactivations_context(); + handle_sequencer_deactivations_mock.expect().return_const(()); + + // 1. + SelectedSequencer::::mutate(|set| set.insert(consts::DEFAULT_CHAIN_ID, 4)); + NextSequencerIndex::::mutate(|ids| ids.insert(consts::DEFAULT_CHAIN_ID, 6)); + + SequencerStaking::set_active_sequencers( + (0u64..11u64).map(|i| (consts::DEFAULT_CHAIN_ID, i)), + ) + .unwrap(); + + SequencerStaking::remove_sequencers_from_active_set( + consts::DEFAULT_CHAIN_ID, + [1, 4, 5, 6, 8, 11].iter().cloned().collect(), + ); + + assert_eq!(SelectedSequencer::::get().get(&consts::DEFAULT_CHAIN_ID), None); + assert_eq!(NextSequencerIndex::::get().get(&consts::DEFAULT_CHAIN_ID), Some(&3u32)); + + assert_eq!( + ActiveSequencers::::get() + .get(&consts::DEFAULT_CHAIN_ID) + .unwrap() + .clone() + .into_inner(), + [0, 2, 3, 7, 9, 10].iter().cloned().collect::>() + ); + + // 2. + SelectedSequencer::::mutate(|set| set.insert(consts::DEFAULT_CHAIN_ID, 4)); + NextSequencerIndex::::mutate(|ids| ids.insert(consts::DEFAULT_CHAIN_ID, 4)); + SequencerStaking::set_active_sequencers( + (0u64..11u64).map(|i| (consts::DEFAULT_CHAIN_ID, i)), + ) + .unwrap(); + + SequencerStaking::remove_sequencers_from_active_set( + consts::DEFAULT_CHAIN_ID, + std::iter::once(4).collect(), + ); + + assert_eq!(SelectedSequencer::::get().get(&consts::DEFAULT_CHAIN_ID), None); + assert_eq!(NextSequencerIndex::::get().get(&consts::DEFAULT_CHAIN_ID), Some(&4u32)); + assert_eq!( + ActiveSequencers::::get() + .get(&consts::DEFAULT_CHAIN_ID) + .unwrap() + .clone() + .into_inner(), + [0, 1, 2, 3, 5, 6, 7, 8, 9, 10].iter().cloned().collect::>() + ); + + // 3. + SelectedSequencer::::mutate(|set| set.insert(consts::DEFAULT_CHAIN_ID, 4)); + NextSequencerIndex::::mutate(|ids| ids.insert(consts::DEFAULT_CHAIN_ID, 6)); + SequencerStaking::set_active_sequencers( + (0u64..11u64).map(|i| (consts::DEFAULT_CHAIN_ID, i)), + ) + .unwrap(); + + SequencerStaking::remove_sequencers_from_active_set( + consts::DEFAULT_CHAIN_ID, + [2, 3, 5, 8, 11].iter().cloned().collect(), + ); + + assert_eq!(SelectedSequencer::::get().get(&consts::DEFAULT_CHAIN_ID), Some(&4u64)); + assert_eq!(NextSequencerIndex::::get().get(&consts::DEFAULT_CHAIN_ID), Some(&3u32)); + assert_eq!( + ActiveSequencers::::get() + .get(&consts::DEFAULT_CHAIN_ID) + .unwrap() + .clone() + .into_inner(), + [0, 1, 4, 6, 7, 9, 10].iter().cloned().collect::>() + ); + }); +} + +#[test] +#[serial] +fn test_on_finalize_works_correctly() { + set_default_mocks!(); + ExtBuilder::new().build().execute_with(|| { + forward_to_block::(10); + + // 1. + SelectedSequencer::::mutate(|set| set.insert(consts::DEFAULT_CHAIN_ID, 5)); + NextSequencerIndex::::mutate(|ids| ids.insert(consts::DEFAULT_CHAIN_ID, 6)); + SequencerStaking::set_active_sequencers( + (0u64..11u64).map(|i| (consts::DEFAULT_CHAIN_ID, i)), + ) + .unwrap(); + + SequencerStaking::on_finalize(10); + + assert_eq!(SelectedSequencer::::get().get(&consts::DEFAULT_CHAIN_ID), Some(&6u64)); + assert_eq!(NextSequencerIndex::::get().get(&consts::DEFAULT_CHAIN_ID), Some(&7u32)); + + // 2. + SelectedSequencer::::mutate(|set| set.insert(consts::DEFAULT_CHAIN_ID, 5)); + NextSequencerIndex::::mutate(|ids| ids.insert(consts::DEFAULT_CHAIN_ID, 12)); + SequencerStaking::set_active_sequencers( + (0u64..11u64).map(|i| (consts::DEFAULT_CHAIN_ID, i)), + ) + .unwrap(); + + SequencerStaking::on_finalize(10); + + assert_eq!(SelectedSequencer::::get().get(&consts::DEFAULT_CHAIN_ID), Some(&0u64)); + assert_eq!(NextSequencerIndex::::get().get(&consts::DEFAULT_CHAIN_ID), Some(&1u32)); + + // // 3. + SelectedSequencer::::mutate(|set| set.insert(consts::DEFAULT_CHAIN_ID, 5)); + NextSequencerIndex::::mutate(|ids| ids.insert(consts::DEFAULT_CHAIN_ID, 13)); + SequencerStaking::set_active_sequencers( + (0u64..11u64).map(|i| (consts::DEFAULT_CHAIN_ID, i)), + ) + .unwrap(); + + SequencerStaking::on_finalize(10); + + assert_eq!(SelectedSequencer::::get().get(&consts::DEFAULT_CHAIN_ID), Some(&0u64)); + assert_eq!(NextSequencerIndex::::get().get(&consts::DEFAULT_CHAIN_ID), Some(&1u32)); + + // 4. + SelectedSequencer::::mutate(|set| set.insert(consts::DEFAULT_CHAIN_ID, 5)); + NextSequencerIndex::::mutate(|ids| ids.insert(consts::DEFAULT_CHAIN_ID, 13)); + SequencerStaking::set_active_sequencers(Vec::new()).unwrap(); + + SequencerStaking::on_finalize(10); + + assert_eq!(SelectedSequencer::::get().get(&consts::DEFAULT_CHAIN_ID), None); + assert_eq!(NextSequencerIndex::::get().get(&consts::DEFAULT_CHAIN_ID), None); + }); +} + +#[test] +#[serial] +fn test_provide_sequencer_stake_sets_updater_account_to_same_address_as_sequencer_by_default() { + set_default_mocks!(); + ExtBuilder::new().build().execute_with(|| { + forward_to_block::(10); + + let new_sequencer_active_mock = MockRolldownProviderApi::new_sequencer_active_context(); + new_sequencer_active_mock.expect().times(1).return_const(()); + + SequencerStaking::set_active_sequencers(Vec::new()).unwrap(); + + assert_ok!(SequencerStaking::provide_sequencer_stake( + RuntimeOrigin::root(), + consts::DEFAULT_CHAIN_ID, + MINIMUM_STAKE, + None, + StakeAction::StakeAndJoinActiveSet, + CHARLIE + )); + + forward_to_block::(100); + + assert!( + >::is_selected_sequencer( + consts::DEFAULT_CHAIN_ID, + &consts::CHARLIE + ) + ); + + assert_eq!( + Some(CHARLIE), + >::selected_sequencer( + consts::DEFAULT_CHAIN_ID + ), + ); + }); +} + +#[test] +#[serial] +fn test_sequencer_can_set_alias_address() { + set_default_mocks!(); + ExtBuilder::new().build().execute_with(|| { + forward_to_block::(10); + + let new_sequencer_active_mock = MockRolldownProviderApi::new_sequencer_active_context(); + new_sequencer_active_mock.expect().times(1).return_const(()); + SequencerStaking::set_active_sequencers(Vec::new()).unwrap(); + assert_ok!(SequencerStaking::provide_sequencer_stake( + RuntimeOrigin::root(), + consts::DEFAULT_CHAIN_ID, + MINIMUM_STAKE, + Some(EVE), + StakeAction::StakeAndJoinActiveSet, + CHARLIE + )); + + assert!( + >::is_active_sequencer_alias( + consts::DEFAULT_CHAIN_ID, + &CHARLIE, + &EVE + ) + ); + }); +} + +#[test] +#[serial] +fn test_sequencer_can_update_alias_address() { + set_default_mocks!(); + ExtBuilder::new().build().execute_with(|| { + forward_to_block::(10); + + let new_sequencer_active_mock = MockRolldownProviderApi::new_sequencer_active_context(); + new_sequencer_active_mock.expect().times(1).return_const(()); + SequencerStaking::set_active_sequencers(Vec::new()).unwrap(); + assert_ok!(SequencerStaking::provide_sequencer_stake( + RuntimeOrigin::root(), + consts::DEFAULT_CHAIN_ID, + MINIMUM_STAKE, + None, + StakeAction::StakeAndJoinActiveSet, + CHARLIE + )); + + SequencerStaking::set_updater_account_for_sequencer( + RuntimeOrigin::signed(CHARLIE), + consts::DEFAULT_CHAIN_ID, + Some(consts::EVE), + ) + .unwrap(); + }); +} + +#[test] +#[serial] +fn test_sequencer_can_not_set_another_sequencer_address_as_alias() { + set_default_mocks!(); + ExtBuilder::new().build().execute_with(|| { + forward_to_block::(10); + + let new_sequencer_active_mock = MockRolldownProviderApi::new_sequencer_active_context(); + new_sequencer_active_mock.expect().return_const(()); + SequencerStaking::set_active_sequencers(Vec::new()).unwrap(); + + assert_ok!(SequencerStaking::provide_sequencer_stake( + RuntimeOrigin::root(), + consts::DEFAULT_CHAIN_ID, + MINIMUM_STAKE, + None, + StakeAction::StakeAndJoinActiveSet, + ALICE + )); + + assert_err!( + SequencerStaking::provide_sequencer_stake( + RuntimeOrigin::root(), + consts::DEFAULT_CHAIN_ID, + MINIMUM_STAKE, + Some(ALICE), + StakeAction::StakeAndJoinActiveSet, + CHARLIE + ), + Error::::AliasAccountIsActiveSequencer + ); + + assert_ok!(SequencerStaking::provide_sequencer_stake( + RuntimeOrigin::root(), + consts::DEFAULT_CHAIN_ID, + MINIMUM_STAKE, + None, + StakeAction::StakeAndJoinActiveSet, + CHARLIE + )); + + assert_err!( + SequencerStaking::set_updater_account_for_sequencer( + RuntimeOrigin::signed(CHARLIE), + consts::DEFAULT_CHAIN_ID, + Some(ALICE), + ), + Error::::AliasAccountIsActiveSequencer + ); + }); +} + +#[test] +#[serial] +fn test_sequencer_can_not_set_use_already_used_alias() { + set_default_mocks!(); + ExtBuilder::new().build().execute_with(|| { + forward_to_block::(10); + + let new_sequencer_active_mock = MockRolldownProviderApi::new_sequencer_active_context(); + new_sequencer_active_mock.expect().return_const(()); + SequencerStaking::set_active_sequencers(Vec::new()).unwrap(); + + assert_ok!(SequencerStaking::provide_sequencer_stake( + RuntimeOrigin::root(), + consts::DEFAULT_CHAIN_ID, + MINIMUM_STAKE, + Some(EVE), + StakeAction::StakeAndJoinActiveSet, + ALICE + )); + + assert_err!( + SequencerStaking::provide_sequencer_stake( + RuntimeOrigin::root(), + consts::DEFAULT_CHAIN_ID, + MINIMUM_STAKE, + Some(EVE), + StakeAction::StakeAndJoinActiveSet, + CHARLIE + ), + Error::::AddressInUse + ); + + assert_ok!(SequencerStaking::provide_sequencer_stake( + RuntimeOrigin::root(), + consts::DEFAULT_CHAIN_ID, + MINIMUM_STAKE, + None, + StakeAction::StakeAndJoinActiveSet, + CHARLIE + )); + + assert_err!( + SequencerStaking::set_updater_account_for_sequencer( + RuntimeOrigin::signed(CHARLIE), + consts::DEFAULT_CHAIN_ID, + Some(EVE), + ), + Error::::AddressInUse + ); + }); +} + +#[test] +#[serial] +fn test_sequencer_cannot_join_if_its_account_is_used_as_sequencer_alias() { + set_default_mocks!(); + ExtBuilder::new().build().execute_with(|| { + forward_to_block::(10); + + let new_sequencer_active_mock = MockRolldownProviderApi::new_sequencer_active_context(); + new_sequencer_active_mock.expect().return_const(()); + + SequencerStaking::set_active_sequencers(Vec::new()).unwrap(); + + assert_ok!(SequencerStaking::provide_sequencer_stake( + RuntimeOrigin::root(), + consts::DEFAULT_CHAIN_ID, + MINIMUM_STAKE, + Some(consts::ALICE), + StakeAction::StakeAndJoinActiveSet, + CHARLIE + )); + + assert_err!( + SequencerStaking::provide_sequencer_stake( + RuntimeOrigin::root(), + consts::DEFAULT_CHAIN_ID, + MINIMUM_STAKE, + None, + StakeAction::StakeAndJoinActiveSet, + ALICE + ), + Error::::SequencerAccountIsActiveSequencerAlias + ); + }); +} + +#[test] +#[serial] +fn test_sequencer_can_remove_alias_so_another_sequencer_can_use_it_afterwards() { + set_default_mocks!(); + ExtBuilder::new().build().execute_with(|| { + forward_to_block::(10); + + let new_sequencer_active_mock = MockRolldownProviderApi::new_sequencer_active_context(); + new_sequencer_active_mock.expect().return_const(()); + + let handle_sequencer_deactivations = + MockRolldownProviderApi::handle_sequencer_deactivations_context(); + handle_sequencer_deactivations.expect().return_const(()); + SequencerStaking::set_active_sequencers(Vec::new()).unwrap(); + + assert_ok!(SequencerStaking::provide_sequencer_stake( + RuntimeOrigin::root(), + consts::DEFAULT_CHAIN_ID, + MINIMUM_STAKE, + Some(EVE), + StakeAction::StakeAndJoinActiveSet, + ALICE + )); + + assert_ok!(SequencerStaking::provide_sequencer_stake( + RuntimeOrigin::root(), + consts::DEFAULT_CHAIN_ID, + MINIMUM_STAKE, + None, + StakeAction::StakeAndJoinActiveSet, + CHARLIE + )); + + assert_err!( + SequencerStaking::set_updater_account_for_sequencer( + RuntimeOrigin::signed(CHARLIE), + consts::DEFAULT_CHAIN_ID, + Some(EVE), + ), + Error::::AddressInUse + ); + + assert_ok!(SequencerStaking::set_updater_account_for_sequencer( + RuntimeOrigin::signed(ALICE), + consts::DEFAULT_CHAIN_ID, + None, + )); + + assert_ok!(SequencerStaking::set_updater_account_for_sequencer( + RuntimeOrigin::signed(CHARLIE), + consts::DEFAULT_CHAIN_ID, + Some(EVE), + )); + }); +} + +#[test] +#[serial] +fn payout_distribution_to_sequencers() { + set_default_mocks!(); + ExtBuilder::new().build().execute_with(|| { + forward_to_block::(10); + <::Issuance>::compute_issuance(0); + let total = <::Issuance>::get_sequencer_issuance(0).unwrap(); + + assert_eq!(10_0000, total); + assert_eq!(10_0000, TokensOf::::total_balance(&SequencerIssuanceVault::get())); + + // these are called from rolldown pallet to notify update was created by sequencer + // alice should have 4x more rewards than bob in first round + >::note_update_author(&ALICE); + >::note_update_author(&ALICE); + >::note_update_author(&ALICE); + >::note_update_author(&ALICE); + >::note_update_author(&BOB); + + // simulate new session hook in staking pallet + // we have a delay of 2 rounds + // previous round is 0, current is 1, nothing should be rewarded + >::pay_sequencers(1); + // previous round is 1, current is 2, rewards for first round should be paid out + >::pay_sequencers(2); + + assert_eq!(80_000_u128, RoundSequencerRewardInfo::::get(ALICE, 0).unwrap()); + assert_eq!(20_000_u128, RoundSequencerRewardInfo::::get(BOB, 0).unwrap()); + + let mut sum = 0; + for id in [ALICE, BOB] { + let balance = TokensOf::::total_balance(&id); + let reward = RoundSequencerRewardInfo::::get(id, 0).unwrap(); + assert_ok!(SequencerStaking::payout_sequencer_rewards( + RuntimeOrigin::signed(id), + id, + None + )); + assert_eq!(balance + reward, TokensOf::::total_balance(&id)); + sum += reward; + } + + assert_eq!(sum, total); + assert_eq!(2, CurrentRound::::get()); + assert_eq!(0, TokensOf::::total_balance(&SequencerIssuanceVault::get())); + }); +} + +#[test] +#[serial] +fn pallet_max_sequencers_limit_is_considered_separately_for_each_set() { + set_default_mocks!(); + ExtBuilder::new().build().execute_with(|| { + forward_to_block::(10); + ActiveSequencers::::kill(); + + let seq_limit = <::MaxSequencers as Get>::get() as AccountId; + let new_sequencer_active_mock = MockRolldownProviderApi::new_sequencer_active_context(); + new_sequencer_active_mock + .expect() + .times(2 * seq_limit as usize) + .return_const(()); + const FIRST_CHAIN_ID: u32 = 1; + const SECOND_CHAIN_ID: u32 = 3; + + { + for seq in 0u64..seq_limit { + Tokens::mint(RuntimeOrigin::root(), NATIVE_TOKEN_ID, seq, MINIMUM_STAKE).unwrap(); + assert_ok!(SequencerStaking::provide_sequencer_stake( + RuntimeOrigin::root(), + FIRST_CHAIN_ID, + MINIMUM_STAKE, + None, + StakeAction::StakeAndJoinActiveSet, + seq + )); + assert!(SequencerStaking::is_active_sequencer(consts::DEFAULT_CHAIN_ID, &seq)); + } + + Tokens::mint(RuntimeOrigin::root(), NATIVE_TOKEN_ID, seq_limit, MINIMUM_STAKE).unwrap(); + assert_err!( + SequencerStaking::provide_sequencer_stake( + RuntimeOrigin::root(), + FIRST_CHAIN_ID, + MINIMUM_STAKE, + None, + StakeAction::StakeAndJoinActiveSet, + seq_limit + ), + Error::::MaxSequencersLimitReached + ); + } + + { + for seq in 0u64..seq_limit { + Tokens::mint(RuntimeOrigin::root(), NATIVE_TOKEN_ID, seq, MINIMUM_STAKE).unwrap(); + assert_ok!(SequencerStaking::provide_sequencer_stake( + RuntimeOrigin::root(), + SECOND_CHAIN_ID, + MINIMUM_STAKE, + None, + StakeAction::StakeAndJoinActiveSet, + seq + )); + assert!(SequencerStaking::is_active_sequencer(SECOND_CHAIN_ID, &seq)); + } + + Tokens::mint(RuntimeOrigin::root(), NATIVE_TOKEN_ID, seq_limit, MINIMUM_STAKE).unwrap(); + assert_err!( + SequencerStaking::provide_sequencer_stake( + RuntimeOrigin::root(), + SECOND_CHAIN_ID, + MINIMUM_STAKE, + None, + StakeAction::StakeAndJoinActiveSet, + seq_limit + ), + Error::::MaxSequencersLimitReached + ); + } + }); +} + +#[test] +#[serial] +fn test_sequencer_alias_cleanup() { + set_default_mocks!(); + ExtBuilder::new().build().execute_with(|| { + forward_to_block::(10); + + let new_sequencer_active_mock = MockRolldownProviderApi::new_sequencer_active_context(); + new_sequencer_active_mock.expect().times(1).return_const(()); + + assert!(!SequencerStaking::is_active_sequencer(consts::DEFAULT_CHAIN_ID, &CHARLIE)); + assert_ok!(SequencerStaking::provide_sequencer_stake( + RuntimeOrigin::root(), + consts::DEFAULT_CHAIN_ID, + MINIMUM_STAKE, + Some(EVE), + StakeAction::StakeAndJoinActiveSet, + CHARLIE + )); + + assert!(SequencerStaking::is_active_sequencer(consts::DEFAULT_CHAIN_ID, &CHARLIE)); + assert_eq!(AliasAccount::::get((&CHARLIE, consts::DEFAULT_CHAIN_ID)), Some(EVE)); + assert_eq!(AliasAccountInUse::::get(EVE), Some(())); + + assert_ok!(SequencerStaking::provide_sequencer_stake( + RuntimeOrigin::root(), + consts::DEFAULT_CHAIN_ID, + MINIMUM_STAKE, + Some(DAVE), + StakeAction::StakeOnly, + CHARLIE + )); + + assert!(SequencerStaking::is_active_sequencer(consts::DEFAULT_CHAIN_ID, &CHARLIE)); + assert_eq!(AliasAccount::::get((&CHARLIE, consts::DEFAULT_CHAIN_ID)), Some(DAVE)); + assert_eq!(AliasAccountInUse::::get(DAVE), Some(())); + assert_eq!(AliasAccountInUse::::get(EVE), None); + }); +} diff --git a/gasp-node/pallets/stable-swap/Cargo.toml b/gasp-node/pallets/stable-swap/Cargo.toml new file mode 100644 index 000000000..07cab9de6 --- /dev/null +++ b/gasp-node/pallets/stable-swap/Cargo.toml @@ -0,0 +1,60 @@ +[package] +name = "pallet-stable-swap" +description = "A pallet providing stable swaps." +version = "1.0.0" +authors.workspace = true +homepage.workspace = true +repository.workspace = true +edition.workspace = true + +[package.metadata.docs.rs] +targets = ["x86_64-unknown-linux-gnu"] + +[dependencies] +codec = { workspace = true, default-features = false } +scale-info = { workspace = true, default-features = false, features = ["derive"] } +log = { workspace = true, default-features = false } + +frame-benchmarking = { workspace = true, default-features = false } +frame-support = { workspace = true, default-features = false } +frame-system = { workspace = true, default-features = false } +mangata-support = { workspace = true, default-features = false } +mangata-types = { workspace = true, default-features = false } +sp-arithmetic = { workspace = true, default-features = false } +sp-core = { workspace = true, default-features = false } +sp-io = { workspace = true, default-features = false } +sp-runtime = { workspace = true, default-features = false } +sp-std = { workspace = true, default-features = false } + +orml-traits = { workspace = true, default-features = false } +orml-tokens = { workspace = true, default-features = false } + +[dev-dependencies] +primitive-types = { version = "0.12.1", default-features = false, features = ["codec", "num-traits", "scale-info"] } + +[features] +default = ["std"] +std = [ + "codec/std", + "frame-benchmarking/std", + "frame-support/std", + "frame-system/std", + "mangata-support/std", + "scale-info/std", + "sp-arithmetic/std", + "sp-core/std", + "sp-io/std", + "sp-runtime/std", + "sp-std/std", + "orml-traits/std", + "orml-tokens/std", +] + +runtime-benchmarks = ["frame-benchmarking/runtime-benchmarks"] + +try-runtime = [ + "frame-support/try-runtime", + "frame-system/try-runtime", + "sp-runtime/try-runtime", + "orml-tokens/try-runtime", +] diff --git a/gasp-node/pallets/stable-swap/src/lib.rs b/gasp-node/pallets/stable-swap/src/lib.rs new file mode 100644 index 000000000..1b857b74b --- /dev/null +++ b/gasp-node/pallets/stable-swap/src/lib.rs @@ -0,0 +1,1934 @@ +//! # Stable Pools Pallet. + +#![cfg_attr(not(feature = "std"), no_std)] + +use frame_support::{ + ensure, + pallet_prelude::*, + traits::{ + tokens::{Balance, CurrencyId}, + ExistenceRequirement, MultiTokenCurrency, + }, + PalletId, +}; +use frame_system::pallet_prelude::*; + +use mangata_support::pools::{ + ComputeBalances, Inspect, Mutate, PoolPair, PoolReserves, SwapResult, +}; +use sp_arithmetic::traits::Unsigned; +use sp_runtime::traits::{ + checked_pow, AccountIdConversion, CheckedAdd, CheckedDiv, CheckedMul, CheckedSub, Ensure, One, + TrailingZeroInput, Zero, +}; +use sp_std::{convert::TryInto, fmt::Debug, vec, vec::Vec}; + +use orml_tokens::MultiTokenCurrencyExtended; + +mod weights; +use crate::weights::WeightInfo; + +pub use pallet::*; + +#[cfg(test)] +mod mock; +#[cfg(test)] +mod tests; + +#[derive( + TypeInfo, + Encode, + Decode, + CloneNoBound, + EqNoBound, + PartialEqNoBound, + RuntimeDebugNoBound, + MaxEncodedLen, + Default, +)] +#[codec(mel_bound(skip_type_params(MaxAssets)))] +#[scale_info(skip_type_params(MaxAssets))] +pub struct PoolInfo> { + /// Liquidity pool asset + pub lp_token: Id, + /// associated asset ids + pub assets: BoundedVec, + /// amplification coefficient for StableSwap equation + pub amp_coeff: u128, + pub rate_multipliers: BoundedVec, +} +impl> PoolInfo { + fn get_asset_index(&self, id: Id) -> Result> { + self.assets.iter().position(|x| *x == id).ok_or(Error::::NoSuchAssetInPool) + } +} + +pub type PoolIdOf = ::CurrencyId; +pub type PoolInfoOf = + PoolInfo<::CurrencyId, ::Balance, ::MaxAssetsInPool>; +pub type AssetIdsOf = BoundedVec<::CurrencyId, ::MaxAssetsInPool>; +pub type BalancesOf = BoundedVec<::Balance, ::MaxAssetsInPool>; + +#[frame_support::pallet] +pub mod pallet { + use super::*; + + #[pallet::pallet] + pub struct Pallet(_); + + #[pallet::config] + pub trait Config: frame_system::Config { + /// Overarching event type. + type RuntimeEvent: From> + IsType<::RuntimeEvent>; + + /// Currency type that this works on. + type Currency: MultiTokenCurrencyExtended< + Self::AccountId, + Balance = Self::Balance, + CurrencyId = Self::CurrencyId, + >; + + /// The `Currency::Balance` type of the currency. + type Balance: Balance; + + /// A type used for multiplication of `Balance`. + type HigherPrecisionBalance: Copy + + Debug + + One + + Ensure + + Unsigned + + From + + From + + From + + TryInto; + + /// Identifier for the assets. + type CurrencyId: CurrencyId; + + /// Treasury pallet id used for fee deposits. + type TreasuryPalletId: Get; + + /// Treasury sub-account used for buy & burn. + type BnbTreasurySubAccDerive: Get<[u8; 4]>; + + /// Total fee applied to a swap. + /// Part goes back to pool, part to treasury, and part is burned. + #[pallet::constant] + type MarketTotalFee: Get; + + /// Percentage of total fee that goes into the treasury. + #[pallet::constant] + type MarketTreasuryFeePart: Get; + + /// Percentage of treasury fee that gets burned if possible. + #[pallet::constant] + type MarketBnBFeePart: Get; + + #[pallet::constant] + type MaxApmCoeff: Get; + + #[pallet::constant] + type DefaultApmCoeff: Get; + + #[pallet::constant] + type MaxAssetsInPool: Get; + + /// Weight information for extrinsics in this pallet. + type WeightInfo: WeightInfo; + } + + #[pallet::error] + pub enum Error { + /// Amplification coefficient lower then 1 or too large + AmpCoeffOutOfRange, + /// Initial pool rate multipliers are too large + InitialPoolRateOutOfRange, + /// Too many assets for pool creation + TooManyAssets, + /// Pool already exists + PoolAlreadyExists, + /// Asset does not exist + AssetDoesNotExist, + /// Asset ids cannot be the same + SameAsset, + /// No such pool exists + NoSuchPool, + /// Provided arguments do not match in length + ArgumentsLengthMismatch, + /// Pool is broken, remove liquidity + PoolInvariantBroken, + /// Initial liquidity provision needs all assets + InitialLiquidityZeroAmount, + /// Asset does not exist in pool. + NoSuchAssetInPool, + /// Unexpected failure + UnexpectedFailure, + /// Insufficient output amount does not meet min requirements + InsufficientOutputAmount, + /// Insufficient input amount + InsufficientInputAmount, + /// Excesive output amount does not meet max requirements + ExcesiveOutputAmount, + /// Math overflow + MathOverflow, + /// Liquidity token creation failed + LiquidityTokenCreationFailed, + } + + // Pallet's events. + #[pallet::event] + #[pallet::generate_deposit(pub(super) fn deposit_event)] + pub enum Event { + /// A successful call of the `CretaPool` extrinsic will create this event. + PoolCreated { + /// The account that created the pool. + creator: T::AccountId, + /// The pool id and the account ID of the pool. + pool_id: PoolIdOf, + /// The id of the liquidity tokens that will be minted when assets are added to this + /// pool. + lp_token: T::CurrencyId, + /// The asset ids associated with the pool. Note that the order of the assets may not be + /// the same as the order specified in the create pool extrinsic. + assets: AssetIdsOf, + }, + + /// A successful call of the `AddLiquidity` extrinsic will create this event. + LiquidityMinted { + /// The account that the liquidity was taken from. + who: T::AccountId, + /// The id of the pool that the liquidity was added to. + pool_id: PoolIdOf, + /// The amounts of the assets that were added to the pool. + amounts_provided: BalancesOf, + /// The id of the LP token that was minted. + lp_token: T::CurrencyId, + /// The amount of lp tokens that were minted of that id. + lp_token_minted: T::Balance, + /// The new total supply of the associated LP token. + total_supply: T::Balance, + /// The fees taken into treasury. + fees: BalancesOf, + }, + + /// Assets have been swapped, a successfull call to `Swap` will create this event. + AssetsSwapped { + /// Which account was the instigator of the swap. + who: T::AccountId, + /// The id of the pool where assets were swapped. + pool_id: PoolIdOf, + /// The id of the asset that was swapped. + asset_in: T::CurrencyId, + /// The amount of the asset that was swapped. + amount_in: T::Balance, + /// The id of the asset that was received. + asset_out: T::CurrencyId, + /// The amount of the asset that was received. + amount_out: T::Balance, + }, + /// A successful call of the `RemoveLiquidityOneAsset` extrinsic will create this event. + LiquidityBurnedOne { + /// The account that the liquidity token was taken from. + who: T::AccountId, + /// The id of the pool where assets were swapped. + pool_id: PoolIdOf, + /// The id of the asset that was received. + asset_id: T::CurrencyId, + /// The amount of the asset that was received. + amount: T::Balance, + /// The amount of the associated LP token that was burned. + burned_amount: T::Balance, + /// The new total supply of the associated LP token. + total_supply: T::Balance, + }, + /// A successful call of the `RemoveLiquidityImbalanced` & `RemoveLiquidity` extrinsic will create this event. + LiquidityBurned { + /// The account that the liquidity token was taken from. + who: T::AccountId, + /// The id of the pool where assets were swapped. + pool_id: PoolIdOf, + /// The amount of the asset that was received. + amounts: BalancesOf, + /// The amount of the associated LP token that was burned. + burned_amount: T::Balance, + /// The new total supply of the associated LP token. + total_supply: T::Balance, + /// The fees taken into treasury. + fees: BalancesOf, + }, + } + + #[pallet::storage] + #[pallet::getter(fn asset_pool)] + pub type Pools = StorageMap<_, Identity, PoolIdOf, PoolInfoOf, OptionQuery>; + + #[pallet::hooks] + impl Hooks> for Pallet { + fn integrity_test() { + assert!( + T::MaxAssetsInPool::get() > 1, + "the `MaxAssetsInPool` should be greater than 1", + ); + } + } + + /// Pallet's callable functions. + #[pallet::call] + impl Pallet { + /// Creates a liquidity pool and an associated new `lp_token` asset + /// (the id of which is returned in the `Event::PoolCreated` event). + /// Tokens can have arbitrary decimals (<=18). + /// + /// * `assets` - An array of asset ids in pool + /// * `rates` - An array of: [10 ** (36 - _coins[n].decimals()), ... for n in range(N_COINS)] + /// A custom rate can also be used & values needs to be multiplied by 1e18, with max value of 1e36 + /// eg. for a rate 2:1 it's [2e18, 1e18] + /// * `amp_coeff` - Amplification co-efficient - a lower value here means less tolerance for imbalance within the pool's assets. + /// Suggested values include: + /// * Uncollateralized algorithmic stablecoins: 5-10 + /// * Non-redeemable, collateralized assets: 100 + /// * Redeemable assets: 200-400 + /// + /// Initial liquidity amounts needs to be provided with [`Pallet::add_liquidity`]. + #[pallet::call_index(0)] + #[pallet::weight(T::WeightInfo::create_pool())] + pub fn create_pool( + origin: OriginFor, + assets: Vec, + rates: Vec, + amp_coeff: u128, + ) -> DispatchResult { + let sender = ensure_signed(origin)?; + + let info = Self::do_create_pool(&sender, assets, rates, amp_coeff)?; + + Self::deposit_event(Event::PoolCreated { + creator: sender, + pool_id: info.lp_token, + lp_token: info.lp_token, + assets: info.assets, + }); + + Ok(()) + } + + #[pallet::call_index(1)] + #[pallet::weight(T::WeightInfo::add_liquidity())] + pub fn swap( + origin: OriginFor, + pool_id: PoolIdOf, + asset_in: T::CurrencyId, + asset_out: T::CurrencyId, + dx: T::Balance, + min_dy: T::Balance, + ) -> DispatchResult { + let sender = ensure_signed(origin)?; + + // we ignore the buy&burn, funds stay in treasury + let SwapResult { amount_out, .. } = + Self::do_swap(&sender, pool_id, asset_in, asset_out, dx, min_dy)?; + + Self::deposit_event(Event::AssetsSwapped { + who: sender, + pool_id, + asset_in, + amount_in: dx, + asset_out, + amount_out, + }); + + Ok(()) + } + + #[pallet::call_index(2)] + #[pallet::weight(T::WeightInfo::add_liquidity())] + pub fn add_liquidity( + origin: OriginFor, + pool_id: PoolIdOf, + amounts: Vec, + min_amount_lp_tokens: T::Balance, + ) -> DispatchResult { + let sender = ensure_signed(origin)?; + + let (mint_amount, fees) = + Self::do_add_liquidity(&sender, pool_id, amounts.clone(), min_amount_lp_tokens)?; + + let total_supply = T::Currency::total_issuance(pool_id); + Self::deposit_event(Event::LiquidityMinted { + who: sender, + pool_id, + amounts_provided: BoundedVec::truncate_from(amounts), + lp_token: pool_id, + lp_token_minted: mint_amount, + total_supply, + fees, + }); + + Ok(()) + } + + /// Withdraw a single asset from the pool + #[pallet::call_index(3)] + #[pallet::weight(T::WeightInfo::remove_liquidity_one_asset())] + pub fn remove_liquidity_one_asset( + origin: OriginFor, + pool_id: PoolIdOf, + asset_id: T::CurrencyId, + burn_amount: T::Balance, + min_amount_out: T::Balance, + ) -> DispatchResult { + let sender = ensure_signed(origin)?; + + ensure!(burn_amount > Zero::zero(), Error::::InsufficientInputAmount); + + let maybe_pool = Pools::::get(pool_id.clone()); + let pool = maybe_pool.as_ref().ok_or(Error::::NoSuchPool)?; + let pool_account = Self::get_pool_account(&pool_id); + + let (dy, trsy_fee) = + Self::calc_withdraw_one(&pool_account, &pool, asset_id, burn_amount)?; + + ensure!(dy > min_amount_out, Error::::InsufficientOutputAmount); + + T::Currency::transfer( + asset_id, + &sender, + &Self::treasury_account_id(), + trsy_fee, + ExistenceRequirement::AllowDeath, + )?; + + T::Currency::burn_and_settle(pool.lp_token, &sender, burn_amount)?; + + T::Currency::transfer( + asset_id, + &pool_account, + &sender, + dy, + ExistenceRequirement::AllowDeath, + )?; + + let total_supply = T::Currency::total_issuance(pool.lp_token); + Self::deposit_event(Event::LiquidityBurnedOne { + who: sender, + pool_id, + asset_id, + amount: dy, + burned_amount: burn_amount, + total_supply, + }); + + Ok(()) + } + + /// Withdraw assets from the pool in an imbalanced amounts + #[pallet::call_index(4)] + #[pallet::weight(T::WeightInfo::remove_liquidity_imbalanced())] + pub fn remove_liquidity_imbalanced( + origin: OriginFor, + pool_id: PoolIdOf, + amounts: Vec, + max_burn_amount: T::Balance, + ) -> DispatchResult { + let sender = ensure_signed(origin)?; + + let maybe_pool = Pools::::get(pool_id.clone()); + let pool = maybe_pool.as_ref().ok_or(Error::::NoSuchPool)?; + ensure!(amounts.len() == pool.assets.len(), Error::::ArgumentsLengthMismatch); + let pool_account = Self::get_pool_account(&pool_id); + let asset_amounts = pool.assets.iter().zip(amounts.iter()); + let total_supply = T::Currency::total_issuance(pool.lp_token); + let n = T::HigherPrecisionBalance::from(pool.assets.len() as u128); + + let (balances_0, d_0) = Self::get_invariant_pool(&pool_account, &pool)?; + + // transfer to user account + for (id, amount) in asset_amounts { + T::Currency::transfer( + *id, + &pool_account, + &sender, + *amount, + ExistenceRequirement::AllowDeath, + )?; + } + + let (balances_1, d_1) = Self::get_invariant_pool(&pool_account, &pool)?; + let (d_1, fees) = Self::calc_imbalanced_liquidity_fees( + &pool, + &n, + &d_0, + &d_1, + &balances_0, + &balances_1, + )?; + + let burn_amount = d_0 + .checked_sub(&d_1) + .ok_or(Error::::MathOverflow)? + .checked_mul(&T::HigherPrecisionBalance::from(total_supply)) + .ok_or(Error::::MathOverflow)? + .checked_div(&d_0) + .ok_or(Error::::MathOverflow)? + .checked_add(&One::one()) + .ok_or(Error::::MathOverflow)? + .try_into() + .map_err(|_| Error::::MathOverflow)?; + + ensure!(burn_amount > One::one(), Error::::InsufficientInputAmount); + ensure!(burn_amount <= max_burn_amount, Error::::ExcesiveOutputAmount); + + T::Currency::burn_and_settle(pool.lp_token, &sender, burn_amount)?; + + let mut fees_b: Vec = vec![]; + for (&id, &f) in pool.assets.iter().zip(fees.iter()) { + let to_treasury = + Self::checked_mul_div_u128(&f, &Self::get_trsy_fee(), Self::FEE_DENOMINATOR)? + .try_into() + .map_err(|_| Error::::MathOverflow)?; + + T::Currency::transfer( + id, + &pool_account, + &Self::treasury_account_id(), + to_treasury, + ExistenceRequirement::AllowDeath, + )?; + + fees_b.push(f.try_into().map_err(|_| Error::::MathOverflow)?) + } + + let total_supply = T::Currency::total_issuance(pool.lp_token); + Self::deposit_event(Event::LiquidityBurned { + who: sender, + pool_id, + amounts: BoundedVec::truncate_from(amounts), + burned_amount: burn_amount, + total_supply, + fees: BoundedVec::truncate_from(fees_b), + }); + + Ok(()) + } + + /// Withdraw balanced assets from the pool given LP tokens amount to burn + #[pallet::call_index(5)] + #[pallet::weight(T::WeightInfo::remove_liquidity())] + pub fn remove_liquidity( + origin: OriginFor, + pool_id: PoolIdOf, + burn_amount: T::Balance, + min_amounts: Vec, + ) -> DispatchResult { + let sender = ensure_signed(origin)?; + + let amounts = Self::do_remove_liquidity(&sender, pool_id, burn_amount, min_amounts)?; + + let total_supply = T::Currency::total_issuance(pool_id); + Self::deposit_event(Event::LiquidityBurned { + who: sender, + pool_id, + amounts, + burned_amount: burn_amount, + total_supply, + fees: BoundedVec::new(), + }); + + Ok(()) + } + } + + impl Pallet { + pub const FEE_DENOMINATOR: u128 = 10_u128.pow(10); + pub(crate) const PRECISION_EXP: u32 = 18; + const PRECISION: u128 = 10_u128.pow(Self::PRECISION_EXP); + const A_PRECISION: u128 = 100; + + // calls impl + pub fn do_create_pool( + sender: &T::AccountId, + assets: Vec, + rates: Vec, + amp_coeff: u128, + ) -> Result, DispatchError> { + ensure!( + 1 <= amp_coeff && amp_coeff <= T::MaxApmCoeff::get(), + Error::::AmpCoeffOutOfRange + ); + + let assets_in_len = assets.len(); + ensure!( + assets_in_len <= T::MaxAssetsInPool::get().try_into().unwrap_or_default(), + Error::::TooManyAssets + ); + ensure!( + rates.len() <= T::MaxAssetsInPool::get().try_into().unwrap_or_default(), + Error::::TooManyAssets + ); + ensure!(rates.len() == assets_in_len, Error::::ArgumentsLengthMismatch); + + let mut to_sort: Vec<(T::CurrencyId, T::Balance)> = + assets.into_iter().zip(rates.into_iter()).collect(); + to_sort.sort_by_key(|&(a, _)| a); + let (mut assets, rates): (Vec, Vec) = + to_sort.into_iter().unzip(); + assets.dedup(); + ensure!(assets_in_len == assets.len(), Error::::SameAsset); + + for id in assets.clone() { + ensure!(T::Currency::exists(id), Error::::AssetDoesNotExist) + } + + let lp_token: T::CurrencyId = T::Currency::create(&sender, T::Balance::zero()) + .map_err(|_| Error::::LiquidityTokenCreationFailed)? + .into(); + let pool_account = Self::get_pool_account(&lp_token); + ensure!( + !frame_system::Pallet::::account_exists(&pool_account), + Error::::PoolAlreadyExists + ); + frame_system::Pallet::::inc_providers(&pool_account); + + let assets = AssetIdsOf::::truncate_from(assets); + let rates = BalancesOf::::truncate_from(rates); + let amp_coeff = amp_coeff * Self::A_PRECISION; + let pool_info = PoolInfo { + lp_token: lp_token.clone(), + assets: assets.clone(), + amp_coeff, + rate_multipliers: rates, + }; + Pools::::insert(lp_token.clone(), pool_info.clone()); + + Ok(pool_info) + } + + pub fn do_swap( + sender: &T::AccountId, + pool_id: PoolIdOf, + asset_in: T::CurrencyId, + asset_out: T::CurrencyId, + dx: T::Balance, + min_dy: T::Balance, + ) -> Result, DispatchError> { + let maybe_pool = Pools::::get(pool_id.clone()); + let pool = maybe_pool.as_ref().ok_or(Error::::NoSuchPool)?; + let pool_account = Self::get_pool_account(&pool_id); + + // ensure assets in pool + let i = pool.get_asset_index::(asset_in)?; + let j = pool.get_asset_index::(asset_out)?; + + // old balances + let (_, xp) = Self::get_balances_xp_pool(&pool_account, &pool)?; + + // get tokens in + T::Currency::transfer( + asset_in, + &sender, + &pool_account, + dx, + ExistenceRequirement::AllowDeath, + )?; + + // get dy for dx + let (dy, dy_fee) = Self::calc_dy( + i, + j, + T::HigherPrecisionBalance::from(dx), + pool.amp_coeff, + &xp, + pool.rate_multipliers.to_vec(), + )?; + + let fee = Self::checked_mul_div_to_balance(&dy_fee, pool.rate_multipliers[j])?; + let to_treasury = Self::checked_mul_div_to_balance( + &Self::checked_mul_div_u128(&dy_fee, &Self::get_trsy_fee(), Self::FEE_DENOMINATOR)?, + pool.rate_multipliers[j], + )?; + + let to_bnb = Self::checked_mul_div_to_balance( + &Self::checked_mul_div_u128( + &T::HigherPrecisionBalance::from(to_treasury), + &Self::get_bnb_fee(), + Self::FEE_DENOMINATOR, + )?, + pool.rate_multipliers[j], + )?; + + let to_treasury = to_treasury - to_bnb; + + T::Currency::transfer( + pool.assets[j], + &pool_account, + &Self::treasury_account_id(), + to_treasury, + ExistenceRequirement::AllowDeath, + )?; + + T::Currency::transfer( + pool.assets[j], + &pool_account, + &Self::bnb_treasury_account_id(), + to_bnb, + ExistenceRequirement::AllowDeath, + )?; + + // real units + let dy = Self::checked_mul_div_to_balance( + &dy.checked_sub(&dy_fee).ok_or(Error::::MathOverflow)?, + pool.rate_multipliers[j], + )?; + ensure!(dy >= min_dy, Error::::InsufficientOutputAmount); + + T::Currency::transfer( + asset_out, + &pool_account, + &sender, + dy, + ExistenceRequirement::AllowDeath, + )?; + + Self::deposit_event(Event::AssetsSwapped { + who: sender.clone(), + pool_id, + asset_in, + amount_in: dx, + asset_out, + amount_out: dy, + }); + + Ok(SwapResult { + amount_out: dy, + total_fee: fee, + treasury_fee: to_treasury, + bnb_fee: to_bnb, + }) + } + + pub fn do_add_liquidity( + sender: &T::AccountId, + pool_id: PoolIdOf, + amounts: Vec, + min_amount_lp_tokens: T::Balance, + ) -> Result<(T::Balance, BalancesOf), DispatchError> { + let maybe_pool = Pools::::get(pool_id.clone()); + let pool = maybe_pool.as_ref().ok_or(Error::::NoSuchPool)?; + ensure!(amounts.len() == pool.assets.len(), Error::::ArgumentsLengthMismatch); + let pool_account = Self::get_pool_account(&pool_id); + let asset_amounts = pool.assets.iter().zip(amounts.iter()); + let total_supply = T::Currency::total_issuance(pool.lp_token); + let n = T::HigherPrecisionBalance::from(pool.assets.len() as u128); + + // check initial amounts + for amount in amounts.clone() { + ensure!( + !(total_supply == Zero::zero() && amount == Zero::zero()), + Error::::InitialLiquidityZeroAmount + ); + } + + // get initial invariant + let (balances_0, d_0) = Self::get_invariant_pool(&pool_account, &pool)?; + + // transfer from user account + for (id, amount) in asset_amounts { + T::Currency::transfer( + *id, + &sender, + &pool_account, + *amount, + ExistenceRequirement::AllowDeath, + )?; + } + + // check new invariant + let (balances_1, d_1) = Self::get_invariant_pool(&pool_account, &pool)?; + ensure!(d_1 > d_0, Error::::PoolInvariantBroken); + + let mut fees_b: Vec = vec![]; + // LPs also incur fees. A swap between A & B would pay roughly the same amount of fees as depositing A into the pool and then withdrawing B. + let mint_amount = if total_supply > Zero::zero() { + let (d_1, fees) = Self::calc_imbalanced_liquidity_fees( + &pool, + &n, + &d_0, + &d_1, + &balances_0, + &balances_1, + )?; + + for (&id, &f) in pool.assets.iter().zip(fees.iter()) { + let to_treasury = Self::checked_mul_div_u128( + &f, + &Self::get_trsy_fee(), + Self::FEE_DENOMINATOR, + )? + .try_into() + .map_err(|_| Error::::MathOverflow)?; + + T::Currency::transfer( + id, + &pool_account, + &Self::treasury_account_id(), + to_treasury, + ExistenceRequirement::AllowDeath, + )?; + + fees_b.push(f.try_into().map_err(|_| Error::::MathOverflow)?) + } + + d_1.checked_sub(&d_0) + .ok_or(Error::::MathOverflow)? + .checked_mul(&T::HigherPrecisionBalance::from(total_supply)) + .ok_or(Error::::MathOverflow)? + .checked_div(&d_0) + .ok_or(Error::::MathOverflow)? + .try_into() + .map_err(|_| Error::::MathOverflow)? + } else { + // no fees on intial liquidity deposit + d_1.try_into().map_err(|_| Error::::MathOverflow)? + }; + + ensure!(mint_amount >= min_amount_lp_tokens, Error::::InsufficientOutputAmount); + + T::Currency::mint(pool.lp_token, &sender, mint_amount)?; + + Ok((mint_amount, BoundedVec::truncate_from(fees_b))) + } + + pub fn do_remove_liquidity( + sender: &T::AccountId, + pool_id: PoolIdOf, + burn_amount: T::Balance, + min_amounts: Vec, + ) -> Result, DispatchError> { + let maybe_pool = Pools::::get(pool_id.clone()); + let pool = maybe_pool.as_ref().ok_or(Error::::NoSuchPool)?; + ensure!(min_amounts.len() == pool.assets.len(), Error::::ArgumentsLengthMismatch); + let pool_account = Self::get_pool_account(&pool_id); + let total_supply = T::Currency::total_issuance(pool.lp_token); + + let (balances, _) = Self::get_balances_xp_pool(&pool_account, &pool)?; + + let mut amounts = vec![]; + for i in 0..pool.assets.len() { + let value = balances[i] + .checked_mul(&T::HigherPrecisionBalance::from(burn_amount)) + .ok_or(Error::::MathOverflow)? + .checked_div(&T::HigherPrecisionBalance::from(total_supply)) + .ok_or(Error::::MathOverflow)? + .try_into() + .map_err(|_| Error::::MathOverflow)?; + amounts.push(value); + + ensure!(value >= min_amounts[i], Error::::InsufficientOutputAmount); + + T::Currency::transfer( + pool.assets[i], + &pool_account, + &sender, + value, + ExistenceRequirement::AllowDeath, + )?; + } + + T::Currency::burn_and_settle(pool.lp_token, &sender, burn_amount)?; + + Ok(BoundedVec::truncate_from(amounts)) + } + + pub fn settle_pool_fees( + who: &T::AccountId, + pool_id: PoolIdOf, + asset_id: T::CurrencyId, + fee: T::Balance, + ) -> Result<(), DispatchError> { + let pool_account = Self::get_pool_account(&pool_id); + T::Currency::transfer( + asset_id, + who, + &pool_account, + fee, + ExistenceRequirement::AllowDeath, + )?; + Ok(()) + } + + /// The account of the pool that store asset balances. + /// + /// This actually does computation. If you need to keep using it, then make sure you cache + /// the value and only call this once. + pub fn get_pool_account(pool_id: &PoolIdOf) -> T::AccountId { + let encoded_pool_id = sp_io::hashing::blake2_256(&Encode::encode(&pool_id)); + + Decode::decode(&mut TrailingZeroInput::new(encoded_pool_id.as_ref())) + .expect("infinite length input; no invalid inputs for type; qed") + } + + pub fn get_pool_reserves(pool_id: &PoolIdOf) -> Result, Error> { + let maybe_pool = Pools::::get(pool_id.clone()); + let pool = maybe_pool.as_ref().ok_or(Error::::NoSuchPool)?; + let pool_account = Self::get_pool_account(&pool_id); + + Ok(pool + .assets + .iter() + .map(|&id| T::Currency::available_balance(id, &pool_account)) + .collect()) + } + + /// The current virtual price of the pool LP token, useful for calculating profits. + /// Returns LP token virtual price normalized to 1e18. + pub fn get_virtual_price(pool_id: &PoolIdOf) -> Result> { + let maybe_pool = Pools::::get(pool_id.clone()); + let pool = maybe_pool.as_ref().ok_or(Error::::NoSuchPool)?; + let pool_account = Self::get_pool_account(&pool_id); + + let total_supply: ::Balance = T::Currency::total_issuance(pool.lp_token); + let (_, d) = Self::get_invariant_pool(&pool_account, pool)?; + + d.checked_mul(&T::HigherPrecisionBalance::from(Self::PRECISION)) + .ok_or(Error::::MathOverflow)? + .checked_div(&T::HigherPrecisionBalance::from(total_supply)) + .ok_or(Error::::MathOverflow)? + .try_into() + .map_err(|_| Error::::MathOverflow) + } + + /// Calculate the current input dx given output dy. + pub fn get_dx( + pool_id: &PoolIdOf, + asset_in: T::CurrencyId, + asset_out: T::CurrencyId, + dy: T::Balance, + ) -> Result> { + let maybe_pool = Pools::::get(pool_id.clone()); + let pool = maybe_pool.as_ref().ok_or(Error::::NoSuchPool)?; + let pool_account = Self::get_pool_account(&pool_id); + let (_, xp) = Self::get_balances_xp_pool(&pool_account, &pool)?; + let i = pool.get_asset_index::(asset_in)?; + let j = pool.get_asset_index::(asset_out)?; + + Self::get_dx_xp(&pool, i, j, dy, xp) + } + + pub fn get_dx_with_impact( + pool_id: &PoolIdOf, + asset_in: T::CurrencyId, + asset_out: T::CurrencyId, + dy: T::Balance, + ) -> Result<(T::Balance, T::Balance), Error> { + let maybe_pool = Pools::::get(pool_id.clone()); + let pool = maybe_pool.as_ref().ok_or(Error::::NoSuchPool)?; + let pool_account = Self::get_pool_account(&pool_id); + let (reserves, xp) = Self::get_balances_xp_pool(&pool_account, &pool)?; + let i = pool.get_asset_index::(asset_in)?; + let j = pool.get_asset_index::(asset_out)?; + + let dx = Self::get_dx_xp(&pool, i, j, dy, xp)?; + + let mut reserves = reserves.clone(); + reserves[i] = reserves[i] + T::HigherPrecisionBalance::from(dx); + reserves[j] = reserves[j] - T::HigherPrecisionBalance::from(dy); + let xp = Self::xp(&pool.rate_multipliers, &reserves)?; + + let dx2 = Self::get_dx_xp(&pool, i, j, dy, xp)?; + + Ok((dx, dx2)) + } + + /// Calculate the output dy given input dx. + pub fn get_dy( + pool_id: &PoolIdOf, + asset_in: T::CurrencyId, + asset_out: T::CurrencyId, + dx: T::Balance, + ) -> Result> { + let maybe_pool = Pools::::get(pool_id.clone()); + let pool = maybe_pool.as_ref().ok_or(Error::::NoSuchPool)?; + let pool_account = Self::get_pool_account(&pool_id); + let (_, xp) = Self::get_balances_xp_pool(&pool_account, &pool)?; + let i = pool.get_asset_index::(asset_in)?; + let j = pool.get_asset_index::(asset_out)?; + + log::debug!(target: "", "{:?} {:?} {:?} {:?}", i, j, dx, xp); + Self::get_dy_xp(&pool, i, j, dx, xp) + } + + pub fn get_dy_with_impact( + pool_id: &PoolIdOf, + asset_in: T::CurrencyId, + asset_out: T::CurrencyId, + dx: T::Balance, + ) -> Result<(T::Balance, T::Balance), Error> { + let maybe_pool = Pools::::get(pool_id.clone()); + let pool = maybe_pool.as_ref().ok_or(Error::::NoSuchPool)?; + let pool_account = Self::get_pool_account(&pool_id); + let (reserves, xp) = Self::get_balances_xp_pool(&pool_account, &pool)?; + let i = pool.get_asset_index::(asset_in)?; + let j = pool.get_asset_index::(asset_out)?; + + let dy = Self::get_dy_xp(&pool, i, j, dx, xp)?; + + let mut reserves = reserves.clone(); + reserves[i] = reserves[i] + T::HigherPrecisionBalance::from(dx); + reserves[j] = reserves[j] - T::HigherPrecisionBalance::from(dy); + let xp = Self::xp(&pool.rate_multipliers, &reserves)?; + + let dy2 = Self::get_dy_xp(&pool, i, j, dx, xp)?; + + Ok((dy, dy2)) + } + + pub fn calc_lp_token_amount( + pool_id: &PoolIdOf, + amounts: Vec, + is_deposit: bool, + ) -> Result> { + let maybe_pool = Pools::::get(pool_id.clone()); + let pool = maybe_pool.as_ref().ok_or(Error::::NoSuchPool)?; + let pool_account = Self::get_pool_account(&pool_id); + + let n = T::HigherPrecisionBalance::from(pool.assets.len() as u128); + let total_supply: ::Balance = T::Currency::total_issuance(pool.lp_token); + + let (balances_0, d_0) = Self::get_invariant_pool(&pool_account, &pool)?; + + let mut balances_1 = vec![Zero::zero(); pool.assets.len()]; + for i in 0..balances_0.len() { + let amount = T::HigherPrecisionBalance::from(amounts[i]); + balances_1[i] = if is_deposit { + balances_0[i].checked_add(&amount).ok_or(Error::::MathOverflow)? + } else { + balances_0[i].checked_sub(&amount).ok_or(Error::::MathOverflow)? + } + } + let xp_1 = Self::xp(&pool.rate_multipliers, &balances_1)?; + let d_1 = Self::get_invariant(&xp_1, pool.amp_coeff)?; + + let d_2 = if total_supply > Zero::zero() { + let (d, _) = Self::calc_imbalanced_liquidity_fees( + &pool, + &n, + &d_0, + &d_1, + &balances_0, + &balances_1, + )?; + d + } else { + d_1 + }; + + let diff = if is_deposit { + d_2.checked_sub(&d_0).ok_or(Error::::MathOverflow)? + } else { + d_0.checked_sub(&d_2).ok_or(Error::::MathOverflow)? + }; + + let r = diff + .checked_mul(&T::HigherPrecisionBalance::from(total_supply)) + .ok_or(Error::::MathOverflow)? + .checked_div(&d_0) + .ok_or(Error::::MathOverflow)? + .try_into() + .map_err(|_| Error::::MathOverflow)?; + + Ok(r) + } + + pub fn get_burn_amounts( + pool_id: &PoolIdOf, + burn_amount: T::Balance, + ) -> Result, DispatchError> { + let maybe_pool = Pools::::get(pool_id.clone()); + let pool = maybe_pool.as_ref().ok_or(Error::::NoSuchPool)?; + let pool_account = Self::get_pool_account(&pool_id); + let total_supply = T::Currency::total_issuance(pool.lp_token); + + let (balances, _) = Self::get_balances_xp_pool(&pool_account, &pool)?; + + let mut amounts = vec![]; + for i in 0..pool.assets.len() { + let value = balances[i] + .checked_mul(&T::HigherPrecisionBalance::from(burn_amount)) + .ok_or(Error::::MathOverflow)? + .checked_div(&T::HigherPrecisionBalance::from(total_supply)) + .ok_or(Error::::MathOverflow)? + .try_into() + .map_err(|_| Error::::MathOverflow)?; + amounts.push(value); + } + + Ok(amounts) + } + + fn treasury_account_id() -> T::AccountId { + T::TreasuryPalletId::get().into_account_truncating() + } + + fn bnb_treasury_account_id() -> T::AccountId { + T::TreasuryPalletId::get() + .into_sub_account_truncating(T::BnbTreasurySubAccDerive::get()) + } + + fn get_fee() -> T::HigherPrecisionBalance { + T::HigherPrecisionBalance::from(T::MarketTotalFee::get()) + } + + fn get_trsy_fee() -> T::HigherPrecisionBalance { + T::HigherPrecisionBalance::from(T::MarketTreasuryFeePart::get()) + } + + fn get_bnb_fee() -> T::HigherPrecisionBalance { + T::HigherPrecisionBalance::from(T::MarketBnBFeePart::get()) + } + + // dyn fee 2* mul + fn get_fee_dyn_mul() -> T::HigherPrecisionBalance { + T::HigherPrecisionBalance::from(20_000_000_000_u128) + // T::HigherPrecisionBalance::from(1_u128) + } + + fn has_dynamic_fee() -> bool { + let m = Self::get_fee_dyn_mul(); + let den = T::HigherPrecisionBalance::from(Self::FEE_DENOMINATOR); + return m > den + } + + // stable swap maths + + fn base_fee( + n: &T::HigherPrecisionBalance, + ) -> Result<::HigherPrecisionBalance, Error> { + let fee = Self::get_fee(); + fee.checked_mul(n) + .ok_or(Error::::MathOverflow)? + .checked_div( + &n.checked_sub(&One::one()) + .ok_or(Error::::MathOverflow)? + .checked_mul(&T::HigherPrecisionBalance::from(4_u32)) + .ok_or(Error::::MathOverflow)?, + ) + .ok_or(Error::::MathOverflow) + } + + fn dynamic_fee( + xpi: &T::HigherPrecisionBalance, + xpj: &T::HigherPrecisionBalance, + ) -> Result> { + let fee = Self::get_fee(); + let mul = Self::get_fee_dyn_mul(); + Self::calc_dynamic_fee(xpi, xpj, &fee, &mul) + } + + fn dynamic_fee_base( + xpi: &T::HigherPrecisionBalance, + xpj: &T::HigherPrecisionBalance, + n: &T::HigherPrecisionBalance, + ) -> Result> { + let fee = Self::base_fee(n)?; + let mul = Self::get_fee_dyn_mul(); + Self::calc_dynamic_fee(xpi, xpj, &fee, &mul) + } + + // https://www.desmos.com/calculator/zhrwbvcipo + fn calc_dynamic_fee( + xpi: &T::HigherPrecisionBalance, + xpj: &T::HigherPrecisionBalance, + fee: &T::HigherPrecisionBalance, + m: &T::HigherPrecisionBalance, + ) -> Result> { + let den = T::HigherPrecisionBalance::from(Self::FEE_DENOMINATOR); + if *m <= den { + return Ok(*fee); + } + + let xps2 = checked_pow(xpi.checked_add(xpj).ok_or(Error::::MathOverflow)?, 2) + .ok_or(Error::::MathOverflow)?; + + let res = fee + .checked_mul(m) + .ok_or(Error::::MathOverflow)? + .checked_div( + &m.checked_sub(&den) + .ok_or(Error::::MathOverflow)? + .checked_mul(&T::HigherPrecisionBalance::from(4_u32)) + .ok_or(Error::::MathOverflow)? + .checked_mul(xpi) + .ok_or(Error::::MathOverflow)? + .checked_mul(xpj) + .ok_or(Error::::MathOverflow)? + .checked_div(&xps2) + .ok_or(Error::::MathOverflow)? + .checked_add(&den) + .ok_or(Error::::MathOverflow)?, + ) + .ok_or(Error::::MathOverflow)?; + + Ok(res) + } + + fn get_balances_xp_pool( + pool_account: &T::AccountId, + pool: &PoolInfoOf, + ) -> Result<(Vec, Vec), Error> { + let reserves: Vec = pool + .assets + .iter() + .map(|&id| T::Currency::available_balance(id, pool_account)) + .map(T::HigherPrecisionBalance::from) + .collect(); + + let xp = Self::xp(&pool.rate_multipliers, &reserves)?; + + Ok((reserves, xp)) + } + + fn get_invariant_pool( + pool_account: &T::AccountId, + pool: &PoolInfoOf, + ) -> Result<(Vec, T::HigherPrecisionBalance), Error> { + let amp = pool.amp_coeff; + let (reserves, xp) = Self::get_balances_xp_pool(pool_account, pool)?; + let d = Self::get_invariant(&xp, amp)?; + Ok((reserves, d)) + } + + fn xp( + rates: &BalancesOf, + balances: &Vec, + ) -> Result, Error> { + let mut xp = vec![]; + for (&balance, &rate) in balances.iter().zip(rates.iter()) { + xp.push(Self::checked_mul_div_u128( + &T::HigherPrecisionBalance::from(balance), + &T::HigherPrecisionBalance::from(rate), + Self::PRECISION, + )?); + } + Ok(xp) + } + + pub fn get_dx_xp( + pool: &PoolInfoOf, + i: usize, + j: usize, + dy: T::Balance, + xp: Vec, + ) -> Result> { + let d = Self::get_invariant(&xp, pool.amp_coeff)?; + + let mut x = xp[i]; + let mut y = xp[j]; + for _i in 0..255 { + let x_prev = x; + let dyn_fee = Self::dynamic_fee( + &Self::checked_add_div_2(&xp[i], &x)?, + &Self::checked_add_div_2(&xp[j], &y)?, + )?; + let dy_fee = Self::checked_mul_div_u128( + &T::HigherPrecisionBalance::from(dy), + &T::HigherPrecisionBalance::from(pool.rate_multipliers[j]), + Self::PRECISION, + )? + .checked_add(&One::one()) + .ok_or(Error::::MathOverflow)? + .checked_mul(&T::HigherPrecisionBalance::from(Self::FEE_DENOMINATOR)) + .ok_or(Error::::MathOverflow)? + .checked_div( + &T::HigherPrecisionBalance::from(Self::FEE_DENOMINATOR) + .checked_sub(&dyn_fee) + .ok_or(Error::::MathOverflow)?, + ) + .ok_or(Error::::MathOverflow)?; + + y = xp[j].checked_sub(&dy_fee).ok_or(Error::::MathOverflow)?; + x = Self::get_y(j, i, &y, &xp, pool.amp_coeff, &d)?; + + // if we don't have dynamic fee we can return immediatelly, otherwise loop with adjusted fee + if !Self::has_dynamic_fee() || Self::check_diff_le_one(&x, &x_prev) { + return Ok(Self::checked_mul_div_to_balance( + &x.checked_sub(&xp[i]).ok_or(Error::::MathOverflow)?, + pool.rate_multipliers[i], + )?); + } + } + + Err(Error::::UnexpectedFailure) + } + + fn get_dy_xp( + pool: &PoolInfoOf, + i: usize, + j: usize, + dx: T::Balance, + xp: Vec, + ) -> Result> { + let (dy, dy_fee) = Self::calc_dy( + i, + j, + T::HigherPrecisionBalance::from(dx), + pool.amp_coeff, + &xp, + pool.rate_multipliers.to_vec(), + )?; + + Self::checked_mul_div_to_balance( + &dy.checked_sub(&dy_fee).ok_or(Error::::MathOverflow)?, + pool.rate_multipliers[j], + ) + } + + /// Computes the Stable Swap invariant (D). + /// + /// The invariant is defined as follows: + /// + /// ```text + /// A * sum(x_i) * n**n + D = A * D * n**n + D**(n+1) / (n**n * prod(x_i)) + /// ``` + /// Converging solution: + /// ```text + /// D[j+1] = (A * n**n * sum(x_i) - D[j]**(n+1) / (n**n prod(x_i))) / (A * n**n - 1) + /// ``` + fn get_invariant( + xp: &Vec, + amp: u128, + ) -> Result> { + let n = T::HigherPrecisionBalance::from(xp.len() as u128); + let amp = T::HigherPrecisionBalance::from(amp); + let mut sum = T::HigherPrecisionBalance::zero(); + for balance in xp.iter() { + sum = sum.checked_add(balance).ok_or(Error::::MathOverflow)?; + } + + if sum == Zero::zero() { + return Ok(Zero::zero()); + } + + let mut d = sum; + // len will allways be less then u32::MAX + let ann = amp.checked_mul(&n).ok_or(Error::::MathOverflow)?; + + for _ in 0..256 { + let mut d_p = d; + for b in xp.iter() { + d_p = d_p + .checked_mul(&d) + .ok_or(Error::::MathOverflow)? + .checked_div(b) + .ok_or(Error::::MathOverflow)?; + } + let nn = checked_pow(n, xp.len()).ok_or(Error::::MathOverflow)?; + d_p = d_p.checked_div(&nn).ok_or(Error::::MathOverflow)?; + + let d_prev = d; + // (Ann * S / A_PRECISION + D_P * N_COINS) * D / ((Ann - A_PRECISION) * D / A_PRECISION + (N_COINS + 1) * D_P) + d = Self::checked_mul_div_u128(&ann, &sum, Self::A_PRECISION)? + .checked_add(&d_p.checked_mul(&n).ok_or(Error::::MathOverflow)?) + .ok_or(Error::::MathOverflow)? + .checked_mul(&d) + .ok_or(Error::::MathOverflow)? + .checked_div( + &Self::checked_mul_div_u128( + &ann.checked_sub(&T::HigherPrecisionBalance::from(Self::A_PRECISION)) + .ok_or(Error::::MathOverflow)?, + &d, + Self::A_PRECISION, + )? + .checked_add( + &n.checked_add(&One::one()) + .ok_or(Error::::MathOverflow)? + .checked_mul(&d_p) + .ok_or(Error::::MathOverflow)?, + ) + .ok_or(Error::::MathOverflow)?, + ) + .ok_or(Error::::MathOverflow)?; + + if Self::check_diff_le_one(&d, &d_prev) { + return Ok(d); + } + } + + // converges in few iters, should not happen + // if it does, pool is broken, users should remove liquidity + Err(Error::::PoolInvariantBroken) + } + + /// Calculate x[j] if one makes x[i] = x + /// + /// Done by solving quadratic equation iteratively. + /// x_1**2 + x_1 * (sum' - (A*n**n - 1) * D / (A * n**n)) = D ** (n + 1) / (n ** (2 * n) * prod' * A) + /// x_1**2 + b*x_1 = c + /// x_1 = (x_1**2 + c) / (2*x_1 + b) + /// + /// x in the input is converted to the same price/precision + fn get_y( + i: usize, + j: usize, + x: &T::HigherPrecisionBalance, + xp: &Vec, + amp: u128, + d: &T::HigherPrecisionBalance, + ) -> Result> { + // ensure 0 < i,j < max assets, i != j, ...should not happen due previous checks + ensure!( + i != j && j >= 0 as usize && j < T::MaxAssetsInPool::get() as usize, + Error::::UnexpectedFailure + ); + ensure!( + i >= 0 as usize && i < T::MaxAssetsInPool::get() as usize, + Error::::UnexpectedFailure + ); + + let n = T::HigherPrecisionBalance::from(xp.len() as u128); + let amp = T::HigherPrecisionBalance::from(amp); + let ann = amp.checked_mul(&n).ok_or(Error::::MathOverflow)?; + + let mut sum = T::HigherPrecisionBalance::zero(); + let mut c = *d; + + for _i in 0..xp.len() { + let mut _x = Zero::zero(); + if _i == i { + _x = *x + } else if _i != j { + _x = xp[_i] + } else { + continue + }; + + sum = sum.checked_add(&_x).ok_or(Error::::MathOverflow)?; + c = c + .checked_mul(&d) + .ok_or(Error::::MathOverflow)? + .checked_div(&_x.checked_mul(&n).ok_or(Error::::MathOverflow)?) + .ok_or(Error::::MathOverflow)?; + } + + Self::solve_y(&n, &ann, d, &c, &sum) + } + + /// Calculate x[i] if one reduces D from being calculated for xp to D + /// + /// x in the input is converted to the same price/precision + fn get_y_d( + i: usize, + xp: &Vec, + amp: u128, + d: &T::HigherPrecisionBalance, + ) -> Result> { + ensure!( + i >= 0 as usize && i < T::MaxAssetsInPool::get() as usize, + Error::::UnexpectedFailure + ); + + let n = T::HigherPrecisionBalance::from(xp.len() as u128); + let amp = T::HigherPrecisionBalance::from(amp); + let ann = amp.checked_mul(&n).ok_or(Error::::MathOverflow)?; + + let mut sum = T::HigherPrecisionBalance::zero(); + let mut c = *d; + + for _i in 0..xp.len() { + let mut _x = Zero::zero(); + if _i != i { + _x = xp[_i] + } else { + continue + }; + + sum = sum.checked_add(&_x).ok_or(Error::::MathOverflow)?; + c = c + .checked_mul(d) + .ok_or(Error::::MathOverflow)? + .checked_div(&_x.checked_mul(&n).ok_or(Error::::MathOverflow)?) + .ok_or(Error::::MathOverflow)?; + } + Self::solve_y(&n, &ann, d, &c, &sum) + } + + /// Done by solving quadratic equation iteratively. + /// x_1**2 + x_1 * (sum' - (A*n**n - 1) * D / (A * n**n)) = D ** (n + 1) / (n ** (2 * n) * prod' * A) + /// x_1**2 + b*x_1 = c + /// x_1 = (x_1**2 + c) / (2*x_1 + b) + fn solve_y( + n: &T::HigherPrecisionBalance, + ann: &T::HigherPrecisionBalance, + d: &T::HigherPrecisionBalance, + c: &T::HigherPrecisionBalance, + sum: &T::HigherPrecisionBalance, + ) -> Result> { + let c = c + .checked_mul(d) + .ok_or(Error::::MathOverflow)? + .checked_mul(&T::HigherPrecisionBalance::from(Self::A_PRECISION)) + .ok_or(Error::::MathOverflow)? + .checked_div(&ann.checked_mul(n).ok_or(Error::::MathOverflow)?) + .ok_or(Error::::MathOverflow)?; + let b = sum + .checked_add( + &d.checked_mul(&T::HigherPrecisionBalance::from(Self::A_PRECISION)) + .ok_or(Error::::MathOverflow)? + .checked_div(ann) + .ok_or(Error::::MathOverflow)?, + ) + .ok_or(Error::::MathOverflow)?; + let mut y = *d; + + for _ in 0..256 { + let y_prev = y; + y = y + .checked_mul(&y) + .ok_or(Error::::MathOverflow)? + .checked_add(&c) + .ok_or(Error::::MathOverflow)? + .checked_div( + &y.checked_mul(&T::HigherPrecisionBalance::from(2_u32)) + .ok_or(Error::::MathOverflow)? + .checked_add(&b) + .ok_or(Error::::MathOverflow)? + .checked_sub(d) + .ok_or(Error::::MathOverflow)?, + ) + .ok_or(Error::::MathOverflow)?; + + if Self::check_diff_le_one(&y, &y_prev) { + return Ok(y); + } + } + + Err(Error::::UnexpectedFailure) + } + + fn calc_withdraw_one( + pool_account: &T::AccountId, + pool: &PoolInfoOf, + asset_id: T::CurrencyId, + burn_amount: T::Balance, + ) -> Result<(T::Balance, T::Balance), Error> { + let n = T::HigherPrecisionBalance::from(pool.assets.len() as u128); + let i = pool.get_asset_index(asset_id)?; + + let (_, xp) = Self::get_balances_xp_pool(pool_account, pool)?; + let (_, d_0) = Self::get_invariant_pool(pool_account, pool)?; + let total_supply = T::Currency::total_issuance(pool.lp_token); + + let d_1 = d_0 + .checked_sub( + &T::HigherPrecisionBalance::from(burn_amount) + .checked_mul(&d_0) + .ok_or(Error::::MathOverflow)? + .checked_div(&T::HigherPrecisionBalance::from(total_supply)) + .ok_or(Error::::MathOverflow)?, + ) + .ok_or(Error::::MathOverflow)?; + + let new_y = Self::get_y_d(i, &xp, pool.amp_coeff, &d_1)?; + + let ys = d_0 + .checked_add(&d_1) + .ok_or(Error::::MathOverflow)? + .checked_div( + &n.checked_mul(&T::HigherPrecisionBalance::from(2_u32)) + .ok_or(Error::::MathOverflow)?, + ) + .ok_or(Error::::MathOverflow)?; + + let mut xp_reduced = vec![]; + for j in 0..pool.assets.len() { + let xpjdd = xp[j] + .checked_mul(&d_1) + .ok_or(Error::::MathOverflow)? + .checked_div(&d_0) + .ok_or(Error::::MathOverflow)?; + let xavg: T::HigherPrecisionBalance; + let dx_exp: T::HigherPrecisionBalance; + if i == j { + dx_exp = xpjdd.checked_sub(&new_y).ok_or(Error::::MathOverflow)?; + xavg = Self::checked_add_div_2(&xp[j], &new_y)?; + } else { + dx_exp = xp[j].checked_sub(&xpjdd).ok_or(Error::::MathOverflow)?; + xavg = xp[j] + } + let dyn_fee = Self::dynamic_fee_base(&xavg, &ys, &n)?; + xp_reduced.push( + xp[j] + .checked_sub(&Self::checked_mul_div_u128( + &dyn_fee, + &dx_exp, + Self::FEE_DENOMINATOR, + )?) + .ok_or(Error::::MathOverflow)?, + ) + } + + let dy = xp_reduced[i] + .checked_sub(&Self::get_y_d(i, &xp_reduced, pool.amp_coeff, &d_1)?) + .ok_or(Error::::MathOverflow)?; + let dy_0 = Self::checked_mul_div_to_balance( + &xp[i].checked_sub(&new_y).ok_or(Error::::MathOverflow)?, + pool.rate_multipliers[i], + )?; + let dy = Self::checked_mul_div_to_balance( + &dy.checked_sub(&One::one()) // less for roudning errors + .ok_or(Error::::MathOverflow)?, + pool.rate_multipliers[i], + )?; + let fee = T::HigherPrecisionBalance::from( + dy_0.checked_sub(&dy).ok_or(Error::::MathOverflow)?, + ); + let trsy_fee = + Self::checked_mul_div_u128(&fee, &Self::get_trsy_fee(), Self::FEE_DENOMINATOR)? + .try_into() + .map_err(|_| Error::::MathOverflow)?; + + Ok((dy, trsy_fee)) + } + + fn calc_dy( + i: usize, + j: usize, + dx: T::HigherPrecisionBalance, + amp: u128, + xp: &Vec, + rates: Vec, + ) -> Result<(T::HigherPrecisionBalance, T::HigherPrecisionBalance), Error> { + let x = Self::checked_mul_div_u128( + &dx, + &T::HigherPrecisionBalance::from(rates[i]), + Self::PRECISION, + )? + .checked_add(&xp[i]) + .ok_or(Error::::MathOverflow)?; + + let d = Self::get_invariant(&xp, amp)?; + let y = Self::get_y(i, j, &x, xp, amp, &d)?; + // -1 in case of rounding error + let dy = xp[j] + .checked_sub(&y) + .ok_or(Error::::MathOverflow)? + .checked_sub(&One::one()) + .ok_or(Error::::MathOverflow)?; + + // fees + let dyn_fee = Self::dynamic_fee( + &Self::checked_add_div_2(&xp[i], &x)?, + &Self::checked_add_div_2(&xp[j], &y)?, + )?; + let dy_fee = Self::checked_mul_div_u128(&dy, &dyn_fee, Self::FEE_DENOMINATOR)?; + Ok((dy, dy_fee)) + } + + fn calc_imbalanced_liquidity_fees( + pool: &PoolInfoOf, + n: &T::HigherPrecisionBalance, + d_0: &T::HigherPrecisionBalance, + d_1: &T::HigherPrecisionBalance, + balances_0: &Vec, + balances_1: &Vec, + ) -> Result<(T::HigherPrecisionBalance, Vec), Error> { + let mut fees = vec![]; + let ys = d_0 + .checked_add(d_1) + .ok_or(Error::::MathOverflow)? + .checked_div(n) + .ok_or(Error::::MathOverflow)?; + + let mut balances_mint = balances_1.clone(); + + for i in 0..pool.assets.len() { + let ideal_balance = d_1 + .checked_mul(&balances_0[i]) + .ok_or(Error::::MathOverflow)? + .checked_div(d_0) + .ok_or(Error::::MathOverflow)?; + + let diff = if ideal_balance > balances_1[i] { + ideal_balance - balances_1[i] + } else { + balances_1[i] - ideal_balance + }; + + let xs = Self::checked_mul_div_u128( + &balances_0[i].checked_add(&balances_1[i]).ok_or(Error::::MathOverflow)?, + &T::HigherPrecisionBalance::from(pool.rate_multipliers[i]), + Self::PRECISION, + )?; + + let dyn_fee = Self::dynamic_fee_base(&xs, &ys, n)?; + fees.push(Self::checked_mul_div_u128(&diff, &dyn_fee, Self::FEE_DENOMINATOR)?); + + // this can fail if the fee is bigger then available balance + // eg. pool initialized with low liquidity and adding big amount for single coin + // would cause fees on other assets larger then initial liquidity + balances_mint[i] = + balances_mint[i].checked_sub(&fees[i]).ok_or(Error::::MathOverflow)?; + } + + let xp = Self::xp(&pool.rate_multipliers, &balances_mint)?; + let d_1 = Self::get_invariant(&xp, pool.amp_coeff)?; + + Ok((d_1, fees)) + } + + // math + fn checked_add_div_2( + a: &T::HigherPrecisionBalance, + b: &T::HigherPrecisionBalance, + ) -> Result> { + a.checked_add(b) + .ok_or(Error::::MathOverflow)? + .checked_div(&T::HigherPrecisionBalance::from(2_u32)) + .ok_or(Error::::MathOverflow) + } + + fn checked_mul_div_u128( + a: &T::HigherPrecisionBalance, + b: &T::HigherPrecisionBalance, + d: u128, + ) -> Result> { + a.checked_mul(b) + .ok_or(Error::::MathOverflow)? + .checked_div(&T::HigherPrecisionBalance::from(d)) + .ok_or(Error::::MathOverflow) + } + + fn checked_mul_div_to_balance( + a: &T::HigherPrecisionBalance, + rate: T::Balance, + ) -> Result> { + a.checked_mul(&T::HigherPrecisionBalance::from(Self::PRECISION)) + .ok_or(Error::::MathOverflow)? + .checked_div(&T::HigherPrecisionBalance::from(rate)) + .ok_or(Error::::MathOverflow)? + .try_into() + .map_err(|_| Error::::MathOverflow) + } + + fn check_diff_le_one(a: &T::HigherPrecisionBalance, b: &T::HigherPrecisionBalance) -> bool { + if a.checked_sub(&b).map_or(false, |diff| diff.le(&One::one())) { + return true; + } + if b.checked_sub(&a).map_or(false, |diff| diff.le(&One::one())) { + return true; + } + return false; + } + } +} + +impl Inspect for Pallet { + type CurrencyId = T::CurrencyId; + type Balance = T::Balance; + + fn get_pool_info(pool_id: Self::CurrencyId) -> Option> { + let info = Pools::::get(pool_id)?; + let asset1 = info.assets.get(0)?; + let asset2 = info.assets.get(1)?; + Some((*asset1, *asset2)) + } + + fn get_pool_reserves(pool_id: Self::CurrencyId) -> Option> { + let reserves = Self::get_pool_reserves(&pool_id).ok()?; + let balance1 = reserves.get(0)?; + let balance2 = reserves.get(1)?; + Some((*balance1, *balance2)) + } + + fn get_non_empty_pools() -> Option> { + let result = Pools::::iter_values() + .map(|v| v.lp_token) + .filter(|v| !T::Currency::total_issuance((*v).into()).is_zero()) + .collect(); + + Some(result) + } +} + +impl ComputeBalances for Pallet { + fn get_dy( + pool_id: Self::CurrencyId, + asset_in: Self::CurrencyId, + asset_out: Self::CurrencyId, + dx: Self::Balance, + ) -> Option { + Self::get_dy(&pool_id, asset_in, asset_out, dx).ok() + } + + fn get_dy_with_impact( + pool_id: Self::CurrencyId, + asset_in: Self::CurrencyId, + asset_out: Self::CurrencyId, + dx: Self::Balance, + ) -> Option<(Self::Balance, Self::Balance)> { + Self::get_dy_with_impact(&pool_id, asset_in, asset_out, dx).ok() + } + + fn get_dx( + pool_id: Self::CurrencyId, + asset_in: Self::CurrencyId, + asset_out: Self::CurrencyId, + dy: Self::Balance, + ) -> Option { + Self::get_dx(&pool_id, asset_in, asset_out, dy).ok() + } + + fn get_dx_with_impact( + pool_id: Self::CurrencyId, + asset_in: Self::CurrencyId, + asset_out: Self::CurrencyId, + dy: Self::Balance, + ) -> Option<(Self::Balance, Self::Balance)> { + Self::get_dx_with_impact(&pool_id, asset_in, asset_out, dy).ok() + } + + fn get_burn_amounts( + pool_id: Self::CurrencyId, + lp_burn_amount: Self::Balance, + ) -> Option<(Self::Balance, Self::Balance)> { + let amounts = Self::get_burn_amounts(&pool_id, lp_burn_amount).ok()?; + let asset1 = amounts.get(0)?; + let asset2 = amounts.get(1)?; + Some((*asset1, *asset2)) + } + + fn get_mint_amount( + pool_id: Self::CurrencyId, + amounts: (Self::Balance, Self::Balance), + ) -> Option { + Self::calc_lp_token_amount(&pool_id, vec![amounts.0, amounts.1], true).ok() + } + + fn get_expected_amount_for_mint( + pool_id: Self::CurrencyId, + asset_id: Self::CurrencyId, + amount: Self::Balance, + ) -> Option { + let info = Pools::::get(pool_id)?; + let asset1 = info.assets.get(0)?; + let asset2 = info.assets.get(1)?; + let exp1 = info.rate_multipliers.get(0)?; + let exp2 = info.rate_multipliers.get(1)?; + + let (same, other) = if asset_id == *asset1 { + (exp1, exp2) + } else if asset_id == *asset2 { + (exp2, exp1) + } else { + return None; + }; + + T::HigherPrecisionBalance::from(amount) + .checked_mul(&T::HigherPrecisionBalance::from(*same))? + .checked_div(&T::HigherPrecisionBalance::from(*other))? + .try_into() + .ok() + } +} + +impl Mutate for Pallet { + fn create_pool( + sender: &T::AccountId, + asset_1: Self::CurrencyId, + amount_1: Self::Balance, + asset_2: Self::CurrencyId, + amount_2: Self::Balance, + ) -> Result { + let assets = vec![asset_1, asset_2]; + let ten = T::Balance::from(10_u32); + + ensure!(!amount_1.is_zero(), Error::::InitialLiquidityZeroAmount); + ensure!(!amount_2.is_zero(), Error::::InitialLiquidityZeroAmount); + + let (rate_1_mul, rate_2_mul) = if amount_1 == amount_2 { + (One::one(), One::one()) + } else { + let exp = |amount: Self::Balance| { + let mut i = 0_usize; + let mut pow_10 = T::Balance::one() * ten; + loop { + if amount % pow_10 != Zero::zero() { + break; + } + i += 1; + if amount / pow_10 < ten { + break; + } + pow_10 *= ten; + } + i + }; + + let exp1 = exp(amount_1); + let exp2 = exp(amount_2); + let min = exp1.min(exp2); + let exp = checked_pow(ten, min).ok_or(Error::::MathOverflow)?; + + (amount_1 / exp, amount_2 / exp) + }; + + // the max rate cannot be more than 1e18 + let precision = + checked_pow(ten, Self::PRECISION_EXP as usize).ok_or(Error::::MathOverflow)?; + ensure!(rate_1_mul <= precision, Error::::InitialPoolRateOutOfRange); + ensure!(rate_2_mul <= precision, Error::::InitialPoolRateOutOfRange); + + let rate_1 = rate_2_mul.checked_mul(&precision).ok_or(Error::::MathOverflow)?; + let rate_2 = rate_1_mul.checked_mul(&precision).ok_or(Error::::MathOverflow)?; + + let rates = vec![rate_1, rate_2]; + let info = Self::do_create_pool(sender, assets, rates, T::DefaultApmCoeff::get())?; + + // asset ids are ordered + let asset_pool_1 = *info.assets.get(0).ok_or(Error::::UnexpectedFailure)?; + let amounts = if asset_pool_1 == asset_1 { + vec![amount_1, amount_2] + } else { + vec![amount_2, amount_1] + }; + + let _ = Self::do_add_liquidity(&sender, info.lp_token, amounts, Zero::zero())?; + + Ok(info.lp_token) + } + + fn add_liquidity( + sender: &T::AccountId, + pool_id: Self::CurrencyId, + amounts: (Self::Balance, Self::Balance), + min_amount_lp_tokens: Self::Balance, + ) -> Result { + let amounts = vec![amounts.0, amounts.1]; + let (minted, _) = Self::do_add_liquidity(&sender, pool_id, amounts, min_amount_lp_tokens)?; + Ok(minted) + } + + fn remove_liquidity( + sender: &T::AccountId, + pool_id: Self::CurrencyId, + liquidity_asset_amount: Self::Balance, + min_asset_amounts_out: (Self::Balance, Self::Balance), + ) -> Result<(T::Balance, T::Balance), DispatchError> { + let min_amounts = vec![min_asset_amounts_out.0, min_asset_amounts_out.1]; + let amounts = + Self::do_remove_liquidity(&sender, pool_id, liquidity_asset_amount, min_amounts)?; + let asset1 = amounts.get(0).ok_or(Error::::UnexpectedFailure)?; + let asset2 = amounts.get(1).ok_or(Error::::UnexpectedFailure)?; + Ok((*asset1, *asset2)) + } + + fn swap( + sender: &T::AccountId, + pool_id: Self::CurrencyId, + asset_in: Self::CurrencyId, + asset_out: Self::CurrencyId, + amount_in: Self::Balance, + min_amount_out: Self::Balance, + ) -> Result, DispatchError> { + Self::do_swap(sender, pool_id, asset_in, asset_out, amount_in, min_amount_out) + } +} diff --git a/gasp-node/pallets/stable-swap/src/mock.rs b/gasp-node/pallets/stable-swap/src/mock.rs new file mode 100644 index 000000000..ce62f49fc --- /dev/null +++ b/gasp-node/pallets/stable-swap/src/mock.rs @@ -0,0 +1,179 @@ +// Copyright (C) 2020 Mangata team + +use super::*; +use crate as swap; + +use frame_support::{ + construct_runtime, derive_impl, parameter_types, + traits::{ + tokens::currency::MultiTokenCurrency, ConstU128, ConstU32, Contains, ExistenceRequirement, + Nothing, + }, + PalletId, +}; +use frame_system as system; + +pub use orml_tokens::{MultiTokenCurrencyAdapter, MultiTokenCurrencyExtended}; +use orml_traits::parameter_type_with_key; + +use sp_runtime::{traits::AccountIdConversion, BuildStorage}; + +use std::convert::TryFrom; + +pub(crate) type AccountId = u128; +pub(crate) type Amount = i128; +pub(crate) type Balance = u128; +pub(crate) type TokenId = u32; + +type Block = frame_system::mocking::MockBlock; + +construct_runtime!( + pub enum Test { + System: frame_system, + Tokens: orml_tokens, + StableSwap: swap, + } +); + +#[derive_impl(frame_system::config_preludes::TestDefaultConfig as frame_system::DefaultConfig)] +impl frame_system::Config for Test { + type AccountId = AccountId; + type Block = Block; + type Lookup = sp_runtime::traits::IdentityLookup; +} + +parameter_type_with_key! { + pub ExistentialDeposits: |currency_id: TokenId| -> Balance { + match currency_id { + _ => 0, + } + }; +} + +pub struct DustRemovalWhitelist; +impl Contains for DustRemovalWhitelist { + fn contains(a: &AccountId) -> bool { + *a == TreasuryAccount::get() + } +} + +parameter_types! { + pub const TreasuryPalletId: PalletId = PalletId(*b"py/trsry"); + pub const BnbTreasurySubAccDerive: [u8; 4] = *b"bnbt"; + pub TreasuryAccount: AccountId = TreasuryPalletId::get().into_account_truncating(); + pub const MaxLocks: u32 = 50; +} + +impl orml_tokens::Config for Test { + type RuntimeEvent = RuntimeEvent; + type Balance = Balance; + type Amount = Amount; + type CurrencyId = TokenId; + type WeightInfo = (); + type ExistentialDeposits = ExistentialDeposits; + type MaxLocks = MaxLocks; + type DustRemovalWhitelist = DustRemovalWhitelist; + type MaxReserves = (); + type ReserveIdentifier = [u8; 8]; + type CurrencyHooks = (); + type NontransferableTokens = Nothing; + type NontransferableTokensAllowList = Nothing; +} + +impl swap::Config for Test { + type RuntimeEvent = RuntimeEvent; + type Currency = MultiTokenCurrencyAdapter; + type Balance = Balance; + type HigherPrecisionBalance = sp_core::U256; + type CurrencyId = TokenId; + type TreasuryPalletId = TreasuryPalletId; + type BnbTreasurySubAccDerive = BnbTreasurySubAccDerive; + type MarketTotalFee = ConstU128<30_000_000>; + type MarketTreasuryFeePart = ConstU128<3_333_333_334>; + type MarketBnBFeePart = ConstU128<5_000_000_000>; + type MaxApmCoeff = ConstU128<1_000_000>; + type DefaultApmCoeff = ConstU128<1_000>; + type MaxAssetsInPool = ConstU32<8>; + type WeightInfo = (); +} + +impl Pallet +where + ::Currency: + MultiTokenCurrencyExtended, +{ + pub fn balance(id: TokenId, who: AccountId) -> Balance { + ::Currency::free_balance(id.into(), &who).into() + } + pub fn total_supply(id: TokenId) -> Balance { + ::Currency::total_issuance(id.into()).into() + } + pub fn transfer( + currency_id: TokenId, + source: AccountId, + dest: AccountId, + value: Balance, + ) -> DispatchResult { + ::Currency::transfer( + currency_id, + &source, + &dest, + value, + ExistenceRequirement::KeepAlive, + ) + } + pub fn create_new_token(who: &AccountId, amount: Balance) -> TokenId { + ::Currency::create(who, amount).expect("Token creation failed") + } + + pub fn mint_token(token_id: TokenId, who: &AccountId, amount: Balance) { + ::Currency::mint(token_id, who, amount).expect("Token minting failed") + } +} + +// This function basically just builds a genesis storage key/value store according to +// our desired mockup. +pub fn new_test_ext() -> sp_io::TestExternalities { + let mut ext: sp_io::TestExternalities = + system::GenesisConfig::::default().build_storage().unwrap().into(); + ext.execute_with(|| { + System::set_block_number(1); + }); + ext +} + +pub(crate) fn events() -> Vec> { + System::events() + .into_iter() + .map(|r| r.event) + .filter_map(|e| if let RuntimeEvent::StableSwap(inner) = e { Some(inner) } else { None }) + .collect::>() +} + +/// Compares the system events with passed in events +/// Prints highlighted diff iff assert_eq fails +#[macro_export] +macro_rules! assert_eq_events { + ($events:expr) => { + match &$events { + e => similar_asserts::assert_eq!(*e, $crate::mock::events()), + } + }; +} + +/// Panics if an event is not found in the system log of events +#[macro_export] +macro_rules! assert_event_emitted { + ($event:expr) => { + match &$event { + e => { + assert!( + $crate::mock::events().iter().find(|x| *x == e).is_some(), + "Event {:?} was not found in events: \n {:?}", + e, + crate::mock::events() + ); + }, + } + }; +} diff --git a/gasp-node/pallets/stable-swap/src/tests.rs b/gasp-node/pallets/stable-swap/src/tests.rs new file mode 100644 index 000000000..4c0f0e244 --- /dev/null +++ b/gasp-node/pallets/stable-swap/src/tests.rs @@ -0,0 +1,414 @@ +use std::vec; + +use frame_support::{assert_err, assert_ok}; +use sp_runtime::BoundedVec; + +use crate::{assert_event_emitted, mock::*, Config, Error, Event, Pallet}; + +const UNIT: u128 = 10_u128.pow(18); + +fn prep_pool() -> (AccountId, Balance, Balance) { + let account: AccountId = 2; + let amount: Balance = 1_000_000 * UNIT; + let mint: Balance = 1_000 * UNIT; + + StableSwap::create_new_token(&account, amount); + StableSwap::create_new_token(&account, amount); + StableSwap::create_new_token(&account, amount); + StableSwap::create_pool( + RuntimeOrigin::signed(account), + vec![0, 1, 2], + vec![UNIT, UNIT, UNIT], + 200, + ) + .unwrap(); + + assert_ok!(StableSwap::add_liquidity( + RuntimeOrigin::signed(account), + 3, + vec![mint, mint, mint], + 0, + )); + + return (account, amount, mint) +} + +// create pool tests + +#[test] +fn create_pool_should_work() { + new_test_ext().execute_with(|| { + let account: AccountId = 2; + let amount: Balance = 1_000_000_000; + let rate: u128 = 10_u128.pow(18); + + StableSwap::create_new_token(&account, amount); + StableSwap::create_new_token(&account, amount); + StableSwap::create_new_token(&account, amount); + + StableSwap::create_pool( + RuntimeOrigin::signed(account), + vec![0, 1, 2], + vec![rate, rate, rate], + 100, + ) + .unwrap(); + + assert!(::Currency::exists(3)); + }); +} + +#[test] +fn create_pool_should_fail_on_nonexistent_asset() { + new_test_ext().execute_with(|| { + let account: AccountId = 2; + let amount: Balance = 1_000_000_000; + let rate: u128 = 10_u128.pow(18); + + StableSwap::create_new_token(&account, amount); + StableSwap::create_new_token(&account, amount); + + assert_err!( + StableSwap::create_pool( + RuntimeOrigin::signed(account), + vec![0, 1, 2], + vec![rate, rate, rate], + 100 + ), + Error::::AssetDoesNotExist, + ); + }); +} + +#[test] +fn create_pool_should_fail_on_same_asset() { + new_test_ext().execute_with(|| { + let account: AccountId = 2; + let amount: Balance = 1_000_000_000; + let rate: u128 = 10_u128.pow(18); + + StableSwap::create_new_token(&account, amount); + StableSwap::create_new_token(&account, amount); + + assert_err!( + StableSwap::create_pool( + RuntimeOrigin::signed(account), + vec![0, 1, 1], + vec![rate, rate, rate], + 100 + ), + Error::::SameAsset, + ); + }); +} + +#[test] +fn create_pool_should_fail_on_too_many_assets() { + new_test_ext().execute_with(|| { + let account: AccountId = 2; + let amount: Balance = 1_000_000_000; + let rate: u128 = 10_u128.pow(18); + + StableSwap::create_new_token(&account, amount); + StableSwap::create_new_token(&account, amount); + + assert_err!( + StableSwap::create_pool( + RuntimeOrigin::signed(account), + vec![0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1], + vec![rate, rate, rate], + 100 + ), + Error::::TooManyAssets, + ); + }); +} + +#[test] +fn create_pool_should_fail_on_coeff_out_out_range() { + new_test_ext().execute_with(|| { + let account: AccountId = 2; + let amount: Balance = 1_000_000_000; + let rate: u128 = 10_u128.pow(18); + + StableSwap::create_new_token(&account, amount); + StableSwap::create_new_token(&account, amount); + + assert_err!( + StableSwap::create_pool( + RuntimeOrigin::signed(account), + vec![0, 1], + vec![rate, rate, rate], + 0 + ), + Error::::AmpCoeffOutOfRange, + ); + assert_err!( + StableSwap::create_pool( + RuntimeOrigin::signed(account), + vec![0, 1], + vec![rate, rate, rate], + u128::MAX + ), + Error::::AmpCoeffOutOfRange, + ); + }); +} + +// add liquidity tests +#[test] +fn add_liquidity_should_work() { + new_test_ext().execute_with(|| { + let (account, _, mint) = prep_pool(); + + // check first balanced add + assert_event_emitted!(Event::LiquidityMinted { + who: 2, + pool_id: 3, + amounts_provided: BoundedVec::truncate_from(vec![mint, mint, mint]), + lp_token: 3, + lp_token_minted: 3_000 * UNIT, + total_supply: 3_000 * UNIT, + fees: BoundedVec::truncate_from(vec![]), + }); + + assert_eq!(StableSwap::get_virtual_price(&3).unwrap(), 1 * UNIT); + + let amounts = vec![5 * mint, mint, 40 * mint]; + let expected = StableSwap::calc_lp_token_amount(&3, amounts.clone(), true).unwrap(); + + // imbalanced add, should have fees + assert_ok!(StableSwap::add_liquidity( + RuntimeOrigin::signed(account), + 3, + amounts.clone(), + 1, + )); + + assert_event_emitted!(Event::LiquidityMinted { + who: 2, + pool_id: 3, + amounts_provided: BoundedVec::truncate_from(amounts), + lp_token: 3, + lp_token_minted: expected, + total_supply: 3_000 * UNIT + 45322880144288283584179, + fees: BoundedVec::truncate_from(vec![ + 12498711677479364060, + 21094110011003080268, + 30692298654602638774 + ]), + }); + + // half of fees goes to treasury + assert_eq!(StableSwap::balance(0, TreasuryAccount::get()), 4166237226659702131); + assert_eq!(StableSwap::balance(1, TreasuryAccount::get()), 7031370005073967423); + assert_eq!(StableSwap::balance(2, TreasuryAccount::get()), 10230766220247032834); + assert_eq!(StableSwap::get_virtual_price(&3).unwrap(), 1000961844675256200); + }); +} + +#[test] +fn add_liquidity_balanced_for_single_asset_minimal_fees() { + new_test_ext().execute_with(|| { + let account: AccountId = 2; + let amount: Balance = 1_000_000 * UNIT; + + StableSwap::create_new_token(&account, amount); + StableSwap::create_new_token(&account, amount); + StableSwap::create_new_token(&account, amount); + StableSwap::create_pool( + RuntimeOrigin::signed(account), + vec![0, 1, 2], + vec![UNIT, UNIT, UNIT], + 200, + ) + .unwrap(); + + assert_ok!(StableSwap::add_liquidity( + RuntimeOrigin::signed(account), + 3, + vec![2 * UNIT, 3 * UNIT, 10 * UNIT], + 1, + )); + + let input_0 = 10_000 * UNIT; + let reserves = StableSwap::get_pool_reserves(&3).unwrap(); + let rate = input_0 / reserves[0]; + let amounts = vec![input_0, reserves[1] * rate, reserves[2] * rate]; + let exp = Pallet::::calc_lp_token_amount(&3, amounts.clone(), true).unwrap(); + + assert_ok!(StableSwap::add_liquidity( + RuntimeOrigin::signed(account), + 3, + amounts.clone(), + 1, + )); + + assert_event_emitted!(Event::LiquidityMinted { + who: 2, + pool_id: 3, + amounts_provided: BoundedVec::truncate_from(amounts), + lp_token: 3, + lp_token_minted: exp, + total_supply: 74881186777958934408403, + fees: BoundedVec::truncate_from(vec![0, 0, 1]), + }); + }); +} + +// remove liquidity +#[test] +fn remove_liquidity_one_asset_should_work() { + new_test_ext().execute_with(|| { + let (account, _, _) = prep_pool(); + let total_supply = StableSwap::total_supply(3); + + assert_ok!(StableSwap::remove_liquidity_one_asset( + RuntimeOrigin::signed(account), + 3, + 0, + 10 * UNIT, + 0 + )); + + assert_event_emitted!(Event::LiquidityBurnedOne { + who: 2, + pool_id: 3, + asset_id: 0, + // a bit less then 10, minus fees + amount: 9_984_833_787_927_106_037, + burned_amount: 10 * UNIT, + total_supply: total_supply - 10 * UNIT, + }); + + assert_eq!(StableSwap::balance(0, TreasuryAccount::get()), 4999816321852351); + assert_eq!(StableSwap::balance(1, TreasuryAccount::get()), 0); + assert_eq!(StableSwap::balance(2, TreasuryAccount::get()), 0); + }); +} + +#[test] +fn remove_liquidity_imbalanced_should_work() { + new_test_ext().execute_with(|| { + let (account, balance, mint) = prep_pool(); + let amounts = vec![mint / 100, mint / 50, mint / 200]; + + assert_eq!(StableSwap::balance(0, 2), balance - mint); + assert_eq!(StableSwap::balance(1, 2), balance - mint); + assert_eq!(StableSwap::balance(2, 2), balance - mint); + + assert_ok!(StableSwap::remove_liquidity_imbalanced( + RuntimeOrigin::signed(account), + 3, + amounts.clone(), + u128::MAX, + )); + + let total_supply = StableSwap::total_supply(3); + assert_event_emitted!(Event::LiquidityBurned { + who: 2, + pool_id: 3, + amounts: BoundedVec::truncate_from(amounts.clone()), + burned_amount: 35019044399900264325, + total_supply, + fees: BoundedVec::truncate_from(vec![ + 1875110298930542, + 9374909700834153, + 7500120299077607, + ]), + }); + + // got what requested + assert_eq!(StableSwap::balance(0, 2), balance - mint + amounts[0]); + assert_eq!(StableSwap::balance(1, 2), balance - mint + amounts[1]); + assert_eq!(StableSwap::balance(2, 2), balance - mint + amounts[2]); + assert_eq!(StableSwap::balance(3, 2), 3 * mint - 35019044399900264325); + + assert_eq!(StableSwap::balance(0, TreasuryAccount::get()), 625036766435188); + assert_eq!(StableSwap::balance(1, TreasuryAccount::get()), 3124969900903044); + assert_eq!(StableSwap::balance(2, TreasuryAccount::get()), 2500040100192543); + }); +} + +#[test] +fn remove_liquidity_to_zero_should_work() { + new_test_ext().execute_with(|| { + let (account, balance, mint) = prep_pool(); + let total_supply = StableSwap::total_supply(3); + + assert_ok!(StableSwap::remove_liquidity( + RuntimeOrigin::signed(account), + 3, + total_supply, + vec![mint, mint, mint], + )); + + assert_event_emitted!(Event::LiquidityBurned { + who: 2, + pool_id: 3, + amounts: BoundedVec::truncate_from(vec![mint, mint, mint]), + burned_amount: 3 * mint, + total_supply: 0, + fees: BoundedVec::truncate_from(vec![]), + }); + + // all back + assert_eq!(StableSwap::balance(0, 2), balance); + assert_eq!(StableSwap::balance(1, 2), balance); + assert_eq!(StableSwap::balance(2, 2), balance); + assert_eq!(StableSwap::balance(3, 2), 0); + + assert_eq!(StableSwap::balance(0, TreasuryAccount::get()), 0); + assert_eq!(StableSwap::balance(1, TreasuryAccount::get()), 0); + assert_eq!(StableSwap::balance(2, TreasuryAccount::get()), 0); + }); +} + +// swaps +#[test] +fn swap_should_work_dy() { + new_test_ext().execute_with(|| { + let (account, _, _) = prep_pool(); + + let dy = StableSwap::get_dy(&3, 0, 2, 100 * UNIT).unwrap(); + + assert_ok!(StableSwap::swap(RuntimeOrigin::signed(account), 3, 0, 2, 100 * UNIT, 0)); + + assert_event_emitted!(Event::AssetsSwapped { + who: 2, + pool_id: 3, + asset_in: 0, + amount_in: 100 * UNIT, + asset_out: 2, + amount_out: dy + }); + + assert_eq!(StableSwap::balance(0, TreasuryAccount::get()), 0); + assert_eq!(StableSwap::balance(1, TreasuryAccount::get()), 0); + assert_eq!(StableSwap::balance(2, TreasuryAccount::get()), 50037401982926047); + }); +} + +#[test] +fn swap_should_work_dx() { + new_test_ext().execute_with(|| { + let (account, _, _) = prep_pool(); + + let dx = StableSwap::get_dx(&3, 0, 2, 100 * UNIT).unwrap(); + + assert_ok!(StableSwap::swap(RuntimeOrigin::signed(account), 3, 0, 2, dx, 0)); + + assert_event_emitted!(Event::AssetsSwapped { + who: 2, + pool_id: 3, + asset_in: 0, + amount_in: dx, + asset_out: 2, + amount_out: 100 * UNIT, + }); + + assert_eq!(StableSwap::balance(0, TreasuryAccount::get()), 0); + assert_eq!(StableSwap::balance(1, TreasuryAccount::get()), 0); + assert_eq!(StableSwap::balance(2, TreasuryAccount::get()), 50213816221710612); + }); +} diff --git a/gasp-node/pallets/stable-swap/src/weights.rs b/gasp-node/pallets/stable-swap/src/weights.rs new file mode 100644 index 000000000..2c25dadb3 --- /dev/null +++ b/gasp-node/pallets/stable-swap/src/weights.rs @@ -0,0 +1,33 @@ +use frame_support::{ + traits::Get, + weights::{constants::RocksDbWeight, Weight}, +}; + +/// Weight functions needed for pallet_stable_pools. +pub trait WeightInfo { + fn create_pool() -> Weight; + fn add_liquidity() -> Weight; + fn remove_liquidity_one_asset() -> Weight; + fn remove_liquidity_imbalanced() -> Weight; + fn remove_liquidity() -> Weight; +} + +impl WeightInfo for () { + fn create_pool() -> Weight { + Weight::from_parts(0, 0) + } + + fn add_liquidity() -> Weight { + Weight::from_parts(0, 0) + } + + fn remove_liquidity_one_asset() -> Weight { + Weight::from_parts(0, 0) + } + fn remove_liquidity_imbalanced() -> Weight { + Weight::from_parts(0, 0) + } + fn remove_liquidity() -> Weight { + Weight::from_parts(0, 0) + } +} diff --git a/gasp-node/pallets/sudo-origin/Cargo.toml b/gasp-node/pallets/sudo-origin/Cargo.toml new file mode 100644 index 000000000..1cd3eccf2 --- /dev/null +++ b/gasp-node/pallets/sudo-origin/Cargo.toml @@ -0,0 +1,48 @@ +[package] +name = "pallet-sudo-origin" +version = "4.0.0-dev" +authors = ["Parity Technologies "] +edition = "2018" +license = "Apache-2.0" +homepage = "https://substrate.io" +repository = "https://github.com/mangata-finance/mangata-node/" +description = "FRAME pallet for sudo" +readme = "README.md" + +[package.metadata.docs.rs] +targets = ["x86_64-unknown-linux-gnu"] + +[dependencies] +codec = { workspace = true, default-features = false, features = ["derive"] } +log = { workspace = true, default-features = false } +scale-info = { workspace = true, default-features = false, features = ["derive"] } + +frame-executive = { workspace = true, default-features = false } +frame-support = { workspace = true, default-features = false } +frame-system = { workspace = true, default-features = false } +frame-try-runtime = { workspace = true, default-features = false, optional = true } +sp-io = { workspace = true, default-features = false } +sp-runtime = { workspace = true, default-features = false } +sp-std = { workspace = true, default-features = false } + +[dev-dependencies] +sp-core = { workspace = true } + +[features] +default = ["std"] +std = [ + "codec/std", + "frame-support/std", + "frame-system/std", + "scale-info/std", + "sp-std/std", + "sp-io/std", + "sp-runtime/std", +] +try-runtime = [ + "frame-executive/try-runtime", + "frame-support/try-runtime", + "frame-system/try-runtime", + "frame-try-runtime", + "sp-runtime/try-runtime", +] diff --git a/gasp-node/pallets/sudo-origin/README.md b/gasp-node/pallets/sudo-origin/README.md new file mode 100644 index 000000000..60090db46 --- /dev/null +++ b/gasp-node/pallets/sudo-origin/README.md @@ -0,0 +1,70 @@ +# Sudo Module + +- [`sudo::Config`](https://docs.rs/pallet-sudo/latest/pallet_sudo/trait.Config.html) +- [`Call`](https://docs.rs/pallet-sudo/latest/pallet_sudo/enum.Call.html) + +## Overview + +The Sudo module allows for a single account (called the "sudo key") +to execute dispatchable functions that require a `Root` call +or designate a new account to replace them as the sudo key. +Only one account can be the sudo key at a time. + +## Interface + +### Dispatchable Functions + +Only the sudo key can call the dispatchable functions from the Sudo module. + +* `sudo` - Make a `Root` call to a dispatchable function. +* `set_key` - Assign a new account to be the sudo key. + +## Usage + +### Executing Privileged Functions + +The Sudo module itself is not intended to be used within other modules. +Instead, you can build "privileged functions" (i.e. functions that require `Root` origin) in other modules. +You can execute these privileged functions by calling `sudo` with the sudo key account. +Privileged functions cannot be directly executed via an extrinsic. + +Learn more about privileged functions and `Root` origin in the [`Origin`] type documentation. + +### Simple Code Snippet + +This is an example of a module that exposes a privileged function: + +```rust +use frame_support::{decl_module, dispatch}; +use frame_system::ensure_root; + +pub trait Config: frame_system::Config {} + +decl_module! { + pub struct Module for enum Call where origin: T::Origin { + #[weight = 0] + pub fn privileged_function(origin) -> dispatch::DispatchResult { + ensure_root(origin)?; + + // do something... + + Ok(()) + } + } +} +``` + +## Genesis Config + +The Sudo module depends on the [`GenesisConfig`](https://docs.rs/pallet-sudo/latest/pallet_sudo/struct.GenesisConfig.html). +You need to set an initial superuser account as the sudo `key`. + +## Related Modules + +* [Democracy](https://docs.rs/pallet-democracy/latest/pallet_democracy/) + +[`Call`]: ./enum.Call.html +[`Config`]: ./trait.Config.html +[`Origin`]: https://docs.substrate.io/v3/runtime/origins + +License: Apache-2.0 diff --git a/gasp-node/pallets/sudo-origin/src/lib.rs b/gasp-node/pallets/sudo-origin/src/lib.rs new file mode 100644 index 000000000..621e8e21e --- /dev/null +++ b/gasp-node/pallets/sudo-origin/src/lib.rs @@ -0,0 +1,261 @@ +// This file is part of Substrate. + +// Copyright (C) 2017-2021 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. + +//! # SudoOrigin Pallet +//! +//! - [`Config`] +//! - [`Call`] +//! - [`SudoOrigin`] +//! +//! ## Overview +//! +//! The SudoOrigin pallet allows for an origin +//! to execute dispatchable functions that require a `Root` call. +//! +//! ## Interface +//! +//! ### Dispatchable Functions +//! +//! Only the sudo origin can call the dispatchable functions from the SudoOrigin pallet. +//! +//! * `sudo` - Make a `Root` call to a dispatchable function. +//! +//! ## Usage +//! +//! ### Executing Privileged Functions +//! +//! The SudoOrigin pallet is intended to be used with the Council. The council can use this pallet to make `Root` calls +//! You can build "privileged functions" (i.e. functions that require `Root` origin) in +//! other pallets. You can execute these privileged functions by calling `sudo` with the sudo origin. +//! Privileged functions cannot be directly executed via an extrinsic. +//! +//! Learn more about privileged functions and `Root` origin in the [`Origin`] type documentation. +//! +//! ### Simple Code Snippet +//! +//! This is an example of a pallet that exposes a privileged function: +//! +//! ``` +//! +//! #[frame_support::pallet] +//! pub mod logger { +//! use frame_support::pallet_prelude::*; +//! use frame_system::pallet_prelude::*; +//! use super::*; +//! +//! #[pallet::config] +//! pub trait Config: frame_system::Config {} +//! +//! #[pallet::pallet] +//! pub struct Pallet(PhantomData); +//! +//! #[pallet::call] +//! impl Pallet { +//! #[pallet::weight(0)] +//! pub fn privileged_function(origin: OriginFor) -> DispatchResultWithPostInfo { +//! ensure_root(origin)?; +//! +//! // do something... +//! +//! Ok(().into()) +//! } +//! } +//! } +//! # fn main() {} +//! ``` +//! +//! ## Genesis Config +//! +//! The SudoOrigin pallet depends on the runtiem config. +//! +//! ## Related Pallets +//! +//! * Collective +//! +//! [`Origin`]: https://docs.substrate.io/v3/runtime/origins + +#![cfg_attr(not(feature = "std"), no_std)] + +use frame_support::{dispatch::GetDispatchInfo, traits::UnfilteredDispatchable}; +use sp_runtime::{traits::StaticLookup, DispatchResult}; +use sp_std::{convert::TryInto, prelude::*}; + +#[cfg(test)] +mod mock; +#[cfg(test)] +mod tests; + +pub(crate) const LOG_TARGET: &'static str = "sudo-origin"; +pub(crate) const ALERT_STRING: &'static str = "ALERT!ALERT!ALERT!"; + +// syntactic sugar for logging. +#[macro_export] +macro_rules! alert_log { + ($level:tt, $patter:expr $(, $values:expr)* $(,)?) => { + log::$level!( + target: crate::LOG_TARGET, + concat!("[{:?}] {:?} ", $patter), >::block_number(), crate::ALERT_STRING $(, $values)* + ) + }; +} + +pub use pallet::*; + +#[frame_support::pallet] +pub mod pallet { + use super::{DispatchResult, *}; + use frame_support::pallet_prelude::*; + use frame_system::pallet_prelude::*; + + #[pallet::config] + pub trait Config: frame_system::Config { + /// The overarching event type. + type RuntimeEvent: From> + IsType<::RuntimeEvent>; + + /// A sudo-able call. + type RuntimeCall: Parameter + + UnfilteredDispatchable + + GetDispatchInfo; + + /// The Origin allowed to use sudo + type SudoOrigin: EnsureOrigin; + } + + #[pallet::pallet] + pub struct Pallet(PhantomData); + + #[pallet::call] + impl Pallet { + /// Authenticates the SudoOrigin and dispatches a function call with `Root` origin. + /// + /// # + /// - O(1). + /// - Limited storage reads. + /// - One DB write (event). + /// - Weight of derivative `call` execution + 10,000. + /// # + #[pallet::call_index(0)] + #[pallet::weight({ + let dispatch_info = call.get_dispatch_info(); + (dispatch_info.weight.saturating_add(Weight::from_parts(10_000, 0)), dispatch_info.class) + })] + pub fn sudo( + origin: OriginFor, + call: Box<::RuntimeCall>, + ) -> DispatchResultWithPostInfo { + // This is a public call, so we ensure that the origin is SudoOrigin. + T::SudoOrigin::ensure_origin(origin)?; + + let res = call.clone().dispatch_bypass_filter(frame_system::RawOrigin::Root.into()); + Self::deposit_event(Event::SuOriginDid(res.clone().map(|_| ()).map_err(|e| e.error))); + alert_log!(info, "A sudo action was performed: Call - {:?}, Result - {:?}!", call, res); + // Sudo user does not pay a fee. + Ok(Pays::No.into()) + } + + /// Authenticates the SudoOrigin and dispatches a function call with `Root` origin. + /// This function does not check the weight of the call, and instead allows the + /// SudoOrigin to specify the weight of the call. + /// + /// # + /// - O(1). + /// - The weight of this call is defined by the caller. + /// # + #[pallet::call_index(1)] + #[pallet::weight((*_weight, call.get_dispatch_info().class))] + pub fn sudo_unchecked_weight( + origin: OriginFor, + call: Box<::RuntimeCall>, + _weight: Weight, + ) -> DispatchResultWithPostInfo { + // This is a public call, so we ensure that the origin is SudoOrigin. + T::SudoOrigin::ensure_origin(origin)?; + + let res = call.clone().dispatch_bypass_filter(frame_system::RawOrigin::Root.into()); + Self::deposit_event(Event::SuOriginDid(res.clone().map(|_| ()).map_err(|e| e.error))); + alert_log!( + info, + "A sudo action was performed with unchecked weight: Call - {:?}, Result - {:?}!", + call, + res + ); + // Sudo user does not pay a fee. + Ok(Pays::No.into()) + } + + /// Authenticates the SudoOrigin and dispatches a function call with `Signed` origin from + /// a given account. + /// + /// # + /// - O(1). + /// - Limited storage reads. + /// - One DB write (event). + /// - Weight of derivative `call` execution + 10,000. + /// # + #[pallet::call_index(2)] + #[pallet::weight({ + let dispatch_info = call.get_dispatch_info(); + ( + dispatch_info.weight + .saturating_add(Weight::from_parts(10_000, 0)) + // AccountData for inner call origin accountdata. + .saturating_add(T::DbWeight::get().reads_writes(1, 1)), + dispatch_info.class, + ) + })] + pub fn sudo_as( + origin: OriginFor, + who: ::Source, + call: Box<::RuntimeCall>, + ) -> DispatchResultWithPostInfo { + // This is a public call, so we ensure that the origin is SudoOrigin. + T::SudoOrigin::ensure_origin(origin)?; + + let who = T::Lookup::lookup(who)?; + + let res = call + .clone() + .dispatch_bypass_filter(frame_system::RawOrigin::Signed(who.clone()).into()); + + Self::deposit_event(Event::SuOriginDoAsDone( + res.clone().map(|_| ()).map_err(|e| e.error), + )); + alert_log!( + info, + "A sudo_as action was performed: Who - {:?}, Call - {:?}, Result - {:?}!", + who, + call, + res + ); + // Sudo user does not pay a fee. + Ok(Pays::No.into()) + } + } + + #[pallet::event] + #[pallet::generate_deposit(pub(super) fn deposit_event)] + pub enum Event { + /// A sudo just took place. \[result\] + SuOriginDid(DispatchResult), + /// A sudo just took place. \[result\] + SuOriginDoAsDone(DispatchResult), + } + + #[pallet::error] + /// Error for the Sudo pallet + pub enum Error {} +} diff --git a/gasp-node/pallets/sudo-origin/src/mock.rs b/gasp-node/pallets/sudo-origin/src/mock.rs new file mode 100644 index 000000000..c70d45a69 --- /dev/null +++ b/gasp-node/pallets/sudo-origin/src/mock.rs @@ -0,0 +1,138 @@ +// This file is part of Substrate. + +// Copyright (C) 2020-2021 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 utilities + +use super::*; +use crate as sudo_origin; +use frame_support::{ + derive_impl, parameter_types, + traits::{Contains, Everything}, + weights::Weight, +}; +use frame_system::{limits, EnsureRoot}; +use sp_io; +use sp_runtime::BuildStorage; +use sp_std::convert::TryFrom; + +// Logger module to track execution. +#[frame_support::pallet] +pub mod logger { + use frame_support::pallet_prelude::*; + use frame_system::pallet_prelude::*; + + #[pallet::config] + pub trait Config: frame_system::Config { + type RuntimeEvent: From> + IsType<::RuntimeEvent>; + } + + #[pallet::pallet] + pub struct Pallet(PhantomData); + + #[pallet::call] + impl Pallet { + #[pallet::call_index(0)] + #[pallet::weight(*weight)] + pub fn privileged_i32_log( + origin: OriginFor, + i: i32, + weight: Weight, + ) -> DispatchResultWithPostInfo { + // Ensure that the `origin` is `Root`. + ensure_root(origin)?; + >::append(i); + Self::deposit_event(Event::AppendI32(i, weight)); + Ok(().into()) + } + + #[pallet::call_index(1)] + #[pallet::weight(*weight)] + pub fn non_privileged_log( + origin: OriginFor, + i: i32, + weight: Weight, + ) -> DispatchResultWithPostInfo { + // Ensure that the `origin` is some signed account. + let sender = ensure_signed(origin)?; + >::append(i); + >::append(sender.clone()); + Self::deposit_event(Event::AppendI32AndAccount(sender, i, weight)); + Ok(().into()) + } + } + + #[pallet::event] + #[pallet::generate_deposit(pub(super) fn deposit_event)] + pub enum Event { + AppendI32(i32, Weight), + AppendI32AndAccount(T::AccountId, i32, Weight), + } + + #[pallet::storage] + #[pallet::unbounded] + #[pallet::getter(fn account_log)] + pub(super) type AccountLog = StorageValue<_, Vec, ValueQuery>; + + #[pallet::storage] + #[pallet::unbounded] + #[pallet::getter(fn i32_log)] + pub(super) type I32Log = StorageValue<_, Vec, ValueQuery>; +} + +type Block = frame_system::mocking::MockBlock; + +frame_support::construct_runtime!( + pub enum Test { + System: frame_system, + SudoOrigin: sudo_origin, + Logger: logger, + } +); + +pub struct BlockEverything; +impl Contains for BlockEverything { + fn contains(_: &RuntimeCall) -> bool { + false + } +} + +#[derive_impl(frame_system::config_preludes::TestDefaultConfig as frame_system::DefaultConfig)] +impl frame_system::Config for Test { + type Block = Block; +} + +// Implement the logger module's `Config` on the Test runtime. +impl logger::Config for Test { + type RuntimeEvent = RuntimeEvent; +} + +// Implement the sudo_origin module's `Config` on the Test runtime. +impl Config for Test { + type RuntimeEvent = RuntimeEvent; + type RuntimeCall = RuntimeCall; + type SudoOrigin = EnsureRoot; +} + +// New types for dispatchable functions. +pub type SudoOriginCall = sudo_origin::Call; +pub type LoggerCall = logger::Call; + +// Build test environment by setting the root `key` for the Genesis. +pub fn new_test_ext() -> sp_io::TestExternalities { + let t = frame_system::GenesisConfig::::default().build_storage().unwrap(); + t.into() +} diff --git a/gasp-node/pallets/sudo-origin/src/tests.rs b/gasp-node/pallets/sudo-origin/src/tests.rs new file mode 100644 index 000000000..8e91acfb7 --- /dev/null +++ b/gasp-node/pallets/sudo-origin/src/tests.rs @@ -0,0 +1,186 @@ +// This file is part of Substrate. + +// Copyright (C) 2020-2021 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. + +//! Tests for the module. + +use super::*; +use frame_support::{assert_noop, assert_ok, weights::Weight}; +use frame_system::RawOrigin; +use mock::{ + new_test_ext, Logger, LoggerCall, RuntimeCall, RuntimeEvent as TestEvent, RuntimeOrigin, + SudoOrigin, SudoOriginCall, System, +}; +use sp_runtime::DispatchError; + +#[test] +fn test_setup_works() { + // Environment setup, logger storage, and sudo `key` retrieval should work as expected. + new_test_ext().execute_with(|| { + assert!(Logger::i32_log().is_empty()); + assert!(Logger::account_log().is_empty()); + }); +} + +#[test] +fn sudo_basics() { + // Configure a default test environment and set the root `key` to 1. + new_test_ext().execute_with(|| { + // A privileged function should work when `sudo` is passed the root `key` as `origin`. + let call = Box::new(RuntimeCall::Logger(LoggerCall::privileged_i32_log { + i: 42, + weight: Weight::from_parts(1_000, 0), + })); + assert_ok!(SudoOrigin::sudo(RawOrigin::Root.into(), call)); + assert_eq!(Logger::i32_log(), vec![42i32]); + + // A privileged function should not work when `sudo` is passed a non-root `key` as `origin`. + let call = Box::new(RuntimeCall::Logger(LoggerCall::privileged_i32_log { + i: 42, + weight: Weight::from_parts(1_000, 0), + })); + assert_noop!(SudoOrigin::sudo(RuntimeOrigin::signed(2), call), DispatchError::BadOrigin); + }); +} + +#[test] +fn sudo_emits_events_correctly() { + new_test_ext().execute_with(|| { + // Set block number to 1 because events are not emitted on block 0. + System::set_block_number(1); + + // Should emit event to indicate success when called with the root `key` and `call` is `Ok`. + let call = Box::new(RuntimeCall::Logger(LoggerCall::privileged_i32_log { + i: 42, + weight: Weight::from_parts(1, 0), + })); + assert_ok!(SudoOrigin::sudo(RawOrigin::Root.into(), call)); + System::assert_has_event(TestEvent::SudoOrigin(Event::SuOriginDid(Ok(())))); + }) +} + +#[test] +fn sudo_unchecked_weight_basics() { + new_test_ext().execute_with(|| { + // A privileged function should work when `sudo` is passed the root `key` as origin. + let call = Box::new(RuntimeCall::Logger(LoggerCall::privileged_i32_log { + i: 42, + weight: Weight::from_parts(1000, 0), + })); + assert_ok!(SudoOrigin::sudo_unchecked_weight( + RawOrigin::Root.into(), + call, + Weight::from_parts(1_000, 0) + )); + assert_eq!(Logger::i32_log(), vec![42i32]); + + // A privileged function should not work when called with a non-root `key`. + let call = Box::new(RuntimeCall::Logger(LoggerCall::privileged_i32_log { + i: 42, + weight: Weight::from_parts(1_000, 0), + })); + assert_noop!( + SudoOrigin::sudo_unchecked_weight( + RuntimeOrigin::signed(2), + call, + Weight::from_parts(1_000, 0) + ), + DispatchError::BadOrigin, + ); + // `I32Log` is unchanged after unsuccessful call. + assert_eq!(Logger::i32_log(), vec![42i32]); + + // Controls the dispatched weight. + let call = Box::new(RuntimeCall::Logger(LoggerCall::privileged_i32_log { + i: 42, + weight: Weight::from_parts(1, 0), + })); + let sudo_unchecked_weight_call = + SudoOriginCall::sudo_unchecked_weight { call, weight: Weight::from_parts(1_000, 0) }; + let info = sudo_unchecked_weight_call.get_dispatch_info(); + assert_eq!(info.weight, Weight::from_parts(1_000, 0)); + }); +} + +#[test] +fn sudo_unchecked_weight_emits_events_correctly() { + new_test_ext().execute_with(|| { + // Set block number to 1 because events are not emitted on block 0. + System::set_block_number(1); + + // Should emit event to indicate success when called with the root `key` and `call` is `Ok`. + let call = Box::new(RuntimeCall::Logger(LoggerCall::privileged_i32_log { + i: 42, + weight: Weight::from_parts(1, 0), + })); + assert_ok!(SudoOrigin::sudo_unchecked_weight( + RawOrigin::Root.into(), + call, + Weight::from_parts(1_000, 0) + )); + System::assert_has_event(TestEvent::SudoOrigin(Event::SuOriginDid(Ok(())))); + }) +} + +#[test] +fn sudo_as_basics() { + new_test_ext().execute_with(|| { + // A privileged function will not work when passed to `sudo_as`. + let call = Box::new(RuntimeCall::Logger(LoggerCall::privileged_i32_log { + i: 42, + weight: Weight::from_parts(1_000, 0), + })); + assert_ok!(SudoOrigin::sudo_as(RawOrigin::Root.into(), 2, call)); + assert!(Logger::i32_log().is_empty()); + assert!(Logger::account_log().is_empty()); + + // A non-privileged function should not work when called with a non-root `key`. + let call = Box::new(RuntimeCall::Logger(LoggerCall::non_privileged_log { + i: 42, + weight: Weight::from_parts(1, 0), + })); + assert_noop!( + SudoOrigin::sudo_as(RuntimeOrigin::signed(3), 2, call), + DispatchError::BadOrigin + ); + + // A non-privileged function will work when passed to `sudo_as` with the root `key`. + let call = Box::new(RuntimeCall::Logger(LoggerCall::non_privileged_log { + i: 42, + weight: Weight::from_parts(1, 0), + })); + assert_ok!(SudoOrigin::sudo_as(RawOrigin::Root.into(), 2, call)); + assert_eq!(Logger::i32_log(), vec![42i32]); + // The correct user makes the call within `sudo_as`. + assert_eq!(Logger::account_log(), vec![2]); + }); +} + +#[test] +fn sudo_as_emits_events_correctly() { + new_test_ext().execute_with(|| { + // Set block number to 1 because events are not emitted on block 0. + System::set_block_number(1); + + // A non-privileged function will work when passed to `sudo_as` with the root `key`. + let call = Box::new(RuntimeCall::Logger(LoggerCall::non_privileged_log { + i: 42, + weight: Weight::from_parts(1, 0), + })); + assert_ok!(SudoOrigin::sudo_as(RawOrigin::Root.into(), 2, call)); + System::assert_has_event(TestEvent::SudoOrigin(Event::SuOriginDoAsDone(Ok(())))); + }); +} diff --git a/gasp-node/pallets/xyk/Cargo.toml b/gasp-node/pallets/xyk/Cargo.toml new file mode 100644 index 000000000..6797bc35b --- /dev/null +++ b/gasp-node/pallets/xyk/Cargo.toml @@ -0,0 +1,84 @@ +[package] +authors = ["Mangata team"] +edition = "2018" +name = "pallet-xyk" +version = "0.1.0" + +[package.metadata.docs.rs] +targets = ["x86_64-unknown-linux-gnu"] + +[dependencies] +codec = { workspace = true, default-features = false } +hex = { workspace = true, default-features = false } +hex-literal = { workspace = true, default-features = false } +log = { workspace = true, default-features = false } +serde = { workspace = true, optional = true } +scale-info = { workspace = true, default-features = false, features = ["derive"] } + +libm = { git = "https://github.com/rust-lang/libm", rev = "2f3fc968f43d345f9b449938d050a9ea46a04c83", default-features = false } + +pallet-issuance = { path = "../issuance", default-features = false } +pallet-bootstrap = { path = "../bootstrap", default-features = false } +pallet-proof-of-stake = { path = "../proof-of-stake", default-features = false } + +frame-benchmarking = { workspace = true, default-features = false } +frame-executive = { workspace = true, default-features = false } +frame-support = { workspace = true, default-features = false } +frame-system = { workspace = true, default-features = false } +frame-try-runtime = { workspace = true, default-features = false, optional = true } +mangata-support = { workspace = true, default-features = false } +mangata-types = { workspace = true, default-features = false } +pallet-vesting-mangata = { workspace = true, default-features = false } +sp-arithmetic = { workspace = true, default-features = false } +sp-core = { workspace = true, default-features = false } +sp-runtime = { workspace = true, default-features = false } +sp-std = { workspace = true, default-features = false } +sp-debug-derive = { workspace = true, default-features = false, features = ["force-debug"] } + +orml-tokens = { workspace = true, default-features = false } +orml-traits = { workspace = true, default-features = false } + +[dev-dependencies] +env_logger.workspace = true +lazy_static.workspace = true +serial_test.workspace = true +similar-asserts.workspace = true +test-case.workspace = true +mockall.workspace = true + +pallet-proof-of-stake = { path = "../proof-of-stake" } + +sp-io = { workspace = true, default-features = false } + +orml-traits = { workspace = true, default-features = false } + +[features] +default = ["std"] +std = [ + "codec/std", + "frame-benchmarking/std", + "frame-support/std", + "frame-system/std", + "hex/std", + "mangata-support/std", + "orml-tokens/std", + "pallet-vesting-mangata/std", + "scale-info/std", + "serde", + "sp-std/std", + "sp-core/std", +] + +runtime-benchmarks = ["frame-benchmarking/runtime-benchmarks"] + +try-runtime = [ + "frame-executive/try-runtime", + "frame-support/try-runtime", + "frame-system/try-runtime", + "frame-try-runtime", + "orml-tokens/try-runtime", + "pallet-bootstrap/try-runtime", + "pallet-issuance/try-runtime", + "pallet-vesting-mangata/try-runtime", + "sp-runtime/try-runtime", +] diff --git a/gasp-node/pallets/xyk/rpc/Cargo.toml b/gasp-node/pallets/xyk/rpc/Cargo.toml new file mode 100644 index 000000000..abc55f018 --- /dev/null +++ b/gasp-node/pallets/xyk/rpc/Cargo.toml @@ -0,0 +1,36 @@ +[package] +authors = ["Mangata team"] +name = "xyk-rpc" +version = "2.0.0" +edition = "2018" +description = "RPC calls for xyk" +license = "GPL-3.0-or-later" + +[dependencies] +codec = { workspace = true } +jsonrpsee = { workspace = true, features = ["server", "macros"] } +serde = { workspace = true, features = ["derive"], optional = true } + +# Substrate packages + +sp-api = { workspace = true, default-features = false } +sp-blockchain = { workspace = true, default-features = false } +sp-core = { workspace = true, default-features = false } +sp-rpc = { workspace = true, default-features = false } +sp-runtime = { workspace = true, default-features = false } +sp-std = { workspace = true, default-features = false } + +# local packages + +xyk-runtime-api = { version = "2.0.0", path = "../runtime-api", default-features = false } + +[features] +default = ["std"] +std = [ + "serde", + "sp-api/std", + "sp-core/std", + "sp-std/std", + "sp-runtime/std", + "xyk-runtime-api/std", +] diff --git a/gasp-node/pallets/xyk/rpc/src/lib.rs b/gasp-node/pallets/xyk/rpc/src/lib.rs new file mode 100644 index 000000000..cb9815df7 --- /dev/null +++ b/gasp-node/pallets/xyk/rpc/src/lib.rs @@ -0,0 +1,381 @@ +// Copyright (C) 2021 Mangata team + +use codec::Codec; +use jsonrpsee::{ + core::{async_trait, RpcResult}, + proc_macros::rpc, + types::error::ErrorObject, +}; +use sp_api::ProvideRuntimeApi; +use sp_blockchain::HeaderBackend; +use sp_core::U256; +use sp_rpc::number::NumberOrHex; +use sp_runtime::traits::{Block as BlockT, MaybeDisplay, MaybeFromStr}; +use sp_std::convert::{TryFrom, TryInto}; +use std::sync::Arc; +use xyk_runtime_api::RpcAssetMetadata; +pub use xyk_runtime_api::XykRuntimeApi; + +#[rpc(client, server)] +pub trait XykApi { + #[method(name = "xyk_calculate_sell_price")] + fn calculate_sell_price( + &self, + input_reserve: NumberOrHex, + output_reserve: NumberOrHex, + sell_amount: NumberOrHex, + at: Option, + ) -> RpcResult; + + #[method(name = "xyk_calculate_buy_price")] + fn calculate_buy_price( + &self, + input_reserve: NumberOrHex, + output_reserve: NumberOrHex, + buy_amount: NumberOrHex, + at: Option, + ) -> RpcResult; + + #[method(name = "xyk_calculate_sell_price_id")] + fn calculate_sell_price_id( + &self, + sold_token_id: TokenId, + bought_token_id: TokenId, + sell_amount: NumberOrHex, + at: Option, + ) -> RpcResult; + + #[method(name = "xyk_calculate_buy_price_id")] + fn calculate_buy_price_id( + &self, + sold_token_id: TokenId, + bought_token_id: TokenId, + buy_amount: NumberOrHex, + at: Option, + ) -> RpcResult; + + #[method(name = "xyk_get_burn_amount")] + fn get_burn_amount( + &self, + first_asset_id: TokenId, + second_asset_id: TokenId, + liquidity_asset_amount: NumberOrHex, + at: Option, + ) -> RpcResult<(NumberOrHex, NumberOrHex)>; + + #[method(name = "xyk_get_max_instant_burn_amount")] + fn get_max_instant_burn_amount( + &self, + user: AccountId, + liquidity_asset_id: TokenId, + at: Option, + ) -> RpcResult; + + #[method(name = "xyk_get_max_instant_unreserve_amount")] + fn get_max_instant_unreserve_amount( + &self, + user: AccountId, + liquidity_asset_id: TokenId, + at: Option, + ) -> RpcResult; + + #[method(name = "xyk_calculate_rewards_amount")] + fn calculate_rewards_amount( + &self, + user: AccountId, + liquidity_asset_id: TokenId, + at: Option, + ) -> RpcResult; + + #[method(name = "xyk_calculate_balanced_sell_amount")] + fn calculate_balanced_sell_amount( + &self, + total_amount: NumberOrHex, + reserve_amount: NumberOrHex, + at: Option, + ) -> RpcResult; + + #[method(name = "xyk_get_liq_tokens_for_trading")] + fn get_liq_tokens_for_trading(&self, at: Option) -> RpcResult>; + + #[method(name = "xyk_is_buy_asset_lock_free")] + fn is_buy_asset_lock_free( + &self, + path: sp_std::vec::Vec, + input_amount: NumberOrHex, + at: Option, + ) -> RpcResult>; + + #[method(name = "xyk_is_sell_asset_lock_free")] + fn is_sell_asset_lock_free( + &self, + path: sp_std::vec::Vec, + input_amount: NumberOrHex, + at: Option, + ) -> RpcResult>; + + #[method(name = "xyk_get_tradeable_tokens")] + fn get_tradeable_tokens( + &self, + at: Option, + ) -> RpcResult>>; +} + +pub struct Xyk { + client: Arc, + _marker: std::marker::PhantomData, +} + +impl Xyk { + pub fn new(client: Arc) -> Self { + Self { client, _marker: Default::default() } + } +} + +trait TryIntoBalance { + fn try_into_balance(self) -> RpcResult; +} + +impl> TryIntoBalance for NumberOrHex { + fn try_into_balance(self) -> RpcResult { + self.into_u256().try_into().or(Err(ErrorObject::owned( + 1, + "Unable to serve the request", + Some(String::from("input parameter doesnt fit into u128")), + ))) + } +} + +#[async_trait] +impl + XykApiServer<::Hash, Balance, TokenId, AccountId> for Xyk +where + Block: BlockT, + C: Send + Sync + 'static, + C: ProvideRuntimeApi, + C: HeaderBackend, + C::Api: XykRuntimeApi, + Balance: Codec + MaybeDisplay + MaybeFromStr + TryFrom + Into, + TokenId: Codec + MaybeDisplay + MaybeFromStr, + AccountId: Codec + MaybeDisplay + MaybeFromStr, +{ + fn calculate_sell_price( + &self, + input_reserve: NumberOrHex, + output_reserve: NumberOrHex, + sell_amount: NumberOrHex, + _at: Option<::Hash>, + ) -> RpcResult { + let api = self.client.runtime_api(); + let at = self.client.info().best_hash; + + api.calculate_sell_price( + at, + input_reserve.try_into_balance()?, + output_reserve.try_into_balance()?, + sell_amount.try_into_balance()?, + ) + .map(Into::::into) + .map_err(|e| ErrorObject::owned(1, "Unable to serve the request", Some(format!("{:?}", e)))) + } + + fn calculate_buy_price( + &self, + input_reserve: NumberOrHex, + output_reserve: NumberOrHex, + buy_amount: NumberOrHex, + _at: Option<::Hash>, + ) -> RpcResult { + let api = self.client.runtime_api(); + let at = self.client.info().best_hash; + + api.calculate_buy_price( + at, + input_reserve.try_into_balance()?, + output_reserve.try_into_balance()?, + buy_amount.try_into_balance()?, + ) + .map(Into::::into) + .map_err(|e| ErrorObject::owned(1, "Unable to serve the request", Some(format!("{:?}", e)))) + } + + fn calculate_sell_price_id( + &self, + sold_token_id: TokenId, + bought_token_id: TokenId, + sell_amount: NumberOrHex, + _at: Option<::Hash>, + ) -> RpcResult { + let api = self.client.runtime_api(); + let at = self.client.info().best_hash; + + api.calculate_sell_price_id( + at, + sold_token_id, + bought_token_id, + sell_amount.try_into_balance()?, + ) + .map(Into::::into) + .map_err(|e| ErrorObject::owned(1, "Unable to serve the request", Some(format!("{:?}", e)))) + } + + fn calculate_buy_price_id( + &self, + sold_token_id: TokenId, + bought_token_id: TokenId, + buy_amount: NumberOrHex, + _at: Option<::Hash>, + ) -> RpcResult { + let api = self.client.runtime_api(); + let at = self.client.info().best_hash; + + api.calculate_buy_price_id( + at, + sold_token_id, + bought_token_id, + buy_amount.try_into_balance()?, + ) + .map(Into::::into) + .map_err(|e| ErrorObject::owned(1, "Unable to serve the request", Some(format!("{:?}", e)))) + } + + fn get_burn_amount( + &self, + first_asset_id: TokenId, + second_asset_id: TokenId, + liquidity_asset_amount: NumberOrHex, + _at: Option<::Hash>, + ) -> RpcResult<(NumberOrHex, NumberOrHex)> { + let api = self.client.runtime_api(); + let at = self.client.info().best_hash; + + api.get_burn_amount( + at, + first_asset_id, + second_asset_id, + liquidity_asset_amount.try_into_balance()?, + ) + .map(|(val1, val2)| (val1.into(), val2.into())) + .map_err(|e| ErrorObject::owned(1, "Unable to serve the request", Some(format!("{:?}", e)))) + } + + fn get_max_instant_burn_amount( + &self, + user: AccountId, + liquidity_asset_id: TokenId, + _at: Option<::Hash>, + ) -> RpcResult { + let api = self.client.runtime_api(); + let at = self.client.info().best_hash; + + api.get_max_instant_burn_amount(at, user, liquidity_asset_id) + .map(Into::::into) + .map_err(|e| { + ErrorObject::owned(1, "Unable to serve the request", Some(format!("{:?}", e))) + }) + } + + fn get_max_instant_unreserve_amount( + &self, + user: AccountId, + liquidity_asset_id: TokenId, + _at: Option<::Hash>, + ) -> RpcResult { + let api = self.client.runtime_api(); + let at = self.client.info().best_hash; + + api.get_max_instant_unreserve_amount(at, user, liquidity_asset_id) + .map(Into::::into) + .map_err(|e| { + ErrorObject::owned(1, "Unable to serve the request", Some(format!("{:?}", e))) + }) + } + + fn calculate_rewards_amount( + &self, + user: AccountId, + liquidity_asset_id: TokenId, + _at: Option<::Hash>, + ) -> RpcResult { + let api = self.client.runtime_api(); + let at = self.client.info().best_hash; + + api.calculate_rewards_amount(at, user, liquidity_asset_id) + .map(Into::::into) + .map_err(|e| { + ErrorObject::owned(1, "Unable to serve the request", Some(format!("{:?}", e))) + }) + } + + fn calculate_balanced_sell_amount( + &self, + total_amount: NumberOrHex, + reserve_amount: NumberOrHex, + _at: Option<::Hash>, + ) -> RpcResult { + let api = self.client.runtime_api(); + let at = self.client.info().best_hash; + + api.calculate_balanced_sell_amount( + at, + total_amount.try_into_balance()?, + reserve_amount.try_into_balance()?, + ) + .map(Into::::into) + .map_err(|e| ErrorObject::owned(1, "Unable to serve the request", Some(format!("{:?}", e)))) + } + + fn get_liq_tokens_for_trading( + &self, + _at: Option<::Hash>, + ) -> RpcResult> { + let api = self.client.runtime_api(); + let at = self.client.info().best_hash; + + api.get_liq_tokens_for_trading(at).map_err(|e| { + ErrorObject::owned(1, "Unable to serve the request", Some(format!("{:?}", e))) + }) + } + + fn is_buy_asset_lock_free( + &self, + path: sp_std::vec::Vec, + input_amount: NumberOrHex, + _at: Option<::Hash>, + ) -> RpcResult> { + let api = self.client.runtime_api(); + let at = self.client.info().best_hash; + + api.is_buy_asset_lock_free(at, path, input_amount.try_into_balance()?) + .map_err(|e| { + ErrorObject::owned(1, "Unable to serve the request", Some(format!("{:?}", e))) + }) + } + + fn is_sell_asset_lock_free( + &self, + path: sp_std::vec::Vec, + input_amount: NumberOrHex, + _at: Option<::Hash>, + ) -> RpcResult> { + let api = self.client.runtime_api(); + let at = self.client.info().best_hash; + + api.is_sell_asset_lock_free(at, path, input_amount.try_into_balance()?) + .map_err(|e| { + ErrorObject::owned(1, "Unable to serve the request", Some(format!("{:?}", e))) + }) + } + + fn get_tradeable_tokens( + &self, + _at: Option<::Hash>, + ) -> RpcResult>> { + let api = self.client.runtime_api(); + let at = self.client.info().best_hash; + + api.get_tradeable_tokens(at).map_err(|e| { + ErrorObject::owned(1, "Unable to serve the request", Some(format!("{:?}", e))) + }) + } +} diff --git a/gasp-node/pallets/xyk/runtime-api/Cargo.toml b/gasp-node/pallets/xyk/runtime-api/Cargo.toml new file mode 100644 index 000000000..17c10aee0 --- /dev/null +++ b/gasp-node/pallets/xyk/runtime-api/Cargo.toml @@ -0,0 +1,32 @@ +[package] +authors = ["Mangata team"] +name = "xyk-runtime-api" +version = "2.0.0" +edition = "2018" +license = "GPL-3.0-or-later" + +[dependencies] +codec = { workspace = true, default-features = false, features = ["derive"] } +serde = { workspace = true, optional = true, features = ["derive"] } +scale-info = { workspace = true, default-features = false, features = ["derive"] } + +frame-support = { workspace = true, default-features = false } +frame-system = { workspace = true, default-features = false } +sp-api = { workspace = true, default-features = false } +sp-core = { workspace = true, default-features = false } +sp-runtime = { workspace = true, default-features = false } +sp-std = { workspace = true, default-features = false } + +[dev-dependencies] +serde_json.workspace = true + +[features] +default = ["std"] +std = [ + "codec/std", + "frame-support/std", + "frame-system/std", + "serde", + "sp-api/std", + "sp-runtime/std", +] diff --git a/gasp-node/pallets/xyk/runtime-api/src/lib.rs b/gasp-node/pallets/xyk/runtime-api/src/lib.rs new file mode 100644 index 000000000..b7875d4f7 --- /dev/null +++ b/gasp-node/pallets/xyk/runtime-api/src/lib.rs @@ -0,0 +1,81 @@ +// Copyright (C) 2021 Mangata team +#![cfg_attr(not(feature = "std"), no_std)] +#![allow(clippy::too_many_arguments)] +#![allow(clippy::unnecessary_mut_passed)] +use codec::{Codec, Decode, Encode}; +use frame_support::pallet_prelude::TypeInfo; +#[cfg(feature = "std")] +use serde::{Deserialize, Serialize}; +use sp_runtime::traits::{MaybeDisplay, MaybeFromStr}; +use sp_std::vec::Vec; + +#[derive(Clone, Eq, PartialEq, Encode, Decode, Default, TypeInfo)] +#[cfg_attr(feature = "std", derive(Debug, Serialize, Deserialize))] +#[cfg_attr(feature = "std", serde(rename_all = "camelCase"))] +pub struct RpcAssetMetadata { + pub token_id: TokenId, + pub decimals: u32, + pub name: Vec, + pub symbol: Vec, +} + +sp_api::decl_runtime_apis! { + pub trait XykRuntimeApi where + Balance: Codec + MaybeDisplay + MaybeFromStr, + TokenId: Codec + MaybeDisplay + MaybeFromStr, + AccountId: Codec + MaybeDisplay + MaybeFromStr,{ + fn calculate_sell_price( + input_reserve: Balance, + output_reserve: Balance, + sell_amount: Balance + ) -> Balance; + fn calculate_buy_price( + input_reserve: Balance, + output_reserve: Balance, + buy_amount: Balance + ) -> Balance; + fn calculate_sell_price_id( + sold_token_id: TokenId, + bought_token_id: TokenId, + sell_amount: Balance + ) -> Balance; + fn calculate_buy_price_id( + sold_token_id: TokenId, + bought_token_id: TokenId, + buy_amount: Balance + ) -> Balance; + fn get_burn_amount( + first_asset_id: TokenId, + second_asset_id: TokenId, + liquidity_asset_amount: Balance, + ) -> (Balance,Balance); + fn get_max_instant_burn_amount( + user: AccountId, + liquidity_asset_id: TokenId, + ) -> Balance; + fn get_max_instant_unreserve_amount( + user: AccountId, + liquidity_asset_id: TokenId, + ) -> Balance; + fn calculate_rewards_amount( + user: AccountId, + liquidity_asset_id: TokenId, + ) -> Balance; + fn calculate_balanced_sell_amount( + total_amount: Balance, + reserve_amount: Balance, + ) -> Balance; + fn get_liq_tokens_for_trading( + ) -> Vec; + fn is_buy_asset_lock_free( + path: sp_std::vec::Vec, + input_amount: Balance, + ) -> Option; + fn is_sell_asset_lock_free( + path: sp_std::vec::Vec, + input_amount: Balance, + ) -> Option; + fn get_tradeable_tokens() -> Vec>; + fn get_total_number_of_swaps() -> u128; + } +} diff --git a/gasp-node/pallets/xyk/src/benchmarking.rs b/gasp-node/pallets/xyk/src/benchmarking.rs new file mode 100644 index 000000000..bbb3ac56f --- /dev/null +++ b/gasp-node/pallets/xyk/src/benchmarking.rs @@ -0,0 +1,530 @@ +// This file is part of Substrate. + +// Copyright (C) 2020-2022 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. + +//! Balances pallet benchmarking. + +#![cfg(feature = "runtime-benchmarks")] + +use super::*; + +use frame_benchmarking::{account, benchmarks, whitelisted_caller}; +use frame_system::RawOrigin; +use mangata_support::traits::{ComputeIssuance, ProofOfStakeRewardsApi}; +use orml_tokens::MultiTokenCurrencyExtended; +use sp_runtime::{Permill, SaturatedConversion}; + +use crate::Pallet as Xyk; + +const MILION: u128 = 1_000__000_000__000_000; + +trait ToBalance { + fn to_balance(self) -> BalanceOf; +} + +impl ToBalance for u128 { + fn to_balance(self) -> BalanceOf { + self.try_into().ok().expect("u128 should fit into Balance type") + } +} + +#[macro_export] +macro_rules! init { + () => { + frame_system::Pallet::::set_block_number(1_u32.into()); + pallet_issuance::Pallet::::initialize(); + }; +} + +#[macro_export] +macro_rules! forward_to_next_session { + () => { + let current_block: u32 = frame_system::Pallet::::block_number().saturated_into::(); + + let blocks_per_session: u32 = pallet_proof_of_stake::Pallet::::rewards_period(); + let target_block_nr: u32; + let target_session_nr: u32; + + if (current_block == 0_u32 || current_block == 1_u32) { + target_session_nr = 1_u32; + target_block_nr = blocks_per_session; + } else { + // to fail on user trying to manage block nr on its own + assert!(current_block % blocks_per_session == 0); + target_session_nr = (current_block / blocks_per_session) + 1_u32; + target_block_nr = (target_session_nr * blocks_per_session); + } + + frame_system::Pallet::::set_block_number(target_block_nr.into()); + pallet_issuance::Pallet::::compute_issuance(target_session_nr); + }; +} + +benchmarks! { + + create_pool { + init!(); + let caller: T::AccountId = whitelisted_caller(); + let first_asset_amount = MILION.to_balance::(); + let second_asset_amount = MILION.to_balance::(); + ::Currency::create(&caller, first_asset_amount).unwrap(); + let first_asset_id = ::Currency::create(&caller, second_asset_amount).unwrap(); + let second_asset_id = ::Currency::create(&caller, second_asset_amount).unwrap(); + let liquidity_asset_id = second_asset_id + 1_u32.into(); + + }: create_pool(RawOrigin::Signed(caller.clone().into()), first_asset_id, first_asset_amount, second_asset_id, second_asset_amount) + verify { + + assert_eq!( + Xyk::::asset_pool((first_asset_id, second_asset_id)), + (first_asset_amount, second_asset_amount) + ); + + assert!( + Xyk::::liquidity_asset((first_asset_id, second_asset_id)).is_some() + ); + + } + + sell_asset { + // NOTE: duplicates test case XYK::buy_and_burn_sell_none_have_mangata_pair + + init!(); + let caller: T::AccountId = whitelisted_caller(); + let initial_amount: BalanceOf = 1000000000000000.to_balance::(); + let expected_amount = BalanceOf::::zero(); + let expected_native_asset_id = ::NativeCurrencyId::get(); + ::Currency::create(&caller, initial_amount).unwrap(); + let native_asset_id = ::Currency::create(&caller, initial_amount).unwrap(); + let non_native_asset_id1 = ::Currency::create(&caller, initial_amount).unwrap(); + let non_native_asset_id2 = ::Currency::create(&caller, initial_amount).unwrap(); + + let pool_amount: BalanceOf = 100_000_000_000_000.to_balance::(); + + Xyk::::create_pool(RawOrigin::Signed(caller.clone().into()).into(), native_asset_id, pool_amount, non_native_asset_id1, pool_amount).unwrap(); + Xyk::::create_pool(RawOrigin::Signed(caller.clone().into()).into(), non_native_asset_id1, pool_amount, non_native_asset_id2, pool_amount).unwrap(); + + }: sell_asset(RawOrigin::Signed(caller.clone().into()), non_native_asset_id1, non_native_asset_id2, pool_amount / 2_u32.into(), 0_u32.into()) + verify { + // verify only trading result as rest of the assertion is in unit test + assert_eq!(::Currency::free_balance(non_native_asset_id1, &caller).into(), 750000000000000_u128); + assert_eq!(::Currency::free_balance(non_native_asset_id2, &caller).into(), 933266599933266_u128); + + } + + multiswap_sell_asset { + + // NOTE: All atomic swaps in the chain involve sold tokens that are pooled with the native token for bnb, which is the worst case + + // liquidity tokens + let x in 3_u32..100; + + init!(); + let caller: T::AccountId = whitelisted_caller(); + + let mint_amount: BalanceOf = 1000000000000000000.to_balance::(); + let pool_creation_amount: BalanceOf = 10000000000000000.to_balance::(); + let trade_amount: BalanceOf = 1000000000000.to_balance::(); + + // Create 4 tokens to avoid DisallowedPool (Bootstrap by default disallows pools between 0 & 4) + ::Currency::create(&caller, mint_amount).unwrap(); + ::Currency::create(&caller, mint_amount).unwrap(); + ::Currency::create(&caller, mint_amount).unwrap(); + + let mut initial_asset_id = ::Currency::get_next_currency_id(); + let native_asset_id = ::NativeCurrencyId::get(); + + if initial_asset_id == native_asset_id { + assert_eq!(::Currency::create(&caller, mint_amount * x.into()).unwrap(), native_asset_id); + initial_asset_id = initial_asset_id + 1_u32.into(); + } else { + assert_ok!(::Currency::mint(native_asset_id, &caller, mint_amount * x.into())); + } + + // Create all the non-native tokens we will need + for i in 0_u32..x{ + assert_eq!(::Currency::create(&caller, mint_amount).unwrap(), initial_asset_id + i.into()); + } + + // Create all pool with the subsequent non-native tokens + for i in 0_u32.. (x - 1){ + assert_ok!(Xyk::::create_pool(RawOrigin::Signed(caller.clone().into()).into(), initial_asset_id + i.into(), pool_creation_amount, initial_asset_id + (i+1).into(), pool_creation_amount)); + } + + // Create all pool with the subsequent non-native tokens + for i in 0_u32..x{ + assert_ok!(Xyk::::create_pool(RawOrigin::Signed(caller.clone().into()).into(), native_asset_id, pool_creation_amount, initial_asset_id + i.into(), pool_creation_amount)); + } + + let mut swap_token_list: Vec> = vec![]; + + for i in 0_u32..x{ + swap_token_list.push(initial_asset_id + i.into()); + } + + let expected: BalanceOf = mint_amount - pool_creation_amount - pool_creation_amount; + assert_eq!(::Currency::free_balance(initial_asset_id + (x - 1).into(), &caller), expected); + + }: multiswap_sell_asset(RawOrigin::Signed(caller.clone().into()), swap_token_list.into(), trade_amount, 0_u32.into()) + verify { + // verify only trading result as rest of the assertion is in unit test + assert!(::Currency::free_balance(initial_asset_id + (x - 1).into(), &caller) > expected); + + } + + buy_asset { + // NOTE: duplicates test case XYK::buy_and_burn_buy_where_sold_has_mangata_pair + + init!(); + let caller: T::AccountId = whitelisted_caller(); + let initial_amount: BalanceOf = 1000000000000000.to_balance::(); + let expected_amount = BalanceOf::::zero(); + let expected_native_asset_id = ::NativeCurrencyId::get(); + ::Currency::create(&caller, initial_amount).unwrap(); + let native_asset_id = ::Currency::create(&caller, initial_amount).unwrap(); + let non_native_asset_id1 = ::Currency::create(&caller, initial_amount).unwrap(); + let non_native_asset_id2 = ::Currency::create(&caller, initial_amount).unwrap(); + + let amount = 100000000000000.to_balance::(); + Xyk::::create_pool(RawOrigin::Signed(caller.clone().into()).into(), native_asset_id, amount, non_native_asset_id1, amount).unwrap(); + Xyk::::create_pool(RawOrigin::Signed(caller.clone().into()).into(), non_native_asset_id1, amount, non_native_asset_id2, amount).unwrap(); + + }: buy_asset(RawOrigin::Signed(caller.clone().into()), non_native_asset_id2.into(), non_native_asset_id1.into(), 33266599933266.to_balance::(), 50000000000001.to_balance::()) + verify { + // verify only trading result as rest of the assertion is in unit test + assert_eq!(::Currency::free_balance(non_native_asset_id1, &caller).into(), 833266599933266); + assert_eq!(::Currency::free_balance(non_native_asset_id2, &caller).into(), 850000000000001); + } + + multiswap_buy_asset { + + // NOTE: All atomic swaps in the chain involve sold tokens that are pooled with the native token for bnb, which is the worst case + + // liquidity tokens + let x in 3_u32..100; + + init!(); + let caller: T::AccountId = whitelisted_caller(); + + let mint_amount: BalanceOf = 1000000000000000000.to_balance::(); + let pool_creation_amount: BalanceOf = 10000000000000000.to_balance::(); + let trade_amount: BalanceOf = 1000000000000.to_balance::(); + + // Create 4 tokens to avoid DisallowedPool (Bootstrap by default disallows pools between 0 & 4) + ::Currency::create(&caller, mint_amount).unwrap(); + ::Currency::create(&caller, mint_amount).unwrap(); + ::Currency::create(&caller, mint_amount).unwrap(); + + let mut initial_asset_id = ::Currency::get_next_currency_id(); + let native_asset_id = ::NativeCurrencyId::get(); + + if initial_asset_id == native_asset_id { + assert_eq!(::Currency::create(&caller, mint_amount * x.into()).unwrap(), native_asset_id); + initial_asset_id = initial_asset_id + 1_u32.into(); + } else { + assert_ok!(::Currency::mint(native_asset_id, &caller, mint_amount * x.into())); + } + + // Create all the non-native tokens we will need + for i in 0..x{ + assert_eq!(::Currency::create(&caller, mint_amount).unwrap(), initial_asset_id + i.into()); + } + + // Create all pool with the subsequent non-native tokens + for i in 0.. (x - 1){ + assert_ok!(Xyk::::create_pool(RawOrigin::Signed(caller.clone().into()).into(), initial_asset_id + i.into(), pool_creation_amount, initial_asset_id + (i+1).into(), pool_creation_amount)); + } + + // Create all pool with the subsequent non-native tokens + for i in 0..x{ + assert_ok!(Xyk::::create_pool(RawOrigin::Signed(caller.clone().into()).into(), native_asset_id, pool_creation_amount, initial_asset_id + i.into(), pool_creation_amount)); + } + + let mut swap_token_list: Vec> = vec![]; + + for i in 0..x{ + swap_token_list.push(initial_asset_id + i.into()); + } + + let expected: BalanceOf = mint_amount - pool_creation_amount - pool_creation_amount; + assert_eq!(::Currency::free_balance(initial_asset_id + (x - 1).into(), &caller), expected); + + }: multiswap_buy_asset(RawOrigin::Signed(caller.clone().into()), swap_token_list, trade_amount, trade_amount*10_u32.into()) + verify { + // verify only trading result as rest of the assertion is in unit test + assert!(::Currency::free_balance(initial_asset_id + (x - 1).into(), &caller) > expected); + } + + mint_liquidity { + // 1. create, + // 2. promote, + // 3. mint/activate, + // 4. wait some, + // 5. mint – second mint is prob harder then 1st, as there are some data in + + init!(); + let caller: T::AccountId = whitelisted_caller(); + let initial_amount:BalanceOf = 1000000000000000000000.to_balance::(); + let expected_native_asset_id = ::NativeCurrencyId::get(); + let native_asset_id = ::Currency::create(&caller, initial_amount).unwrap(); + let non_native_asset_id1 = ::Currency::create(&caller, initial_amount).unwrap(); + let non_native_asset_id2 = ::Currency::create(&caller, initial_amount).unwrap(); + let liquidity_asset_id = non_native_asset_id2 + 1_u32.into(); + let pool_create_first_token_amount = 40000000000000000000_u128.to_balance::(); + let pool_create_second_token_amount = 60000000000000000000_u128.to_balance::(); + let pool_mint_first_token_amount = 20000000000000000000_u128.to_balance::(); + let pool_mint_second_token_amount = 30000000000000000001_u128.to_balance::(); + + + Xyk::::create_pool( + RawOrigin::Signed(caller.clone().into()).into(), + non_native_asset_id1, + pool_create_first_token_amount, + non_native_asset_id2, + pool_create_second_token_amount + ).unwrap(); + let initial_liquidity_amount = ::Currency::total_issuance(liquidity_asset_id); + + + // as ProofOfStakeRewardsApi>::update_pool_promotion( liquidity_asset_id, Some(1u8)).unwrap(); + T::LiquidityMiningRewards::enable(liquidity_asset_id, 1u8); + + Xyk::::mint_liquidity( + RawOrigin::Signed(caller.clone().into()).into(), + non_native_asset_id1, + non_native_asset_id2, + pool_mint_first_token_amount, + pool_mint_second_token_amount, + ).unwrap(); + + let liquidity_amount_after_first_mint = ::Currency::total_issuance(liquidity_asset_id); + + assert!( liquidity_amount_after_first_mint > initial_liquidity_amount); + + forward_to_next_session!(); + + }: mint_liquidity(RawOrigin::Signed(caller.clone().into()), non_native_asset_id1, non_native_asset_id2, 20000000000000000000.to_balance::(), 30000000000000000001.to_balance::()) + verify { + let liquidity_amount_after_second_mint = ::Currency::total_issuance(liquidity_asset_id); + + assert!( + liquidity_amount_after_second_mint > liquidity_amount_after_first_mint + ) + } + + mint_liquidity_using_vesting_native_tokens { + // NOTE: duplicates test case XYK::mint_W + + init!(); + let caller: T::AccountId = whitelisted_caller(); + let initial_amount:BalanceOf = 1000000000000000000000.to_balance::(); + let expected_native_asset_id = ::NativeCurrencyId::get(); + let native_asset_id = ::NativeCurrencyId::get(); + while ::Currency::create(&caller, initial_amount).unwrap() < native_asset_id { + } + + ::Currency::mint(native_asset_id, &caller, MILION.to_balance::()).expect("Token creation failed"); + ::Currency::create(&caller, initial_amount).unwrap(); + let non_native_asset_id2 = ::Currency::create(&caller, initial_amount).unwrap(); + let liquidity_asset_id = non_native_asset_id2 + 1_u32.into(); + let pool_creation_asset_1_amount = 40000000000000000000_u128.to_balance::(); + let pool_creation_asset_2_amount = 60000000000000000000_u128.to_balance::(); + let initial_liquidity_amount = pool_creation_asset_1_amount / 2_u32.into() + pool_creation_asset_2_amount / 2_u32.into(); + let lock = 1_000_000_u32; + + ::Currency::mint( + ::NativeCurrencyId::get(), + &caller, + initial_amount + ).expect("Token creation failed"); + + Xyk::::create_pool( + RawOrigin::Signed(caller.clone().into()).into(), + native_asset_id, + pool_creation_asset_1_amount, + non_native_asset_id2, + pool_creation_asset_2_amount + ).unwrap(); + // as ProofOfStakeRewardsApi>::update_pool_promotion( liquidity_asset_id, Some(1u8)).unwrap(); + T::LiquidityMiningRewards::enable(liquidity_asset_id, 1u8); + + + assert_eq!( + ::Currency::total_issuance(liquidity_asset_id), + initial_liquidity_amount + ); + + forward_to_next_session!(); + + ::VestingProvider::lock_tokens(&caller, native_asset_id, initial_amount - pool_creation_asset_1_amount, None, lock.into()).unwrap(); + + forward_to_next_session!(); + + Xyk::::mint_liquidity_using_vesting_native_tokens( + RawOrigin::Signed(caller.clone().into()).into(), 10000000000000000000.to_balance::(), non_native_asset_id2, 20000000000000000000.to_balance::() + ).unwrap(); + + forward_to_next_session!(); + + let pre_minting_liq_token_amount = ::Currency::total_issuance(liquidity_asset_id); + + }: mint_liquidity_using_vesting_native_tokens(RawOrigin::Signed(caller.clone().into()), 10000000000000000000.to_balance::(), non_native_asset_id2, 20000000000000000000.to_balance::()) + verify { + assert!( + ::Currency::total_issuance(liquidity_asset_id) > pre_minting_liq_token_amount + ); + } + + burn_liquidity { + // 1. create, + // 2. promote, + // 3. mint( activates tokens automatically) + // 4. wait some, + // 5. burn all ( automatically unreserves ) + + init!(); + let caller: T::AccountId = whitelisted_caller(); + let initial_amount:BalanceOf = 1000000000000000000000.to_balance::(); + let expected_native_asset_id = ::NativeCurrencyId::get(); + let native_asset_id = ::Currency::create(&caller, initial_amount).unwrap(); + let non_native_asset_id1 = ::Currency::create(&caller, initial_amount).unwrap(); + let non_native_asset_id2 = ::Currency::create(&caller, initial_amount).unwrap(); + let liquidity_asset_id = non_native_asset_id2 + 1_u32.into(); + let pool_create_first_token_amount = 40000000000000000000_u128.to_balance::(); + let pool_create_second_token_amount = 60000000000000000000_u128.to_balance::(); + let pool_mint_first_token_amount = 20000000000000000000_u128.to_balance::(); + let pool_mint_second_token_amount = 30000000000000000001_u128.to_balance::(); + + Xyk::::create_pool(RawOrigin::Signed(caller.clone().into()).into(), non_native_asset_id1, pool_create_first_token_amount, non_native_asset_id2, pool_create_second_token_amount).unwrap(); + // as ProofOfStakeRewardsApi>::update_pool_promotion( liquidity_asset_id, Some(1u8)).unwrap(); + T::LiquidityMiningRewards::enable(liquidity_asset_id, 1u8); + + + assert!(Xyk::::liquidity_pool(liquidity_asset_id).is_some()); + + Xyk::::mint_liquidity(RawOrigin::Signed(caller.clone().into()).into(), non_native_asset_id1, non_native_asset_id2, pool_mint_first_token_amount, pool_mint_second_token_amount).unwrap(); + + forward_to_next_session!(); + let total_liquidity_after_minting = ::Currency::total_issuance(liquidity_asset_id); + + + }: burn_liquidity(RawOrigin::Signed(caller.clone().into()), non_native_asset_id1, non_native_asset_id2, total_liquidity_after_minting) + verify { + assert_eq!(Xyk::::liquidity_pool(liquidity_asset_id), Some((non_native_asset_id1, non_native_asset_id2))); + assert_eq!(::Currency::total_issuance(liquidity_asset_id), BalanceOf::::zero()); + } + + provide_liquidity_with_conversion { + let caller: T::AccountId = whitelisted_caller(); + let initial_amount:BalanceOf = 1_000_000_000.to_balance::(); + ::Currency::create(&caller, initial_amount).unwrap(); + let asset_id_1 = ::Currency::create(&caller, initial_amount).unwrap(); + let asset_id_2 = ::Currency::create(&caller, initial_amount).unwrap(); + let liquidity_asset_id = asset_id_2 + 1_u32.into(); + + Xyk::::create_pool(RawOrigin::Signed(caller.clone().into()).into(), asset_id_1, 500_000_000.to_balance::(), asset_id_2, 500_000_000.to_balance::()).unwrap(); + + }: provide_liquidity_with_conversion(RawOrigin::Signed(caller.clone().into()), liquidity_asset_id, asset_id_1, 100_000_u128.to_balance::()) + verify { + + let post_asset_amount_1 = ::Currency::free_balance(asset_id_1, &caller); + let post_asset_amount_2 = ::Currency::free_balance(asset_id_2, &caller); + assert_eq!(post_asset_amount_1, 499_900_002.to_balance::()); + assert_eq!(post_asset_amount_2, 500_000_000.to_balance::()); + + let post_pool_balance = Xyk::::asset_pool((asset_id_1, asset_id_2)); + assert_eq!(post_pool_balance.0, 500_099_946.to_balance::()); + assert_eq!(post_pool_balance.1, 500_000_000.to_balance::()); + } + + compound_rewards { + let other: T::AccountId = account("caller1", 0, 0); + let caller: T::AccountId = whitelisted_caller(); + let reward_ratio = 1_000_000.to_balance::(); + let initial_amount:BalanceOf = 1_000_000_000.to_balance::(); + let pool_amount:BalanceOf = initial_amount / 2_u32.into(); + + // Create 4 tokens to avoid DisallowedPool (Bootstrap by default disallows pools between 0 & 4) + ::Currency::create(&caller, initial_amount).unwrap(); + ::Currency::create(&caller, initial_amount).unwrap(); + ::Currency::create(&caller, initial_amount).unwrap(); + + let next_asset_id = ::Currency::get_next_currency_id(); + let asset_id_1; + let asset_id_2; + if next_asset_id == 0_u32.into() { + // in test there is no other currencies created + asset_id_1 = ::Currency::create(&caller, initial_amount).unwrap(); + ::Currency::mint(asset_id_1, &other, initial_amount * reward_ratio).unwrap(); + asset_id_2 = ::Currency::create(&caller, initial_amount).unwrap(); + ::Currency::mint(asset_id_2, &other, initial_amount * reward_ratio).unwrap(); + } else { + // in bench the genesis sets up the assets + asset_id_1 = ::NativeCurrencyId::get(); + ::Currency::mint(asset_id_1, &caller, initial_amount).unwrap(); + ::Currency::mint(asset_id_1, &other, initial_amount * reward_ratio).unwrap(); + asset_id_2 = ::Currency::create(&caller, initial_amount).unwrap(); + ::Currency::mint(asset_id_2, &other, initial_amount * reward_ratio).unwrap(); + } + + let liquidity_asset_id = asset_id_2 + 1_u32.into(); + as ComputeIssuance>::initialize(); + + Xyk::::create_pool(RawOrigin::Signed(caller.clone().into()).into(), asset_id_1, pool_amount, asset_id_2, pool_amount).unwrap(); + T::LiquidityMiningRewards::enable(liquidity_asset_id, 1u8); + T::LiquidityMiningRewards::activate_liquidity(caller.clone(), liquidity_asset_id, pool_amount, None).unwrap(); + + // mint for other to split the rewards rewards_ratio:1 + Xyk::::mint_liquidity( + RawOrigin::Signed(other.clone().into()).into(), + asset_id_1, + asset_id_2, + pool_amount * reward_ratio, + pool_amount * reward_ratio + 1_u32.into(), + ).unwrap(); + + frame_system::Pallet::::set_block_number(50_000u32.into()); + as ComputeIssuance>::compute_issuance(1); + + let mut pre_pool_balance = Xyk::::asset_pool((asset_id_1, asset_id_2)); + let rewards_to_claim = T::LiquidityMiningRewards::calculate_rewards_amount(caller.clone(), liquidity_asset_id).unwrap(); + let swap_amount = Xyk::::calculate_balanced_sell_amount(rewards_to_claim, pre_pool_balance.0).unwrap(); + let balance_native_before = ::Currency::free_balance(::NativeCurrencyId::get(), &caller); + let balance_asset_before = ::Currency::free_balance(liquidity_asset_id, &caller); + pre_pool_balance = Xyk::::asset_pool((asset_id_1, asset_id_2)); + + }: compound_rewards(RawOrigin::Signed(caller.clone().into()), liquidity_asset_id, Permill::one()) + verify { + + assert_eq!( + T::LiquidityMiningRewards::calculate_rewards_amount(caller.clone(), liquidity_asset_id).unwrap(), + 0_u32.into(), + ); + + let balance_native_after = ::Currency::free_balance(::NativeCurrencyId::get(), &caller); + let balance_asset_after = ::Currency::free_balance(liquidity_asset_id, &caller); + // surplus asset amount + assert!(balance_native_before < balance_native_after); + assert_eq!(balance_asset_before, balance_asset_after); + + let post_pool_balance = Xyk::::asset_pool((asset_id_1, asset_id_2)); + assert!( pre_pool_balance.0 < post_pool_balance.0); + assert!( pre_pool_balance.1 >= post_pool_balance.1); + } + + + impl_benchmark_test_suite!(Xyk, crate::mock::new_test_ext(), crate::mock::Test) +} diff --git a/gasp-node/pallets/xyk/src/lib.rs b/gasp-node/pallets/xyk/src/lib.rs new file mode 100644 index 000000000..3123560ea --- /dev/null +++ b/gasp-node/pallets/xyk/src/lib.rs @@ -0,0 +1,3864 @@ +//! # XYK pallet + +//! Provides functions for token operations, swapping tokens, creating token pools, minting and burning liquidity and supporting public functions +//! +//! ### Token operation functions: +//! - create_pool +//! - mint_liquidity +//! - burn_liquidity +//! - sell_asset +//! - buy_asset +//! - compound_rewards +//! - provide_liquidity_with_conversion +//! +//! ### Supporting public functions: +//! - calculate_sell_price +//! - calculate_buy_price +//! - calculate_sell_price_id +//! - calculate_buy_price_id +//! - get_liquidity_token +//! - get_burn_amount +//! - account_id +//! - settle_treasury_buy_and_burn +//! - calculate_balanced_sell_amount +//! - get_liq_tokens_for_trading +//! +//! # fn create_pool +//! -Sets the initial ratio/price of both assets to each other depending on amounts of each assets when creating pool. +//! +//! -Transfers assets from user to vault and makes appropriate entry to pools map, where are assets amounts kept. +//! +//! -Issues new liquidity asset in amount corresponding to amounts creating the pool, marks them as liquidity assets corresponding to this pool and transfers them to user. +//! first_token_amount +//! ### arguments +//! `origin` - sender of a fn, user creating the pool +//! +//! `first_token_id` - id of first token which will be directly inter-tradeable in a pair of first_token_id-second_token_id +//! +//! `first_token_amount` - amount of first token in which the pool will be initiated, which will set their initial ratio/price +//! +//! `second_token_id` - id of second token which will be directly inter-tradeable in a pair of first_token_id-second_token_id +//! +//! `second_token_amount` - amount of second token in which the pool will be initiated, which will set their initial ratio/price +//! +//! ### Example +//! ```ignore +//! create_pool( +//! Origin::signed(1), +//! 0, +//! 1000, +//! 1, +//! 2000, +//! ) +//! ``` +//! Account_id 1 created pool with tokens 0 and 1, with amounts 1000, 2000. Initial ratio is 1:2. Liquidity token with new id created in an amount of 1500 and transfered to user 1. +//! +//! ### Errors +//! `ZeroAmount` - creating pool with 0 amount of first or second token +//! +//! `PoolAlreadyExists` - creating pool which already exists +//! +//! `NotEnoughTokens` - creating pool with amounts higher then user owns +//! +//! `SameToken` - creating pool with same token +//! +//! # fn sell_token +//! -Sells/exchanges set amount of sold token for corresponding amount by xyk formula of bought token +//! ### arguments +//! `origin` - sender of a fn, user creating the pool +//! +//! `sold_token_id` - token which will be sold +//! +//! `bought_token_id` - token which will be bought +//! +//! `sold_token_amount` - amount of token to be sold +//! +//! `min_amount_out` - minimal acceptable amount of bought token received after swap +//! +//! ### Example +//! ```ignore +//! sell_token ( +//! Origin::signed(1), +//! 0, +//! 1, +//! 1000, +//! 800, +//!) +//! ``` +//! Account_id 1 sells/exchanges 1000 token 0 for corresponding amount of token 1, while requiring at least 800 token 1 +//! +//! ### Errors +//! `ZeroAmount` - buying 0 tokens +//! +//! `NoSuchPool` - pool sold_token_id - bought_token_id does not exist +//! +//! `NotEnoughTokens` - selling more tokens then user owns +//! +//! `InsufficientOutputAmount` - bought tokens to receive amount is lower then required min_amount_out +//! +//! # fn buy_token +//! -Buys/exchanges set amount of bought token for corresponding amount by xyk formula of sold token +//! ### arguments +//! `origin` - sender of a fn, user creating the pool +//! +//! `sold_token_id` - token which will be sold +//! +//! `bought_token_id` - token which will be bought +//! +//! `bought_token_amount` - amount of token to be bought +//! +//! `max_amount_in` - maximal acceptable amount of sold token to pay for requested bought amount +//! +//! ### Example +//! ```ignore +//! buy_token ( +//! Origin::signed(1), +//! 0, +//! 1, +//! 1000, +//! 800, +//!) +//! ``` +//! Account_id 1 buys/exchanges 1000 tokens 1 by paying corresponding amount by xyk formula of tokens 0 +//! +//! ### Errors +//! `ZeroAmount` - selling 0 tokens +//! +//! `NoSuchPool` - pool sold_token_id - bought_token_id does not exist +//! +//! `NotEnoughTokens` - selling more tokens then user owns +//! +//! `InsufficientInputAmount` - sold tokens to pay is higher then maximum acceptable value of max_amount_in +//! +//! # fn mint_liquidity +//! -Adds liquidity to pool, providing both tokens in actual ratio +//! -First token amount is provided by user, second token amount is calculated by function, depending on actual ratio +//! -Mints and transfers corresponding amount of liquidity token to mintin user +//! +//! ### arguments +//! `origin` - sender of a fn, user creating the pool +//! +//! first_token_id - first token in pair +//! +//! second_token_id - second token in pair +//! +//! first_token_amount - amount of first_token_id, second token amount will be calculated +//! +//! ### Example +//! ```ignore +//! mint_liquidity ( +//! Origin::signed(1), +//! 0, +//! 1, +//! 1000, +//!) +//! ``` +//! If pool token 0 - token 1 has tokens in amounts 9000:18000 (total liquidity tokens 27000) +//! +//! Account_id 1 added liquidity to pool token 0 - token 1, by providing 1000 token 0 and corresponding amount of token 1. In this case 2000, as the ratio in pool is 1:2. +//! Account_id 1 also receives corresponding liquidity tokens in corresponding amount. In this case he gets 10% of all corresponding liquidity tokens, as he is providing 10% of all provided liquidity in pool. +//! 3000 out of total 30000 liquidity tokens is now owned by Account_id 1 +//! +//! ### Errors +//! `ZeroAmount` - minting with 0 tokens +//! +//! `NoSuchPool` - pool first_token_id - second_token_id does not exist +//! +//! `NotEnoughTokens` - minting with more tokens then user owns, either first_token_id or second_token_id +//! +//! # fn burn_liquidity +//! -Removes tokens from liquidity pool and transfers them to user, by burning user owned liquidity tokens +//! -Amount of tokens is determined by their ratio in pool and amount of liq tokens burned +//! +//! ### arguments +//! `origin` - sender of a fn, user creating the pool +//! +//! first_token_id - first token in pair +//! +//! second_token_id - second token in pair +//! +//! liquidity_token_amount - amount of liquidity token amount to burn +//! +//! ### Example +//! ```ignore +//! burn_liquidity ( +//! Origin::signed(1), +//! 0, +//! 1, +//! 3000, +//!) +//! ``` +//! If pool token 0 - token 1 has tokens in amounts 10000:20000 (total liquidity tokens 30000) +//! +//! Account_id 1 is burning 3000 liquidity tokens of pool token 0 - token 1 +//! As Account_id 1 is burning 10% of total liquidity tokens for this pool, user receives in this case 1000 token 0 and 2000 token 1 +//! +//! ### Errors +//! `ZeroAmount` - burning 0 liquidity tokens +//! +//! `NoSuchPool` - pool first_token_id - second_token_id does not exist +//! +//! `NotEnoughTokens` - burning more liquidity tokens than user owns +//! +//! # fn compound_rewards +//! - Claims a specified portion of rewards, and provides them back into the selected pool. +//! - Wraps claim_rewards, sell_asset and mint_liquidity, so that there is minimal surplus of reward asset left after operation. +//! - Current impl assumes a MGX-ASSET pool & rewards in MGX asset +//! +//! ### arguments +//! `origin` - sender of a fn, user claiming rewards and providing liquidity to the pool +//! +//! liquidity_asset_id - the pool where we provide the liquidity +//! +//! amount_permille - portion of rewards to claim +//! +//! ### Example +//! ```ignore +//! compound_rewards ( +//! Origin::signed(1), +//! 2, +//! 1_000, +//!) +//! ``` +//! Claim all of the rewards, currently in MGX, and use them to provide liquidity for the pool with asset id 2 +//! +//! ### Errors +//! - inherits all of the errors from `claim_rewards`, `sell_asset` and `mint_liquidity` +//! +//! `NoSuchLiquidityAsset` - pool with given asset id does not exist +//! +//! `FunctionNotAvailableForThisToken` - not available for this asset id +//! +//! `NotEnoughRewardsEarned` - not enough rewards available +//! +//! # fn provide_liquidity_with_conversion +//! - Given one of the liquidity pool asset, computes balanced sell amount and provides liquidity into the pool +//! - Wraps sell_asset and mint_liquidity +//! +//! ### arguments +//! `origin` - sender of a fn, user claiming rewards and providing liquidity to the pool +//! +//! liquidity_asset_id - the pool where we provide the liquidity +//! +//! provided_asset_id - which asset of the pool +//! +//! provided_asset_amount - amount of the provided asset to use +//! +//! ### Example +//! ```ignore +//! provide_liquidity_with_conversion ( +//! Origin::signed(1), +//! 2, +//! 1, +//! 1_000_000, +//!) +//! ``` +//! Given the liquidity pool with asset id 2, we assume that asset id 1 is one of the pool's pair, compute balanced swap and provide liquidity into the pool +//! +//! ### Errors +//! - inherits all of the errors from `sell_asset` and `mint_liquidity` +//! +//! `NoSuchLiquidityAsset` - pool wiht given asset id does not exist +//! +//! `FunctionNotAvailableForThisToken` - not available for this asset id +//! +//! # calculate_sell_price +//! - Supporting public function accessible through rpc call which calculates and returns bought_token_amount while providing sold_token_amount and respective reserves +//! # calculate_buy_price +//! - Supporting public function accessible through rpc call which calculates and returns sold_token_amount while providing bought_token_amount and respective reserves +//! # calculate_sell_price_id +//! - Same as calculate_sell_price, but providing token_id instead of reserves. Reserves are fetched by function. +//! # calculate_buy_price_id +//! - Same as calculate_buy_price, but providing token_id instead of reserves. Reserves are fetched by function. +//! # get_liquidity_token +//! - Supporting public function accessible through rpc call which returns liquidity_token_id while providing pair token ids +//! # get_burn_amount +//! - Supporting public function accessible through rpc call which returns amounts of tokens received by burning provided liquidity_token_amount in pool of provided token ids +//! # account_id +//! - Returns palled account_id +//! # settle_treasury_buy_and_burn +//! - Supporting function which takes tokens to alocate to treasury and tokens to be used to burn mangata +//! - First step is deciding whether we are using sold or bought token id, depending which is closer to mangata token +//! - In second step, if tokens are mangata, they are placed to treasury and removed from corresponding pool. If tokens are not mangata, but are available in mangata pool, +//! they are swapped to mangata and placed to treasury and removed from corresponding pool. If token is not connected to mangata, token is temporarily placed to treasury and burn treasury. +//! # calculate_balanced_sell_amount +//! - Supporting public function accessible through rpc call which calculates how much amount x we need to swap from total_amount, so that after `y = swap(x)`, the resulting balance equals `(total_amount - x) / y = pool_x / pool_y` +//! - the resulting amounts can then be used to `mint_liquidity` with minimal leftover after operation +//! # get_liq_tokens_for_trading +//! - Supporting public function accessible through rpc call which lists all of the liquidity pool token ids that are available for trading + +#![cfg_attr(not(feature = "std"), no_std)] + +use frame_support::{ + assert_ok, + dispatch::{DispatchErrorWithPostInfo, DispatchResult, PostDispatchInfo}, + ensure, + traits::{tokens::CurrencyId, Contains, Equals}, + PalletId, +}; +use frame_system::ensure_signed; +use sp_core::U256; + +use frame_support::{ + pallet_prelude::*, + traits::{ + tokens::currency::{MultiTokenCurrency, MultiTokenVestingLocks}, + ExistenceRequirement, Get, WithdrawReasons, + }, + transactional, +}; +use frame_system::pallet_prelude::*; +use mangata_support::{ + pools::{ComputeBalances, Inspect, PoolPair, PoolReserves, TreasuryBurn}, + traits::{ + ActivationReservesProviderTrait, GetMaintenanceStatusTrait, PoolCreateApi, + PreValidateSwaps, ProofOfStakeRewardsApi, Valuate, XykFunctionsTrait, + }, +}; +use mangata_types::multipurpose_liquidity::ActivateKind; +use orml_tokens::{MultiTokenCurrencyExtended, MultiTokenReservableCurrency}; +use sp_arithmetic::{helpers_128bit::multiply_by_rational_with_rounding, per_things::Rounding}; +use sp_runtime::{ + traits::{ + AccountIdConversion, Bounded, CheckedAdd, CheckedDiv, CheckedSub, One, Saturating, Zero, + }, + DispatchError, ModuleError, Permill, SaturatedConversion, +}; +use sp_std::{ + collections::btree_set::BTreeSet, + convert::{TryFrom, TryInto}, + ops::Div, + prelude::*, + vec, +}; + +#[cfg(test)] +mod mock; + +#[cfg(test)] +mod tests; + +pub(crate) const LOG_TARGET: &str = "xyk"; + +// syntactic sugar for logging. +#[macro_export] +macro_rules! log { + ($level:tt, $patter:expr $(, $values:expr)* $(,)?) => { + log::$level!( + target: $crate::LOG_TARGET, + concat!("[{:?}] 💸 ", $patter), >::block_number() $(, $values)* + ) + }; +} + +const PALLET_ID: PalletId = PalletId(*b"79b14c96"); + +// Keywords for asset_info +const LIQUIDITY_TOKEN_IDENTIFIER: &[u8] = b"LiquidityPoolToken"; +const HEX_INDICATOR: &[u8] = b"0x"; +const TOKEN_SYMBOL: &[u8] = b"TKN"; +const TOKEN_SYMBOL_SEPARATOR: &[u8] = b"-"; +const DEFAULT_DECIMALS: u32 = 18u32; + +pub use pallet::*; + +mod benchmarking; +pub mod weights; +pub use weights::WeightInfo; + +type AccountIdOf = ::AccountId; + +pub type BalanceOf = <::Currency as MultiTokenCurrency< + ::AccountId, +>>::Balance; + +pub type CurrencyIdOf = <::Currency as MultiTokenCurrency< + ::AccountId, +>>::CurrencyId; + +// type LiquidityMiningRewardsOf = ::AccountId; +#[derive(Eq, PartialEq, Encode, Decode)] +pub enum SwapKind { + Sell, + Buy, +} + +#[frame_support::pallet] +pub mod pallet { + use super::*; + use frame_support::dispatch::DispatchClass; + + #[pallet::pallet] + pub struct Pallet(PhantomData); + + #[pallet::hooks] + impl Hooks> for Pallet {} + + #[cfg(feature = "runtime-benchmarks")] + pub trait XykBenchmarkingConfig: + pallet_issuance::Config + pallet_proof_of_stake::Config + { + } + + #[cfg(not(feature = "runtime-benchmarks"))] + pub trait XykBenchmarkingConfig {} + + // #[cfg(feature = "runtime-benchmarks")] + // pub trait XykRewardsApi: ProofOfStakeRewardsApi> + LiquidityMiningApi{} + // #[cfg(feature = "runtime-benchmarks")] + // impl XykRewardsApi for K where + // K: ProofOfStakeRewardsApi>, + // K: LiquidityMiningApi, + // { + // } + // + // #[cfg(not(feature = "runtime-benchmarks"))] + // pub trait XykRewardsApi: ProofOfStakeRewardsApi>{} + // #[cfg(not(feature = "runtime-benchmarks"))] + // impl XykRewardsApi for K where + // K: ProofOfStakeRewardsApi>, + // { + // } + + #[pallet::config] + pub trait Config: frame_system::Config + XykBenchmarkingConfig { + type RuntimeEvent: From> + IsType<::RuntimeEvent>; + type MaintenanceStatusProvider: GetMaintenanceStatusTrait; + type ActivationReservesProvider: ActivationReservesProviderTrait< + Self::AccountId, + BalanceOf, + CurrencyIdOf, + >; + type Currency: MultiTokenCurrencyExtended + + MultiTokenReservableCurrency; + type NativeCurrencyId: Get>; + type TreasuryPalletId: Get; + type BnbTreasurySubAccDerive: Get<[u8; 4]>; + type LiquidityMiningRewards: ProofOfStakeRewardsApi< + Self::AccountId, + BalanceOf, + CurrencyIdOf, + >; + #[pallet::constant] + type PoolFeePercentage: Get; + #[pallet::constant] + type TreasuryFeePercentage: Get; + #[pallet::constant] + type BuyAndBurnFeePercentage: Get; + type DisallowedPools: Contains<(CurrencyIdOf, CurrencyIdOf)>; + type DisabledTokens: Contains>; + type VestingProvider: MultiTokenVestingLocks< + Self::AccountId, + Currency = ::Currency, + Moment = BlockNumberFor, + >; + type AssetMetadataMutation: AssetMetadataMutationTrait>; + type FeeLockWeight: Get; + type WeightInfo: WeightInfo; + } + + #[pallet::error] + /// Errors + pub enum Error { + /// Pool already Exists + PoolAlreadyExists, + /// Not enought assets + NotEnoughAssets, + /// No such pool exists + NoSuchPool, + /// No such liquidity asset exists + NoSuchLiquidityAsset, + /// Not enought reserve + NotEnoughReserve, + /// Zero amount is not supported + ZeroAmount, + /// Insufficient input amount + InsufficientInputAmount, + /// Insufficient output amount + InsufficientOutputAmount, + /// Asset ids cannot be the same + SameAsset, + /// Asset already exists + AssetAlreadyExists, + /// Asset does not exists + AssetDoesNotExists, + /// Division by zero + DivisionByZero, + /// Unexpected failure + UnexpectedFailure, + /// Unexpected failure + NotPairedWithNativeAsset, + /// Second asset amount exceeded expectations + SecondAssetAmountExceededExpectations, + /// Math overflow + MathOverflow, + /// Liquidity token creation failed + LiquidityTokenCreationFailed, + /// Not enough rewards earned + NotEnoughRewardsEarned, + /// Not a promoted pool + NotAPromotedPool, + /// Past time calculation + PastTimeCalculation, + /// Pool already promoted + PoolAlreadyPromoted, + /// Sold Amount too low + SoldAmountTooLow, + /// Asset id is blacklisted + FunctionNotAvailableForThisToken, + /// Pool considting of passed tokens id is blacklisted + DisallowedPool, + LiquidityCheckpointMathError, + CalculateRewardsMathError, + CalculateCumulativeWorkMaxRatioMathError, + CalculateRewardsAllMathError, + NoRights, + MultiswapShouldBeAtleastTwoHops, + MultiBuyAssetCantHaveSamePoolAtomicSwaps, + MultiSwapCantHaveSameTokenConsequetively, + /// Trading blocked by maintenance mode + TradingBlockedByMaintenanceMode, + PoolIsEmpty, + } + + #[pallet::event] + #[pallet::generate_deposit(pub(super) fn deposit_event)] + pub enum Event { + PoolCreated(T::AccountId, CurrencyIdOf, BalanceOf, CurrencyIdOf, BalanceOf), + AssetsSwapped(T::AccountId, Vec>, BalanceOf, BalanceOf), + SellAssetFailedDueToSlippage( + T::AccountId, + CurrencyIdOf, + BalanceOf, + CurrencyIdOf, + BalanceOf, + BalanceOf, + ), + BuyAssetFailedDueToSlippage( + T::AccountId, + CurrencyIdOf, + BalanceOf, + CurrencyIdOf, + BalanceOf, + BalanceOf, + ), + LiquidityMinted( + T::AccountId, + CurrencyIdOf, + BalanceOf, + CurrencyIdOf, + BalanceOf, + CurrencyIdOf, + BalanceOf, + ), + LiquidityBurned( + T::AccountId, + CurrencyIdOf, + BalanceOf, + CurrencyIdOf, + BalanceOf, + CurrencyIdOf, + BalanceOf, + ), + PoolPromotionUpdated(CurrencyIdOf, Option), + LiquidityActivated(T::AccountId, CurrencyIdOf, BalanceOf), + LiquidityDeactivated(T::AccountId, CurrencyIdOf, BalanceOf), + RewardsClaimed(T::AccountId, CurrencyIdOf, BalanceOf), + MultiSwapAssetFailedOnAtomicSwap( + T::AccountId, + Vec>, + BalanceOf, + ModuleError, + ), + } + + #[pallet::storage] + #[pallet::getter(fn asset_pool)] + pub type Pools = StorageMap< + _, + Blake2_128Concat, + (CurrencyIdOf, CurrencyIdOf), + (BalanceOf, BalanceOf), + ValueQuery, + >; + + #[pallet::storage] + #[pallet::getter(fn liquidity_asset)] + pub type LiquidityAssets = StorageMap< + _, + Blake2_128Concat, + (CurrencyIdOf, CurrencyIdOf), + Option>, + ValueQuery, + >; + + #[pallet::storage] + #[pallet::getter(fn liquidity_pool)] + pub type LiquidityPools = StorageMap< + _, + Blake2_128Concat, + CurrencyIdOf, + Option<(CurrencyIdOf, CurrencyIdOf)>, + ValueQuery, + >; + + #[pallet::storage] + #[pallet::getter(fn get_total_number_of_swaps)] + pub type TotalNumberOfSwaps = StorageValue<_, u128, ValueQuery>; + + #[pallet::genesis_config] + pub struct GenesisConfig { + pub created_pools_for_staking: Vec<( + T::AccountId, + CurrencyIdOf, + BalanceOf, + CurrencyIdOf, + BalanceOf, + CurrencyIdOf, + )>, + } + + impl Default for GenesisConfig { + fn default() -> Self { + GenesisConfig { created_pools_for_staking: vec![] } + } + } + + #[pallet::genesis_build] + impl BuildGenesisConfig for GenesisConfig { + fn build(&self) { + self.created_pools_for_staking.iter().for_each( + |( + account_id, + native_token_id, + native_token_amount, + pooled_token_id, + pooled_token_amount, + liquidity_token_id, + )| { + if ::Currency::exists({ *liquidity_token_id }.into()) { + assert!( + as XykFunctionsTrait< + T::AccountId, + BalanceOf, + CurrencyIdOf, + >>::mint_liquidity( + account_id.clone(), + *native_token_id, + *pooled_token_id, + *native_token_amount, + *pooled_token_amount, + true, + ) + .is_ok(), + "Pool mint failed" + ); + } else { + let created_liquidity_token_id: CurrencyIdOf = + ::Currency::get_next_currency_id().into(); + assert_eq!( + created_liquidity_token_id, *liquidity_token_id, + "Assets not initialized in the expected sequence", + ); + assert_ok!( as XykFunctionsTrait< + T::AccountId, + BalanceOf, + CurrencyIdOf, + >>::create_pool( + account_id.clone(), + *native_token_id, + *native_token_amount, + *pooled_token_id, + *pooled_token_amount + )); + } + }, + ) + } + } + + // XYK extrinsics. + #[pallet::call] + impl Pallet { + #[pallet::call_index(0)] + #[pallet::weight(<::WeightInfo>::create_pool().saturating_add(T::FeeLockWeight::get()))] + pub fn create_pool( + origin: OriginFor, + first_asset_id: CurrencyIdOf, + first_asset_amount: BalanceOf, + second_asset_id: CurrencyIdOf, + second_asset_amount: BalanceOf, + ) -> DispatchResultWithPostInfo { + let sender = ensure_signed(origin)?; + + ensure!( + !T::DisabledTokens::contains(&first_asset_id) && + !T::DisabledTokens::contains(&second_asset_id), + Error::::FunctionNotAvailableForThisToken + ); + + ensure!( + !T::DisallowedPools::contains(&(first_asset_id, second_asset_id)), + Error::::DisallowedPool, + ); + + , CurrencyIdOf>>::create_pool( + sender, + first_asset_id, + first_asset_amount, + second_asset_id, + second_asset_amount, + )?; + + Ok(().into()) + } + + /// Executes sell_asset swap. + /// First the swap is prevalidated, if it is successful then the extrinsic is accepted. Beyond this point the exchange commission will be charged. + /// The sold amount of the sold asset is used to determine the bought asset amount. + /// If the bought asset amount is lower than the min_amount_out then it will fail on slippage. + /// The percentage exchange commission is still charged even if the swap fails on slippage. Though the swap itself will be a no-op. + /// The slippage is calculated based upon the sold_asset_amount. + /// Upon slippage failure, the extrinsic is marked "successful", but an event for the failure is emitted + /// + /// + /// # Args: + /// - `sold_asset_id` - The token being sold + /// - `bought_asset_id` - The token being bought + /// - `sold_asset_amount`: The amount of the sold token being sold + /// - `min_amount_out` - The minimum amount of bought asset that must be bought in order to not fail on slippage. Slippage failures still charge exchange commission. + #[pallet::call_index(1)] + #[pallet::weight((<::WeightInfo>::sell_asset().saturating_add(T::FeeLockWeight::get()), DispatchClass::Operational, Pays::No))] + #[deprecated(note = "multiswap_sell_asset should be used instead")] + pub fn sell_asset( + origin: OriginFor, + sold_asset_id: CurrencyIdOf, + bought_asset_id: CurrencyIdOf, + sold_asset_amount: BalanceOf, + min_amount_out: BalanceOf, + ) -> DispatchResultWithPostInfo { + let sender = ensure_signed(origin)?; + + , CurrencyIdOf>>::sell_asset( + sender, + sold_asset_id, + bought_asset_id, + sold_asset_amount, + min_amount_out, + false, + ) + .map_err(|err| DispatchErrorWithPostInfo { + post_info: PostDispatchInfo { + actual_weight: Some(<::WeightInfo>::sell_asset()), + pays_fee: Pays::Yes, + }, + error: err, + })?; + TotalNumberOfSwaps::::mutate(|v| *v = v.saturating_add(One::one())); + Ok(Pays::No.into()) + } + + /// Executes a multiswap sell asset in a series of sell asset atomic swaps. + /// + /// Multiswaps must fee lock instead of paying transaction fees. + /// + /// First the multiswap is prevalidated, if it is successful then the extrinsic is accepted + /// and the exchange commission will be charged upon execution on the **first** swap using **sold_asset_amount**. + /// + /// Upon failure of an atomic swap or bad slippage, all the atomic swaps are reverted and the exchange commission is charged. + /// Upon such a failure, the extrinsic is marked "successful", but an event for the failure is emitted + /// + /// # Args: + /// - `swap_token_list` - This list of tokens is the route of the atomic swaps, starting with the asset sold and ends with the asset finally bought + /// - `sold_asset_amount`: The amount of the first asset sold + /// - `min_amount_out` - The minimum amount of last asset that must be bought in order to not fail on slippage. Slippage failures still charge exchange commission. + #[pallet::call_index(2)] + #[pallet::weight((<::WeightInfo>::multiswap_sell_asset(swap_token_list.len() as u32).saturating_add(T::FeeLockWeight::get()), DispatchClass::Operational, Pays::No))] + pub fn multiswap_sell_asset( + origin: OriginFor, + swap_token_list: Vec>, + sold_asset_amount: BalanceOf, + min_amount_out: BalanceOf, + ) -> DispatchResultWithPostInfo { + let sender = ensure_signed(origin)?; + + if let (Some(sold_asset_id), Some(bought_asset_id), 2) = + (swap_token_list.get(0), swap_token_list.get(1), swap_token_list.len()) + { + , CurrencyIdOf>>::sell_asset( + sender, + *sold_asset_id, + *bought_asset_id, + sold_asset_amount, + min_amount_out, + false, + ) + } else { + , CurrencyIdOf>>::multiswap_sell_asset( + sender, + swap_token_list.clone(), + sold_asset_amount, + min_amount_out, + false, + false, + ) + } + .map_err(|err| DispatchErrorWithPostInfo { + post_info: PostDispatchInfo { + actual_weight: Some(<::WeightInfo>::multiswap_sell_asset( + swap_token_list.len() as u32, + )), + pays_fee: Pays::Yes, + }, + error: err, + })?; + Ok(Pays::No.into()) + } + + /// Executes buy_asset swap. + /// First the swap is prevalidated, if it is successful then the extrinsic is accepted. Beyond this point the exchange commission will be charged. + /// The bought of the bought asset is used to determine the sold asset amount. + /// If the sold asset amount is higher than the max_amount_in then it will fail on slippage. + /// The percentage exchange commission is still charged even if the swap fails on slippage. Though the swap itself will be a no-op. + /// The slippage is calculated based upon the sold asset amount. + /// Upon slippage failure, the extrinsic is marked "successful", but an event for the failure is emitted + /// + /// + /// # Args: + /// - `sold_asset_id` - The token being sold + /// - `bought_asset_id` - The token being bought + /// - `bought_asset_amount`: The amount of the bought token being bought + /// - `max_amount_in` - The maximum amount of sold asset that must be sold in order to not fail on slippage. Slippage failures still charge exchange commission. + #[pallet::call_index(3)] + #[pallet::weight((<::WeightInfo>::buy_asset().saturating_add(T::FeeLockWeight::get()), DispatchClass::Operational, Pays::No))] + #[deprecated(note = "multiswap_buy_asset should be used instead")] + pub fn buy_asset( + origin: OriginFor, + sold_asset_id: CurrencyIdOf, + bought_asset_id: CurrencyIdOf, + bought_asset_amount: BalanceOf, + max_amount_in: BalanceOf, + ) -> DispatchResultWithPostInfo { + let sender = ensure_signed(origin)?; + + , CurrencyIdOf>>::buy_asset( + sender, + sold_asset_id, + bought_asset_id, + bought_asset_amount, + max_amount_in, + false, + ) + .map_err(|err| DispatchErrorWithPostInfo { + post_info: PostDispatchInfo { + actual_weight: Some(<::WeightInfo>::buy_asset()), + pays_fee: Pays::Yes, + }, + error: err, + })?; + TotalNumberOfSwaps::::mutate(|v| *v = v.saturating_add(One::one())); + Ok(Pays::No.into()) + } + + /// Executes a multiswap buy asset in a series of buy asset atomic swaps. + /// + /// Multiswaps must fee lock instead of paying transaction fees. + /// + /// First the multiswap is prevalidated, if it is successful then the extrinsic is accepted + /// and the exchange commission will be charged upon execution on the *first* swap using *max_amount_in*. + /// multiswap_buy_asset cannot have two (or more) atomic swaps on the same pool. + /// multiswap_buy_asset prevaildation only checks for whether there are enough funds to pay for the exchange commission. + /// Failure to have the required amount of first asset funds will result in failure (and charging of the exchange commission). + /// + /// Upon failure of an atomic swap or bad slippage, all the atomic swaps are reverted and the exchange commission is charged. + /// Upon such a failure, the extrinsic is marked "successful", but an event for the failure is emitted + /// + /// # Args: + /// - `swap_token_list` - This list of tokens is the route of the atomic swaps, starting with the asset sold and ends with the asset finally bought + /// - `bought_asset_amount`: The amount of the last asset bought + /// - `max_amount_in` - The maximum amount of first asset that can be sold in order to not fail on slippage. Slippage failures still charge exchange commission. + #[pallet::call_index(4)] + #[pallet::weight((<::WeightInfo>::multiswap_buy_asset(swap_token_list.len() as u32).saturating_add(T::FeeLockWeight::get()), DispatchClass::Operational, Pays::No))] + pub fn multiswap_buy_asset( + origin: OriginFor, + swap_token_list: Vec>, + bought_asset_amount: BalanceOf, + max_amount_in: BalanceOf, + ) -> DispatchResultWithPostInfo { + let sender = ensure_signed(origin)?; + + if let (Some(sold_asset_id), Some(bought_asset_id), 2) = + (swap_token_list.get(0), swap_token_list.get(1), swap_token_list.len()) + { + , CurrencyIdOf>>::buy_asset( + sender, + *sold_asset_id, + *bought_asset_id, + bought_asset_amount, + max_amount_in, + false, + ) + } else { + , CurrencyIdOf>>::multiswap_buy_asset( + sender, + swap_token_list.clone(), + bought_asset_amount, + max_amount_in, + false, + false, + ) + } + .map_err(|err| DispatchErrorWithPostInfo { + post_info: PostDispatchInfo { + actual_weight: Some(<::WeightInfo>::multiswap_buy_asset( + swap_token_list.len() as u32, + )), + pays_fee: Pays::Yes, + }, + error: err, + })?; + Ok(Pays::No.into()) + } + + #[pallet::call_index(5)] + #[pallet::weight(<::WeightInfo>::mint_liquidity_using_vesting_native_tokens().saturating_add(T::FeeLockWeight::get()))] + #[transactional] + pub fn mint_liquidity_using_vesting_native_tokens_by_vesting_index( + origin: OriginFor, + native_asset_vesting_index: u32, + vesting_native_asset_unlock_some_amount_or_all: Option>, + second_asset_id: CurrencyIdOf, + expected_second_asset_amount: BalanceOf, + ) -> DispatchResultWithPostInfo { + let sender = ensure_signed(origin)?; + + let liquidity_asset_id = + Pallet::::get_liquidity_asset(Self::native_token_id(), second_asset_id)?; + + ensure!( + , + CurrencyIdOf, + >>::is_enabled(liquidity_asset_id), + Error::::NotAPromotedPool + ); + + let (unlocked_amount, vesting_starting_block, vesting_ending_block_as_balance): ( + BalanceOf, + BlockNumberFor, + BalanceOf, + ) = <::VestingProvider>::unlock_tokens_by_vesting_index( + &sender, + Self::native_token_id().into(), + native_asset_vesting_index, + vesting_native_asset_unlock_some_amount_or_all, + ) + .map(|x| (x.0, x.1, x.2))?; + + let (liquidity_token_id, liquidity_assets_minted, _) = , + CurrencyIdOf, + >>::mint_liquidity( + sender.clone(), + Self::native_token_id(), + second_asset_id, + unlocked_amount, + expected_second_asset_amount, + false, + )?; + + <::VestingProvider>::lock_tokens( + &sender, + liquidity_token_id.into(), + liquidity_assets_minted, + Some(vesting_starting_block), + vesting_ending_block_as_balance, + )?; + + Ok(().into()) + } + + #[pallet::call_index(6)] + #[pallet::weight(<::WeightInfo>::mint_liquidity_using_vesting_native_tokens().saturating_add(T::FeeLockWeight::get()))] + #[transactional] + pub fn mint_liquidity_using_vesting_native_tokens( + origin: OriginFor, + vesting_native_asset_amount: BalanceOf, + second_asset_id: CurrencyIdOf, + expected_second_asset_amount: BalanceOf, + ) -> DispatchResultWithPostInfo { + let sender = ensure_signed(origin)?; + + let liquidity_asset_id = + Pallet::::get_liquidity_asset(Self::native_token_id(), second_asset_id)?; + + ensure!( + , + CurrencyIdOf, + >>::is_enabled(liquidity_asset_id), + Error::::NotAPromotedPool + ); + + let (vesting_starting_block, vesting_ending_block_as_balance): ( + BlockNumberFor, + BalanceOf, + ) = <::VestingProvider>::unlock_tokens( + &sender, + Self::native_token_id().into(), + vesting_native_asset_amount, + ) + .map(|x| (x.0, x.1))?; + + let (liquidity_token_id, liquidity_assets_minted, _) = , + CurrencyIdOf, + >>::mint_liquidity( + sender.clone(), + Self::native_token_id(), + second_asset_id, + vesting_native_asset_amount, + expected_second_asset_amount, + false, + )?; + + <::VestingProvider>::lock_tokens( + &sender, + liquidity_token_id.into(), + liquidity_assets_minted, + Some(vesting_starting_block), + vesting_ending_block_as_balance, + )?; + + Ok(().into()) + } + + #[pallet::call_index(7)] + #[pallet::weight(<::WeightInfo>::mint_liquidity().saturating_add(T::FeeLockWeight::get()))] + pub fn mint_liquidity( + origin: OriginFor, + first_asset_id: CurrencyIdOf, + second_asset_id: CurrencyIdOf, + first_asset_amount: BalanceOf, + expected_second_asset_amount: BalanceOf, + ) -> DispatchResultWithPostInfo { + let sender = ensure_signed(origin)?; + + ensure!( + !T::DisabledTokens::contains(&first_asset_id) && + !T::DisabledTokens::contains(&second_asset_id), + Error::::FunctionNotAvailableForThisToken + ); + + , CurrencyIdOf>>::mint_liquidity( + sender, + first_asset_id, + second_asset_id, + first_asset_amount, + expected_second_asset_amount, + true, + )?; + + Ok(().into()) + } + + #[pallet::call_index(8)] + #[pallet::weight(<::WeightInfo>::compound_rewards().saturating_add(T::FeeLockWeight::get()))] + #[transactional] + pub fn compound_rewards( + origin: OriginFor, + liquidity_asset_id: CurrencyIdOf, + amount_permille: Permill, + ) -> DispatchResultWithPostInfo { + let sender = ensure_signed(origin)?; + + , CurrencyIdOf>>::do_compound_rewards( + sender, + liquidity_asset_id, + amount_permille, + )?; + + Ok(().into()) + } + + #[pallet::call_index(9)] + #[pallet::weight(<::WeightInfo>::provide_liquidity_with_conversion().saturating_add(T::FeeLockWeight::get()))] + #[transactional] + pub fn provide_liquidity_with_conversion( + origin: OriginFor, + liquidity_asset_id: CurrencyIdOf, + provided_asset_id: CurrencyIdOf, + provided_asset_amount: BalanceOf, + ) -> DispatchResultWithPostInfo { + let sender = ensure_signed(origin)?; + + let (first_asset_id, second_asset_id) = LiquidityPools::::get(liquidity_asset_id) + .ok_or(Error::::NoSuchLiquidityAsset)?; + + ensure!( + !T::DisabledTokens::contains(&first_asset_id) && + !T::DisabledTokens::contains(&second_asset_id), + Error::::FunctionNotAvailableForThisToken + ); + + , CurrencyIdOf>>::provide_liquidity_with_conversion( + sender, + first_asset_id, + second_asset_id, + provided_asset_id, + provided_asset_amount, + true, + )?; + + Ok(().into()) + } + + #[pallet::call_index(10)] + #[pallet::weight(<::WeightInfo>::burn_liquidity().saturating_add(T::FeeLockWeight::get()))] + pub fn burn_liquidity( + origin: OriginFor, + first_asset_id: CurrencyIdOf, + second_asset_id: CurrencyIdOf, + liquidity_asset_amount: BalanceOf, + ) -> DispatchResultWithPostInfo { + let sender = ensure_signed(origin)?; + + , CurrencyIdOf>>::burn_liquidity( + sender, + first_asset_id, + second_asset_id, + liquidity_asset_amount, + )?; + + Ok(().into()) + } + } +} + +impl Pallet { + fn total_fee() -> u128 { + T::PoolFeePercentage::get() + + T::TreasuryFeePercentage::get() + + T::BuyAndBurnFeePercentage::get() + } + + pub fn get_max_instant_burn_amount( + user: &AccountIdOf, + liquidity_asset_id: CurrencyIdOf, + ) -> BalanceOf { + Self::get_max_instant_unreserve_amount(user, liquidity_asset_id).saturating_add( + ::Currency::available_balance(liquidity_asset_id.into(), user), + ) + } + + pub fn get_max_instant_unreserve_amount( + user: &AccountIdOf, + liquidity_asset_id: CurrencyIdOf, + ) -> BalanceOf { + ::ActivationReservesProvider::get_max_instant_unreserve_amount( + liquidity_asset_id, + user, + ) + } + + // Sets the liquidity token's info + // May fail if liquidity_asset_id does not exsist + // Should not fail otherwise as the parameters for the max and min length in pallet_assets_info should be set appropriately + pub fn set_liquidity_asset_info( + liquidity_asset_id: CurrencyIdOf, + first_asset_id: CurrencyIdOf, + second_asset_id: CurrencyIdOf, + ) -> DispatchResult { + let mut name: Vec = Vec::::new(); + name.extend_from_slice(LIQUIDITY_TOKEN_IDENTIFIER); + name.extend_from_slice(HEX_INDICATOR); + for bytes in liquidity_asset_id.saturated_into::().to_be_bytes().iter() { + match (bytes >> 4) as u8 { + x @ 0u8..=9u8 => name.push(x.saturating_add(48u8)), + x => name.push(x.saturating_add(55u8)), + } + match (bytes & 0b0000_1111) as u8 { + x @ 0u8..=9u8 => name.push(x.saturating_add(48u8)), + x => name.push(x.saturating_add(55u8)), + } + } + + let mut symbol: Vec = Vec::::new(); + symbol.extend_from_slice(TOKEN_SYMBOL); + symbol.extend_from_slice(HEX_INDICATOR); + for bytes in first_asset_id.saturated_into::().to_be_bytes().iter() { + match (bytes >> 4) as u8 { + x @ 0u8..=9u8 => symbol.push(x.saturating_add(48u8)), + x => symbol.push(x.saturating_add(55u8)), + } + match (bytes & 0b0000_1111) as u8 { + x @ 0u8..=9u8 => symbol.push(x.saturating_add(48u8)), + x => symbol.push(x.saturating_add(55u8)), + } + } + symbol.extend_from_slice(TOKEN_SYMBOL_SEPARATOR); + symbol.extend_from_slice(TOKEN_SYMBOL); + symbol.extend_from_slice(HEX_INDICATOR); + for bytes in second_asset_id.saturated_into::().to_be_bytes().iter() { + match (bytes >> 4) as u8 { + x @ 0u8..=9u8 => symbol.push(x.saturating_add(48u8)), + x => symbol.push(x.saturating_add(55u8)), + } + match (bytes & 0b0000_1111) as u8 { + x @ 0u8..=9u8 => symbol.push(x.saturating_add(48u8)), + x => symbol.push(x.saturating_add(55u8)), + } + } + + T::AssetMetadataMutation::set_asset_info( + liquidity_asset_id, + name, + symbol, + DEFAULT_DECIMALS, + )?; + Ok(()) + } + + // Calculate amount of tokens to be bought by sellling sell_amount + pub fn calculate_sell_price( + input_reserve: BalanceOf, + output_reserve: BalanceOf, + sell_amount: BalanceOf, + ) -> Result, DispatchError> { + let after_fee_percentage: u128 = 10000_u128 + .checked_sub(Self::total_fee()) + .ok_or_else(|| DispatchError::from(Error::::MathOverflow))?; + let input_reserve_saturated: U256 = input_reserve.into().into(); + let output_reserve_saturated: U256 = output_reserve.into().into(); + let sell_amount_saturated: U256 = sell_amount.into().into(); + + let input_amount_with_fee: U256 = + sell_amount_saturated.saturating_mul(after_fee_percentage.into()); + + let numerator: U256 = input_amount_with_fee + .checked_mul(output_reserve_saturated) + .ok_or_else(|| DispatchError::from(Error::::MathOverflow))?; + + let denominator: U256 = input_reserve_saturated + .saturating_mul(10000.into()) + .checked_add(input_amount_with_fee) + .ok_or_else(|| DispatchError::from(Error::::MathOverflow))?; + + let result_u256 = numerator + .checked_div(denominator) + .ok_or_else(|| DispatchError::from(Error::::DivisionByZero))?; + + let result_u128 = u128::try_from(result_u256) + .map_err(|_| DispatchError::from(Error::::MathOverflow))?; + + let result = BalanceOf::::try_from(result_u128) + .map_err(|_| DispatchError::from(Error::::MathOverflow))?; + log!( + info, + "calculate_sell_price: ({:?}, {:?}, {:?}) -> {:?}", + input_reserve, + output_reserve, + sell_amount, + result + ); + Ok(result) + } + + pub fn calculate_sell_price_no_fee( + // Callculate amount of tokens to be received by sellling sell_amount, without fee + input_reserve: BalanceOf, + output_reserve: BalanceOf, + sell_amount: BalanceOf, + ) -> Result, DispatchError> { + let input_reserve_saturated: U256 = input_reserve.into().into(); + let output_reserve_saturated: U256 = output_reserve.into().into(); + let sell_amount_saturated: U256 = sell_amount.into().into(); + + let numerator: U256 = sell_amount_saturated.saturating_mul(output_reserve_saturated); + let denominator: U256 = input_reserve_saturated.saturating_add(sell_amount_saturated); + let result_u256 = numerator + .checked_div(denominator) + .ok_or_else(|| DispatchError::from(Error::::DivisionByZero))?; + let result_u128 = u128::try_from(result_u256) + .map_err(|_| DispatchError::from(Error::::MathOverflow))?; + let result = BalanceOf::::try_from(result_u128) + .map_err(|_| DispatchError::from(Error::::MathOverflow))?; + log!( + info, + "calculate_sell_price_no_fee: ({:?}, {:?}, {:?}) -> {:?}", + input_reserve, + output_reserve, + sell_amount, + result + ); + Ok(result) + } + + // Calculate amount of tokens to be paid, when buying buy_amount + pub fn calculate_buy_price( + input_reserve: BalanceOf, + output_reserve: BalanceOf, + buy_amount: BalanceOf, + ) -> Result, DispatchError> { + let after_fee_percentage: u128 = 10000_u128 + .checked_sub(Self::total_fee()) + .ok_or_else(|| DispatchError::from(Error::::MathOverflow))?; + let input_reserve_saturated: U256 = input_reserve.into().into(); + let output_reserve_saturated: U256 = output_reserve.into().into(); + let buy_amount_saturated: U256 = buy_amount.into().into(); + + let numerator: U256 = input_reserve_saturated + .saturating_mul(buy_amount_saturated) + .checked_mul(10000.into()) + .ok_or_else(|| DispatchError::from(Error::::MathOverflow))?; + + let denominator: U256 = output_reserve_saturated + .checked_sub(buy_amount_saturated) + .ok_or_else(|| DispatchError::from(Error::::NotEnoughReserve))? + .checked_mul(after_fee_percentage.into()) + .ok_or_else(|| DispatchError::from(Error::::MathOverflow))?; + + let result_u256 = numerator + .checked_div(denominator) + .ok_or_else(|| DispatchError::from(Error::::DivisionByZero))? + .checked_add(1.into()) + .ok_or_else(|| DispatchError::from(Error::::MathOverflow))?; + + let result_u128 = u128::try_from(result_u256) + .map_err(|_| DispatchError::from(Error::::MathOverflow))?; + + let result = BalanceOf::::try_from(result_u128) + .map_err(|_| DispatchError::from(Error::::MathOverflow))?; + log!( + info, + "calculate_buy_price: ({:?}, {:?}, {:?}) -> {:?}", + input_reserve, + output_reserve, + buy_amount, + result + ); + Ok(result) + } + + pub fn calculate_balanced_sell_amount( + total_amount: BalanceOf, + reserve_amount: BalanceOf, + ) -> Result, DispatchError> { + let multiplier: U256 = 10_000.into(); + let multiplier_sq: U256 = multiplier.pow(2.into()); + let non_pool_fees: U256 = Self::total_fee() + .checked_sub(T::PoolFeePercentage::get()) + .ok_or_else(|| DispatchError::from(Error::::MathOverflow))? + .into(); // npf + let total_fee: U256 = Self::total_fee().into(); // tf + let total_amount_saturated: U256 = total_amount.into().into(); // z + let reserve_amount_saturated: U256 = reserve_amount.into().into(); // a + + // n: 2*10_000^2*a - 10_000*tf*a - sqrt( (-2*10_000^2*a + 10_000*tf*a)^2 - 4*10_000^2*a*z*(10_000tf - npf*tf + 10_000npf - 10_000^2) ) + // d: 2 * (10_000tf - npf*tf + 10_000npf - 10_000^2) + // x = n / d + + // fee_rate: 2*10_000^2 - 10_000*tf + // simplify n: fee_rate*a - sqrt( fee_rate^2*s^2 - 2*10_000^2*(-d)*a*z ) + let fee_rate = multiplier_sq + .saturating_mul(2.into()) + .checked_sub(total_fee.saturating_mul(multiplier)) + .ok_or_else(|| DispatchError::from(Error::::MathOverflow))?; + + // 2*(10_000tf - npf*tf + 10_000npf - 10_000^2) -> negative number + // change to: d = (10_000^2 + npf*tf - 10_000tf - 10_000npf) * 2 + let denominator_negative = multiplier_sq + .checked_add(non_pool_fees.saturating_mul(total_fee)) + .and_then(|v| v.checked_sub(total_fee.saturating_mul(multiplier))) + .and_then(|v| v.checked_sub(non_pool_fees.saturating_mul(multiplier))) + .ok_or_else(|| DispatchError::from(Error::::MathOverflow))? + .saturating_mul(2.into()); + + // fee_rate^2*a^2 - 2*10_000^2*(-den)*a*z + let sqrt_arg = reserve_amount_saturated + .checked_pow(2.into()) + .and_then(|v| v.checked_mul(fee_rate.pow(2.into()))) + .and_then(|v| { + v.checked_add( + total_amount_saturated + .saturating_mul(reserve_amount_saturated) + .saturating_mul(denominator_negative) + .saturating_mul(multiplier_sq) + .saturating_mul(2.into()), + ) + }) + .ok_or_else(|| DispatchError::from(Error::::MathOverflow))?; + + // n: fee_rate*a - sqrt(...) -> negative + // sqrt(..) - fee_rate*a + let numerator_negative = sqrt_arg + .integer_sqrt() + .checked_sub(reserve_amount_saturated.saturating_mul(fee_rate)) + .ok_or_else(|| DispatchError::from(Error::::MathOverflow))?; + + // -n/-d == n/d + let result_u256 = numerator_negative + .checked_div(denominator_negative) + .ok_or_else(|| DispatchError::from(Error::::DivisionByZero))?; + let result_u128 = u128::try_from(result_u256) + .map_err(|_| DispatchError::from(Error::::MathOverflow))?; + let result = BalanceOf::::try_from(result_u128) + .map_err(|_| DispatchError::from(Error::::MathOverflow))?; + + Ok(result) + } + + pub fn get_liq_tokens_for_trading() -> Result>, DispatchError> { + let result = LiquidityAssets::::iter_values() + .filter_map(|v| v) + .filter(|v| !::Currency::total_issuance((*v).into()).is_zero()) + .collect(); + + Ok(result) + } + + // MAX: 2R + pub fn get_liquidity_asset( + first_asset_id: CurrencyIdOf, + second_asset_id: CurrencyIdOf, + ) -> Result, DispatchError> { + if LiquidityAssets::::contains_key((first_asset_id, second_asset_id)) { + LiquidityAssets::::get((first_asset_id, second_asset_id)) + .ok_or_else(|| Error::::UnexpectedFailure.into()) + } else { + LiquidityAssets::::get((second_asset_id, first_asset_id)) + .ok_or_else(|| Error::::NoSuchPool.into()) + } + } + + pub fn calculate_sell_price_id( + sold_token_id: CurrencyIdOf, + bought_token_id: CurrencyIdOf, + sell_amount: BalanceOf, + ) -> Result, DispatchError> { + let (input_reserve, output_reserve) = + Pallet::::get_reserves(sold_token_id, bought_token_id)?; + + ensure!(!(Self::is_pool_empty(sold_token_id, bought_token_id)?), Error::::PoolIsEmpty); + + Self::calculate_sell_price(input_reserve, output_reserve, sell_amount) + } + + pub fn calculate_buy_price_id( + sold_token_id: CurrencyIdOf, + bought_token_id: CurrencyIdOf, + buy_amount: BalanceOf, + ) -> Result, DispatchError> { + let (input_reserve, output_reserve) = + Pallet::::get_reserves(sold_token_id, bought_token_id)?; + + ensure!(!(Self::is_pool_empty(sold_token_id, bought_token_id)?), Error::::PoolIsEmpty); + + Self::calculate_buy_price(input_reserve, output_reserve, buy_amount) + } + + pub fn get_reserves( + first_asset_id: CurrencyIdOf, + second_asset_id: CurrencyIdOf, + ) -> Result<(BalanceOf, BalanceOf), DispatchError> { + let mut reserves = Pools::::get((first_asset_id, second_asset_id)); + + if Pools::::contains_key((first_asset_id, second_asset_id)) { + Ok((reserves.0, reserves.1)) + } else if Pools::::contains_key((second_asset_id, first_asset_id)) { + reserves = Pools::::get((second_asset_id, first_asset_id)); + Ok((reserves.1, reserves.0)) + } else { + Err(DispatchError::from(Error::::NoSuchPool)) + } + } + + /// worst case scenario + /// MAX: 2R 1W + pub fn set_reserves( + first_asset_id: CurrencyIdOf, + first_asset_amount: BalanceOf, + second_asset_id: CurrencyIdOf, + second_asset_amount: BalanceOf, + ) -> DispatchResult { + if Pools::::contains_key((first_asset_id, second_asset_id)) { + Pools::::insert( + (first_asset_id, second_asset_id), + (first_asset_amount, second_asset_amount), + ); + } else if Pools::::contains_key((second_asset_id, first_asset_id)) { + Pools::::insert( + (second_asset_id, first_asset_id), + (second_asset_amount, first_asset_amount), + ); + } else { + return Err(DispatchError::from(Error::::NoSuchPool)) + } + + Ok(()) + } + + pub fn settle_pool_fees( + who: &T::AccountId, + pool_id: CurrencyIdOf, + asset_id: CurrencyIdOf, + fee: BalanceOf, + ) -> Result<(), DispatchError> { + let pool = Self::get_pool_info(pool_id).ok_or(Error::::NoSuchPool)?; + let reserves = Self::get_reserves(pool.0, pool.1)?; + let new_reserves = if asset_id == pool.0 { + (reserves.0 + fee, reserves.1) + } else { + (reserves.0, reserves.1 + fee) + }; + ::Currency::transfer( + asset_id, + who, + &Self::account_id(), + fee, + ExistenceRequirement::AllowDeath, + )?; + Self::set_reserves(pool.0, new_reserves.0, pool.1, new_reserves.1)?; + Ok(()) + } + + // Calculate first and second token amounts depending on liquidity amount to burn + pub fn get_burn_amount( + first_asset_id: CurrencyIdOf, + second_asset_id: CurrencyIdOf, + liquidity_asset_amount: BalanceOf, + ) -> Result<(BalanceOf, BalanceOf), DispatchError> { + // Get token reserves and liquidity asset id + let liquidity_asset_id = Self::get_liquidity_asset(first_asset_id, second_asset_id)?; + let (first_asset_reserve, second_asset_reserve) = + Pallet::::get_reserves(first_asset_id, second_asset_id)?; + + ensure!(!(Self::is_pool_empty(first_asset_id, second_asset_id)?), Error::::PoolIsEmpty); + + let (first_asset_amount, second_asset_amount) = Self::get_burn_amount_reserves( + first_asset_reserve, + second_asset_reserve, + liquidity_asset_id, + liquidity_asset_amount, + )?; + + log!( + info, + "get_burn_amount: ({:?}, {:?}, {:?}) -> ({:?}, {:?})", + first_asset_id, + second_asset_id, + liquidity_asset_amount, + first_asset_amount, + second_asset_amount + ); + + Ok((first_asset_amount, second_asset_amount)) + } + + pub fn get_burn_amount_reserves( + first_asset_reserve: BalanceOf, + second_asset_reserve: BalanceOf, + liquidity_asset_id: CurrencyIdOf, + liquidity_asset_amount: BalanceOf, + ) -> Result<(BalanceOf, BalanceOf), DispatchError> { + // Get token reserves and liquidity asset id + + let total_liquidity_assets: BalanceOf = + ::Currency::total_issuance(liquidity_asset_id.into()); + + // Calculate first and second token amount to be withdrawn + ensure!(!total_liquidity_assets.is_zero(), Error::::DivisionByZero); + let first_asset_amount = multiply_by_rational_with_rounding( + first_asset_reserve.into(), + liquidity_asset_amount.into(), + total_liquidity_assets.into(), + Rounding::Down, + ) + .map(SaturatedConversion::saturated_into) + .ok_or(Error::::UnexpectedFailure)?; + let second_asset_amount = multiply_by_rational_with_rounding( + second_asset_reserve.into(), + liquidity_asset_amount.into(), + total_liquidity_assets.into(), + Rounding::Down, + ) + .map(SaturatedConversion::saturated_into) + .ok_or(Error::::UnexpectedFailure)?; + + Ok((first_asset_amount, second_asset_amount)) + } + + fn settle_treasury_and_burn( + sold_asset_id: CurrencyIdOf, + burn_amount: BalanceOf, + treasury_amount: BalanceOf, + ) -> DispatchResult { + let vault = Self::account_id(); + let native_token_id: CurrencyIdOf = Self::native_token_id(); + let treasury_account: T::AccountId = Self::treasury_account_id(); + let bnb_treasury_account: T::AccountId = Self::bnb_treasury_account_id(); + + // If settling token is mangata, treasury amount is added to treasury and burn amount is burned from corresponding pool + if sold_asset_id == native_token_id { + // treasury_amount of MGA is already in treasury at this point + + // MGA burned from bnb_treasury_account + // MAX: 3R 1W + ::Currency::burn_and_settle( + sold_asset_id.into(), + &bnb_treasury_account, + burn_amount, + )?; + } + //If settling token is connected to mangata, token is swapped in corresponding pool to mangata without fee + else if Pools::::contains_key((sold_asset_id, native_token_id)) || + Pools::::contains_key((native_token_id, sold_asset_id)) + { + // MAX: 2R (from if cond) + + // Getting token reserves + let (input_reserve, output_reserve) = + Pallet::::get_reserves(sold_asset_id, native_token_id)?; + + // Calculating swapped mangata amount + let settle_amount_in_mangata = Self::calculate_sell_price_no_fee( + input_reserve, + output_reserve, + treasury_amount + .checked_add(&burn_amount) + .ok_or_else(|| DispatchError::from(Error::::MathOverflow))?, + )?; + + let treasury_amount_in_mangata: BalanceOf = settle_amount_in_mangata + .into() + .checked_mul(T::TreasuryFeePercentage::get()) + .ok_or_else(|| DispatchError::from(Error::::MathOverflow))? + .checked_div( + T::TreasuryFeePercentage::get() + .checked_add(T::BuyAndBurnFeePercentage::get()) + .ok_or_else(|| DispatchError::from(Error::::MathOverflow))?, + ) + .ok_or_else(|| DispatchError::from(Error::::MathOverflow))? + .try_into() + .map_err(|_| DispatchError::from(Error::::MathOverflow))?; + + let burn_amount_in_mangata: BalanceOf = settle_amount_in_mangata + .into() + .checked_sub(treasury_amount_in_mangata.into()) + .ok_or_else(|| DispatchError::from(Error::::MathOverflow))? + .try_into() + .map_err(|_| DispatchError::from(Error::::MathOverflow))?; + + // Apply changes in token pools, adding treasury and burn amounts of settling token, removing treasury and burn amounts of mangata + + // MAX: 2R 1W + Pallet::::set_reserves( + sold_asset_id, + input_reserve.saturating_add(treasury_amount).saturating_add(burn_amount), + native_token_id, + output_reserve + .saturating_sub(treasury_amount_in_mangata) + .saturating_sub(burn_amount_in_mangata), + )?; + + ::Currency::transfer( + sold_asset_id.into(), + &treasury_account, + &vault, + treasury_amount, + ExistenceRequirement::KeepAlive, + )?; + + ::Currency::transfer( + native_token_id.into(), + &vault, + &treasury_account, + treasury_amount_in_mangata, + ExistenceRequirement::KeepAlive, + )?; + + ::Currency::transfer( + sold_asset_id.into(), + &bnb_treasury_account, + &vault, + burn_amount, + ExistenceRequirement::KeepAlive, + )?; + + // Mangata burned from pool + ::Currency::burn_and_settle( + native_token_id.into(), + &vault, + burn_amount_in_mangata, + )?; + } + // Settling token has no mangata connection, settling token is added to treasuries + else { + // Both treasury_amount and buy_and_burn_amount of sold_asset are in their respective treasuries + } + Ok(()) + } + + pub fn account_id() -> T::AccountId { + PALLET_ID.into_account_truncating() + } + + fn treasury_account_id() -> T::AccountId { + T::TreasuryPalletId::get().into_account_truncating() + } + + fn bnb_treasury_account_id() -> T::AccountId { + T::TreasuryPalletId::get().into_sub_account_truncating(T::BnbTreasurySubAccDerive::get()) + } + + fn native_token_id() -> CurrencyIdOf { + ::NativeCurrencyId::get() + } + + fn calculate_initial_liquidity( + first_asset_amount: BalanceOf, + second_asset_amount: BalanceOf, + ) -> Result, DispatchError> { + let initial_liquidity = first_asset_amount + .checked_div(&2_u32.into()) + .ok_or_else(|| DispatchError::from(Error::::MathOverflow))? + .checked_add( + &second_asset_amount + .checked_div(&2_u32.into()) + .ok_or_else(|| DispatchError::from(Error::::MathOverflow))?, + ) + .ok_or_else(|| DispatchError::from(Error::::MathOverflow))?; + + return Ok(if initial_liquidity == BalanceOf::::zero() { + BalanceOf::::one() + } else { + initial_liquidity + }) + } + + fn is_pool_empty( + first_asset_id: CurrencyIdOf, + second_asset_id: CurrencyIdOf, + ) -> Result { + let liquidity_asset_id = Pallet::::get_liquidity_asset(first_asset_id, second_asset_id)?; + let total_liquidity_assets: BalanceOf = + ::Currency::total_issuance(liquidity_asset_id.into()); + + return Ok(total_liquidity_assets.is_zero()) + } +} + +impl PreValidateSwaps, CurrencyIdOf> for Pallet { + fn pre_validate_sell_asset( + sender: &T::AccountId, + sold_asset_id: CurrencyIdOf, + bought_asset_id: CurrencyIdOf, + sold_asset_amount: BalanceOf, + _min_amount_out: BalanceOf, + ) -> Result< + (BalanceOf, BalanceOf, BalanceOf, BalanceOf, BalanceOf, BalanceOf), + DispatchError, + > { + ensure!( + !T::MaintenanceStatusProvider::is_maintenance(), + Error::::TradingBlockedByMaintenanceMode + ); + + // Ensure not selling zero amount + ensure!(!sold_asset_amount.is_zero(), Error::::ZeroAmount,); + + ensure!( + !T::DisabledTokens::contains(&sold_asset_id) && + !T::DisabledTokens::contains(&bought_asset_id), + Error::::FunctionNotAvailableForThisToken + ); + + ensure!(!(Self::is_pool_empty(sold_asset_id, bought_asset_id)?), Error::::PoolIsEmpty); + + let buy_and_burn_amount: BalanceOf = multiply_by_rational_with_rounding( + sold_asset_amount.into(), + T::BuyAndBurnFeePercentage::get(), + 10000, + Rounding::Down, + ) + .ok_or(Error::::UnexpectedFailure)? + .checked_add(1) + .ok_or(Error::::MathOverflow)? + .try_into() + .map_err(|_| Error::::MathOverflow)?; + + let treasury_amount: BalanceOf = multiply_by_rational_with_rounding( + sold_asset_amount.into(), + T::TreasuryFeePercentage::get(), + 10000, + Rounding::Down, + ) + .ok_or(Error::::UnexpectedFailure)? + .checked_add(1) + .ok_or(Error::::MathOverflow)? + .try_into() + .map_err(|_| Error::::MathOverflow)?; + + let pool_fee_amount: BalanceOf = multiply_by_rational_with_rounding( + sold_asset_amount.into(), + T::PoolFeePercentage::get(), + 10000, + Rounding::Down, + ) + .ok_or(Error::::UnexpectedFailure)? + .checked_add(1) + .ok_or(Error::::MathOverflow)? + .try_into() + .map_err(|_| Error::::MathOverflow)?; + + let total_fees: BalanceOf = buy_and_burn_amount + .checked_add(&treasury_amount) + .and_then(|v| v.checked_add(&pool_fee_amount)) + .ok_or(Error::::MathOverflow)?; + + // MAX: 2R + let (input_reserve, output_reserve) = + Pallet::::get_reserves(sold_asset_id, bought_asset_id)?; + + ensure!(input_reserve.checked_add(&sold_asset_amount).is_some(), Error::::MathOverflow); + + // Calculate bought asset amount to be received by paying sold asset amount + let bought_asset_amount = + Pallet::::calculate_sell_price(input_reserve, output_reserve, sold_asset_amount)?; + + // Ensure user has enough tokens to sell + ::Currency::ensure_can_withdraw( + sold_asset_id.into(), + sender, + total_fees, + WithdrawReasons::all(), + // Does not fail due to earlier ensure + Default::default(), + ) + .or(Err(Error::::NotEnoughAssets))?; + + Ok(( + buy_and_burn_amount, + treasury_amount, + pool_fee_amount, + input_reserve, + output_reserve, + bought_asset_amount, + )) + } + + /// We only validate the first atomic swap's ability to accept fees + fn pre_validate_multiswap_sell_asset( + sender: &T::AccountId, + swap_token_list: Vec>, + sold_asset_amount: BalanceOf, + _min_amount_out: BalanceOf, + ) -> Result< + ( + BalanceOf, + BalanceOf, + BalanceOf, + BalanceOf, + BalanceOf, + CurrencyIdOf, + CurrencyIdOf, + ), + DispatchError, + > { + ensure!( + !T::MaintenanceStatusProvider::is_maintenance(), + Error::::TradingBlockedByMaintenanceMode + ); + ensure!(swap_token_list.len() > 2_usize, Error::::MultiswapShouldBeAtleastTwoHops); + let sold_asset_id = + *swap_token_list.get(0).ok_or(Error::::MultiswapShouldBeAtleastTwoHops)?; + let bought_asset_id = + *swap_token_list.get(1).ok_or(Error::::MultiswapShouldBeAtleastTwoHops)?; + + // Ensure not selling zero amount + ensure!(!sold_asset_amount.is_zero(), Error::::ZeroAmount,); + + let atomic_pairs: Vec<(CurrencyIdOf, CurrencyIdOf)> = swap_token_list + .clone() + .into_iter() + .zip(swap_token_list.clone().into_iter().skip(1)) + .collect(); + + for (x, y) in atomic_pairs.iter() { + ensure!(!(Self::is_pool_empty(*x, *y)?), Error::::PoolIsEmpty); + + if x == y { + return Err(Error::::MultiSwapCantHaveSameTokenConsequetively.into()) + } + } + + ensure!( + !T::DisabledTokens::contains(&sold_asset_id) && + !T::DisabledTokens::contains(&bought_asset_id), + Error::::FunctionNotAvailableForThisToken + ); + + let buy_and_burn_amount: BalanceOf = multiply_by_rational_with_rounding( + sold_asset_amount.into(), + T::BuyAndBurnFeePercentage::get(), + 10000, + Rounding::Down, + ) + .ok_or(Error::::UnexpectedFailure)? + .checked_add(1) + .ok_or(Error::::MathOverflow)? + .try_into() + .map_err(|_| Error::::MathOverflow)?; + + let treasury_amount: BalanceOf = multiply_by_rational_with_rounding( + sold_asset_amount.into(), + T::TreasuryFeePercentage::get(), + 10000, + Rounding::Down, + ) + .ok_or(Error::::UnexpectedFailure)? + .checked_add(1) + .ok_or(Error::::MathOverflow)? + .try_into() + .map_err(|_| Error::::MathOverflow)?; + + let pool_fee_amount: BalanceOf = multiply_by_rational_with_rounding( + sold_asset_amount.into(), + T::PoolFeePercentage::get(), + 10000, + Rounding::Down, + ) + .ok_or(Error::::UnexpectedFailure)? + .checked_add(1) + .ok_or(Error::::MathOverflow)? + .try_into() + .map_err(|_| Error::::MathOverflow)?; + + let total_fees = buy_and_burn_amount + .checked_add(&treasury_amount) + .and_then(|v| v.checked_add(&pool_fee_amount)) + .ok_or(Error::::MathOverflow)?; + + // Get token reserves + + // MAX: 2R + let (input_reserve, output_reserve) = + Pallet::::get_reserves(sold_asset_id, bought_asset_id)?; + + ensure!(input_reserve.checked_add(&pool_fee_amount).is_some(), Error::::MathOverflow); + + // Ensure user has enough tokens to sell + ::Currency::ensure_can_withdraw( + sold_asset_id.into(), + sender, + total_fees, + WithdrawReasons::all(), + // Does not fail due to earlier ensure + Default::default(), + ) + .or(Err(Error::::NotEnoughAssets))?; + + Ok(( + buy_and_burn_amount, + treasury_amount, + pool_fee_amount, + input_reserve, + output_reserve, + sold_asset_id, + bought_asset_id, + )) + } + + fn pre_validate_buy_asset( + sender: &T::AccountId, + sold_asset_id: CurrencyIdOf, + bought_asset_id: CurrencyIdOf, + bought_asset_amount: BalanceOf, + _max_amount_in: BalanceOf, + ) -> Result< + (BalanceOf, BalanceOf, BalanceOf, BalanceOf, BalanceOf, BalanceOf), + DispatchError, + > { + ensure!( + !T::MaintenanceStatusProvider::is_maintenance(), + Error::::TradingBlockedByMaintenanceMode + ); + + ensure!( + !T::DisabledTokens::contains(&sold_asset_id) && + !T::DisabledTokens::contains(&bought_asset_id), + Error::::FunctionNotAvailableForThisToken + ); + + ensure!(!(Self::is_pool_empty(sold_asset_id, bought_asset_id)?), Error::::PoolIsEmpty); + + // Get token reserves + let (input_reserve, output_reserve) = + Pallet::::get_reserves(sold_asset_id, bought_asset_id)?; + + // Ensure there are enough tokens in reserves + ensure!(output_reserve > bought_asset_amount, Error::::NotEnoughReserve,); + + // Ensure not buying zero amount + ensure!(!bought_asset_amount.is_zero(), Error::::ZeroAmount,); + + // Calculate amount to be paid from bought amount + let sold_asset_amount = + Pallet::::calculate_buy_price(input_reserve, output_reserve, bought_asset_amount)?; + + let buy_and_burn_amount: BalanceOf = multiply_by_rational_with_rounding( + sold_asset_amount.into(), + T::BuyAndBurnFeePercentage::get(), + 10000, + Rounding::Down, + ) + .ok_or(Error::::UnexpectedFailure)? + .checked_add(1) + .ok_or(Error::::MathOverflow)? + .try_into() + .map_err(|_| Error::::MathOverflow)?; + + let treasury_amount: BalanceOf = multiply_by_rational_with_rounding( + sold_asset_amount.into(), + T::TreasuryFeePercentage::get(), + 10000, + Rounding::Down, + ) + .ok_or(Error::::UnexpectedFailure)? + .checked_add(1) + .ok_or(Error::::MathOverflow)? + .try_into() + .map_err(|_| Error::::MathOverflow)?; + + let pool_fee_amount: BalanceOf = multiply_by_rational_with_rounding( + sold_asset_amount.into(), + T::PoolFeePercentage::get(), + 10000, + Rounding::Down, + ) + .ok_or(Error::::UnexpectedFailure)? + .checked_add(1) + .ok_or(Error::::MathOverflow)? + .try_into() + .map_err(|_| Error::::MathOverflow)?; + + // for future implementation of min fee if necessary + // let min_fee: u128 = 0; + // if buy_and_burn_amount + treasury_amount + pool_fee_amount < min_fee { + // buy_and_burn_amount = min_fee * Self::total_fee() / T::BuyAndBurnFeePercentage::get(); + // treasury_amount = min_fee * Self::total_fee() / T::TreasuryFeePercentage::get(); + // pool_fee_amount = min_fee - buy_and_burn_amount - treasury_amount; + // } + + ensure!(input_reserve.checked_add(&sold_asset_amount).is_some(), Error::::MathOverflow); + + // Ensure user has enough tokens to sell + ::Currency::ensure_can_withdraw( + sold_asset_id.into(), + sender, + sold_asset_amount, + WithdrawReasons::all(), + // Does not fail due to earlier ensure + Default::default(), + ) + .or(Err(Error::::NotEnoughAssets))?; + + Ok(( + buy_and_burn_amount, + treasury_amount, + pool_fee_amount, + input_reserve, + output_reserve, + sold_asset_amount, + )) + } + + /// We only validate the first atomic swap's ability to accept fees + fn pre_validate_multiswap_buy_asset( + sender: &T::AccountId, + swap_token_list: Vec>, + final_bought_asset_amount: BalanceOf, + max_amount_in: BalanceOf, + ) -> Result< + ( + BalanceOf, + BalanceOf, + BalanceOf, + BalanceOf, + BalanceOf, + CurrencyIdOf, + CurrencyIdOf, + ), + DispatchError, + > { + ensure!( + !T::MaintenanceStatusProvider::is_maintenance(), + Error::::TradingBlockedByMaintenanceMode + ); + ensure!(swap_token_list.len() > 2_usize, Error::::MultiswapShouldBeAtleastTwoHops); + // Ensure not buying zero amount + ensure!(!final_bought_asset_amount.is_zero(), Error::::ZeroAmount,); + ensure!(!max_amount_in.is_zero(), Error::::ZeroAmount,); + + // Unwraps are fine due to above ensure + let sold_asset_id = + *swap_token_list.get(0).ok_or(Error::::MultiswapShouldBeAtleastTwoHops)?; + let bought_asset_id = + *swap_token_list.get(1).ok_or(Error::::MultiswapShouldBeAtleastTwoHops)?; + + ensure!( + !T::DisabledTokens::contains(&sold_asset_id) && + !T::DisabledTokens::contains(&bought_asset_id), + Error::::FunctionNotAvailableForThisToken + ); + + // Cannot use multiswap twice on the same pool + let atomic_pairs: Vec<(CurrencyIdOf, CurrencyIdOf)> = swap_token_list + .clone() + .into_iter() + .zip(swap_token_list.clone().into_iter().skip(1)) + .collect(); + + let mut atomic_pairs_hashset = BTreeSet::new(); + + for (x, y) in atomic_pairs.iter() { + ensure!(!(Self::is_pool_empty(*x, *y)?), Error::::PoolIsEmpty); + + if x == y { + return Err(Error::::MultiSwapCantHaveSameTokenConsequetively.into()) + } else if x > y { + if !atomic_pairs_hashset.insert((x, y)) { + return Err(Error::::MultiBuyAssetCantHaveSamePoolAtomicSwaps.into()) + }; + } else + // x < y + { + if !atomic_pairs_hashset.insert((y, x)) { + return Err(Error::::MultiBuyAssetCantHaveSamePoolAtomicSwaps.into()) + }; + } + } + + // Get token reserves + let (input_reserve, output_reserve) = + Pallet::::get_reserves(sold_asset_id, bought_asset_id)?; + + let buy_and_burn_amount: BalanceOf = multiply_by_rational_with_rounding( + max_amount_in.into(), + T::BuyAndBurnFeePercentage::get(), + 10000, + Rounding::Down, + ) + .ok_or(Error::::UnexpectedFailure)? + .checked_add(1) + .ok_or(Error::::MathOverflow)? + .try_into() + .map_err(|_| Error::::MathOverflow)?; + + let treasury_amount: BalanceOf = multiply_by_rational_with_rounding( + max_amount_in.into(), + T::TreasuryFeePercentage::get(), + 10000, + Rounding::Down, + ) + .ok_or(Error::::UnexpectedFailure)? + .checked_add(1) + .ok_or(Error::::MathOverflow)? + .try_into() + .map_err(|_| Error::::MathOverflow)?; + + let pool_fee_amount: BalanceOf = multiply_by_rational_with_rounding( + max_amount_in.into(), + T::PoolFeePercentage::get(), + 10000, + Rounding::Down, + ) + .ok_or(Error::::UnexpectedFailure)? + .checked_add(1) + .ok_or(Error::::MathOverflow)? + .try_into() + .map_err(|_| Error::::MathOverflow)?; + + let total_fees = buy_and_burn_amount + .checked_add(&treasury_amount) + .and_then(|v| v.checked_add(&pool_fee_amount)) + .ok_or(Error::::MathOverflow)?; + + ensure!(input_reserve.checked_add(&pool_fee_amount).is_some(), Error::::MathOverflow); + + // Ensure user has enough tokens to sell + ::Currency::ensure_can_withdraw( + sold_asset_id.into(), + sender, + total_fees, + WithdrawReasons::all(), + // Does not fail due to earlier ensure + Default::default(), + ) + .or(Err(Error::::NotEnoughAssets))?; + + Ok(( + buy_and_burn_amount, + treasury_amount, + pool_fee_amount, + input_reserve, + output_reserve, + sold_asset_id, + bought_asset_id, + )) + } +} + +impl XykFunctionsTrait, CurrencyIdOf> for Pallet { + fn create_pool( + sender: T::AccountId, + first_asset_id: CurrencyIdOf, + first_asset_amount: BalanceOf, + second_asset_id: CurrencyIdOf, + second_asset_amount: BalanceOf, + ) -> Result, DispatchError> { + let vault: T::AccountId = Pallet::::account_id(); + + // Ensure pool is not created with zero amount + ensure!( + !first_asset_amount.is_zero() && !second_asset_amount.is_zero(), + Error::::ZeroAmount, + ); + + // Ensure pool does not exists yet + ensure!( + !Pools::::contains_key((first_asset_id, second_asset_id)), + Error::::PoolAlreadyExists, + ); + + // Ensure pool does not exists yet + ensure!( + !Pools::::contains_key((second_asset_id, first_asset_id)), + Error::::PoolAlreadyExists, + ); + + // Ensure user has enough withdrawable tokens to create pool in amounts required + + ::Currency::ensure_can_withdraw( + first_asset_id.into(), + &sender, + first_asset_amount, + WithdrawReasons::all(), + // Does not fail due to earlier ensure + Default::default(), + ) + .or(Err(Error::::NotEnoughAssets))?; + + ::Currency::ensure_can_withdraw( + second_asset_id.into(), + &sender, + second_asset_amount, + WithdrawReasons::all(), + // Does not fail due to earlier ensure + Default::default(), + ) + .or(Err(Error::::NotEnoughAssets))?; + + // Ensure pool is not created with same token in pair + ensure!(first_asset_id != second_asset_id, Error::::SameAsset,); + + // Liquidity token amount calculation + let initial_liquidity = + Pallet::::calculate_initial_liquidity(first_asset_amount, second_asset_amount)?; + + Pools::::insert( + (first_asset_id, second_asset_id), + (first_asset_amount, second_asset_amount), + ); + + // Pools::insert((second_asset_id, first_asset_id), second_asset_amount); + + // Moving tokens from user to vault + ::Currency::transfer( + first_asset_id.into(), + &sender, + &vault, + first_asset_amount, + ExistenceRequirement::AllowDeath, + )?; + + ::Currency::transfer( + second_asset_id.into(), + &sender, + &vault, + second_asset_amount, + ExistenceRequirement::AllowDeath, + )?; + + // Creating new liquidity token and transfering it to user + let liquidity_asset_id: CurrencyIdOf = + ::Currency::create(&sender, initial_liquidity) + .map_err(|_| Error::::LiquidityTokenCreationFailed)? + .into(); + + // Adding info about liquidity asset + LiquidityAssets::::insert((first_asset_id, second_asset_id), Some(liquidity_asset_id)); + LiquidityPools::::insert(liquidity_asset_id, Some((first_asset_id, second_asset_id))); + + log!( + info, + "create_pool: ({:?}, {:?}, {:?}, {:?}, {:?}) -> ({:?}, {:?})", + sender, + first_asset_id, + first_asset_amount, + second_asset_id, + second_asset_amount, + liquidity_asset_id, + initial_liquidity + ); + + log!( + info, + "pool-state: [({:?}, {:?}) -> {:?}, ({:?}, {:?}) -> {:?}]", + first_asset_id, + second_asset_id, + first_asset_amount, + second_asset_id, + first_asset_id, + second_asset_amount + ); + // This, will and should, never fail + Pallet::::set_liquidity_asset_info(liquidity_asset_id, first_asset_id, second_asset_id)?; + + Pallet::::deposit_event(Event::PoolCreated( + sender, + first_asset_id, + first_asset_amount, + second_asset_id, + second_asset_amount, + )); + + Ok(liquidity_asset_id) + } + + // To put it comprehensively the only reason that the user should lose out on swap fee + // is if the mistake is theirs, which in the context of swaps is bad slippage besides pre_validation. + // In this implementation, once pre_validation passes the swap fee mechanism that follows should suceed. + // And if the function fails beyond pre_validation but not on slippage then the user is free from blame. + // Further internals calls, might determine the slippage themselves before calling this swap function, + // in which case again the user must not be charged the swap fee. + fn sell_asset( + sender: T::AccountId, + sold_asset_id: CurrencyIdOf, + bought_asset_id: CurrencyIdOf, + sold_asset_amount: BalanceOf, + min_amount_out: BalanceOf, + err_upon_bad_slippage: bool, + ) -> Result, DispatchError> { + let ( + buy_and_burn_amount, + treasury_amount, + pool_fee_amount, + input_reserve, + output_reserve, + bought_asset_amount, + ) = as PreValidateSwaps, CurrencyIdOf>>::pre_validate_sell_asset( + &sender, + sold_asset_id, + bought_asset_id, + sold_asset_amount, + min_amount_out, + )?; + + let vault = Pallet::::account_id(); + let treasury_account: T::AccountId = Self::treasury_account_id(); + let bnb_treasury_account: T::AccountId = Self::bnb_treasury_account_id(); + + // Transfer of fees, before tx can fail on min amount out + ::Currency::transfer( + sold_asset_id.into(), + &sender, + &vault, + pool_fee_amount, + ExistenceRequirement::KeepAlive, + )?; + + ::Currency::transfer( + sold_asset_id.into(), + &sender, + &treasury_account, + treasury_amount, + ExistenceRequirement::KeepAlive, + )?; + + ::Currency::transfer( + sold_asset_id.into(), + &sender, + &bnb_treasury_account, + buy_and_burn_amount, + ExistenceRequirement::KeepAlive, + )?; + + // Add pool fee to pool + // 2R 1W + Pallet::::set_reserves( + sold_asset_id, + input_reserve.saturating_add(pool_fee_amount), + bought_asset_id, + output_reserve, + )?; + + // Ensure bought token amount is higher then requested minimal amount + if bought_asset_amount >= min_amount_out { + // Transfer the rest of sold token amount from user to vault and bought token amount from vault to user + ::Currency::transfer( + sold_asset_id.into(), + &sender, + &vault, + sold_asset_amount + .checked_sub( + &buy_and_burn_amount + .checked_add(&treasury_amount) + .and_then(|v| v.checked_add(&pool_fee_amount)) + .ok_or_else(|| DispatchError::from(Error::::SoldAmountTooLow))?, + ) + .ok_or_else(|| DispatchError::from(Error::::SoldAmountTooLow))?, + ExistenceRequirement::KeepAlive, + )?; + + ::Currency::transfer( + bought_asset_id.into(), + &vault, + &sender, + bought_asset_amount, + ExistenceRequirement::KeepAlive, + )?; + + // Apply changes in token pools, adding sold amount and removing bought amount + // Neither should fall to zero let alone underflow, due to how pool destruction works + // Won't overflow due to earlier ensure + let input_reserve_updated = input_reserve.saturating_add( + sold_asset_amount + .checked_sub(&treasury_amount) + .and_then(|v| v.checked_sub(&buy_and_burn_amount)) + .ok_or_else(|| DispatchError::from(Error::::SoldAmountTooLow))?, + ); + let output_reserve_updated = output_reserve.saturating_sub(bought_asset_amount); + + // MAX 2R 1W + Pallet::::set_reserves( + sold_asset_id, + input_reserve_updated, + bought_asset_id, + output_reserve_updated, + )?; + + log!( + info, + "sell_asset: ({:?}, {:?}, {:?}, {:?}, {:?}) -> {:?}", + sender, + sold_asset_id, + bought_asset_id, + sold_asset_amount, + min_amount_out, + bought_asset_amount + ); + + log!( + info, + "pool-state: [({:?}, {:?}) -> {:?}, ({:?}, {:?}) -> {:?}]", + sold_asset_id, + bought_asset_id, + input_reserve_updated, + bought_asset_id, + sold_asset_id, + output_reserve_updated + ); + + Pallet::::deposit_event(Event::AssetsSwapped( + sender.clone(), + vec![sold_asset_id, bought_asset_id], + sold_asset_amount, + bought_asset_amount, + )); + } + + // Settle tokens which goes to treasury and for buy and burn purpose + Pallet::::settle_treasury_and_burn(sold_asset_id, buy_and_burn_amount, treasury_amount)?; + + if bought_asset_amount < min_amount_out { + if err_upon_bad_slippage { + return Err(DispatchError::from(Error::::InsufficientOutputAmount)) + } else { + Pallet::::deposit_event(Event::SellAssetFailedDueToSlippage( + sender, + sold_asset_id, + sold_asset_amount, + bought_asset_id, + bought_asset_amount, + min_amount_out, + )); + return Ok(Default::default()) + } + } + + Ok(bought_asset_amount) + } + + fn do_multiswap_sell_asset( + sender: T::AccountId, + swap_token_list: Vec>, + sold_asset_amount: BalanceOf, + min_amount_out: BalanceOf, + ) -> Result, DispatchError> { + frame_support::storage::with_storage_layer(|| -> Result, DispatchError> { + // Ensure user has enough tokens to sell + ::Currency::ensure_can_withdraw( + // Naked unwrap is fine due to pre validation len check + { *swap_token_list.get(0).ok_or(Error::::MultiswapShouldBeAtleastTwoHops)? } + .into(), + &sender, + sold_asset_amount, + WithdrawReasons::all(), + Default::default(), + ) + .or(Err(Error::::NotEnoughAssets))?; + + // pre_validate has already confirmed that swap_token_list.len()>1 + let atomic_pairs: Vec<(CurrencyIdOf, CurrencyIdOf)> = swap_token_list + .clone() + .into_iter() + .zip(swap_token_list.clone().into_iter().skip(1)) + .collect(); + + let mut atomic_sold_asset_amount = sold_asset_amount; + let mut atomic_bought_asset_amount = BalanceOf::::zero(); + + for (atomic_sold_asset, atomic_bought_asset) in atomic_pairs.iter() { + atomic_bought_asset_amount = , + CurrencyIdOf, + >>::sell_asset( + sender.clone(), + *atomic_sold_asset, + *atomic_bought_asset, + atomic_sold_asset_amount, + BalanceOf::::zero(), + // We using most possible slippage so this should be irrelevant + true, + )?; + + // Prep the next loop + atomic_sold_asset_amount = atomic_bought_asset_amount; + } + + // fail/error and revert if bad final slippage + if atomic_bought_asset_amount < min_amount_out { + return Err(Error::::InsufficientOutputAmount.into()) + } else { + return Ok(atomic_bought_asset_amount) + } + }) + } + + fn multiswap_sell_asset( + sender: T::AccountId, + swap_token_list: Vec>, + sold_asset_amount: BalanceOf, + min_amount_out: BalanceOf, + _err_upon_bad_slippage: bool, + _err_upon_non_slippage_fail: bool, + ) -> Result, DispatchError> { + let ( + fee_swap_buy_and_burn_amount, + fee_swap_treasury_amount, + fee_swap_pool_fee_amount, + fee_swap_input_reserve, + fee_swap_output_reserve, + fee_swap_sold_asset_id, + fee_swap_bought_asset_id, + ) = as PreValidateSwaps, CurrencyIdOf>>::pre_validate_multiswap_sell_asset( + &sender, + swap_token_list.clone(), + sold_asset_amount, + min_amount_out, + )?; + + // First execute all atomic swaps in a storage layer + // And then the finally bought amount is compared + // The bool in error represents if the fail is due to bad final slippage + match , CurrencyIdOf>>::do_multiswap_sell_asset( + sender.clone(), + swap_token_list.clone(), + sold_asset_amount, + min_amount_out, + ) { + Ok(bought_asset_amount) => { + Pallet::::deposit_event(Event::AssetsSwapped( + sender.clone(), + swap_token_list.clone(), + sold_asset_amount, + bought_asset_amount, + )); + TotalNumberOfSwaps::::mutate(|v|{*v=v.saturating_add(One::one())}); + Ok(bought_asset_amount) + }, + Err(e) => { + // Charge fee + + let vault = Pallet::::account_id(); + let treasury_account: T::AccountId = Self::treasury_account_id(); + let bnb_treasury_account: T::AccountId = Self::bnb_treasury_account_id(); + + // Transfer of fees, before tx can fail on min amount out + ::Currency::transfer( + fee_swap_sold_asset_id, + &sender, + &vault, + fee_swap_pool_fee_amount, + ExistenceRequirement::KeepAlive, + )?; + + ::Currency::transfer( + fee_swap_sold_asset_id, + &sender, + &treasury_account, + fee_swap_treasury_amount, + ExistenceRequirement::KeepAlive, + )?; + + ::Currency::transfer( + fee_swap_sold_asset_id, + &sender, + &bnb_treasury_account, + fee_swap_buy_and_burn_amount, + ExistenceRequirement::KeepAlive, + )?; + + // Add pool fee to pool + // 2R 1W + Pallet::::set_reserves( + fee_swap_sold_asset_id, + fee_swap_input_reserve.saturating_add(fee_swap_pool_fee_amount), + fee_swap_bought_asset_id, + fee_swap_output_reserve, + )?; + + // Settle tokens which goes to treasury and for buy and burn purpose + Pallet::::settle_treasury_and_burn( + fee_swap_sold_asset_id, + fee_swap_buy_and_burn_amount, + fee_swap_treasury_amount, + )?; + + if let DispatchError::Module(module_err) = e { + Pallet::::deposit_event(Event::MultiSwapAssetFailedOnAtomicSwap( + sender.clone(), + swap_token_list.clone(), + sold_asset_amount, + module_err, + )); + Ok(Default::default()) + } else { + Err(DispatchError::from(Error::::UnexpectedFailure)) + } + }, + } + } + + // To put it comprehensively the only reason that the user should lose out on swap fee + // is if the mistake is theirs, which in the context of swaps is bad slippage besides pre_validation. + // In this implementation, once pre_validation passes the swap fee mechanism that follows should suceed. + // And if the function fails beyond pre_validation but not on slippage then the user is free from blame. + // Further internals calls, might determine the slippage themselves before calling this swap function, + // in which case again the user must not be charged the swap fee. + fn buy_asset( + sender: T::AccountId, + sold_asset_id: CurrencyIdOf, + bought_asset_id: CurrencyIdOf, + bought_asset_amount: BalanceOf, + max_amount_in: BalanceOf, + err_upon_bad_slippage: bool, + ) -> Result, DispatchError> { + let ( + buy_and_burn_amount, + treasury_amount, + pool_fee_amount, + input_reserve, + output_reserve, + sold_asset_amount, + ) = as PreValidateSwaps, CurrencyIdOf>>::pre_validate_buy_asset( + &sender, + sold_asset_id, + bought_asset_id, + bought_asset_amount, + max_amount_in, + )?; + + let vault = Pallet::::account_id(); + let treasury_account: T::AccountId = Self::treasury_account_id(); + let bnb_treasury_account: T::AccountId = Self::bnb_treasury_account_id(); + + // Transfer of fees, before tx can fail on min amount out + ::Currency::transfer( + sold_asset_id, + &sender, + &vault, + pool_fee_amount, + ExistenceRequirement::KeepAlive, + )?; + + ::Currency::transfer( + sold_asset_id, + &sender, + &treasury_account, + treasury_amount, + ExistenceRequirement::KeepAlive, + )?; + + ::Currency::transfer( + sold_asset_id, + &sender, + &bnb_treasury_account, + buy_and_burn_amount, + ExistenceRequirement::KeepAlive, + )?; + + // Add pool fee to pool + Pallet::::set_reserves( + sold_asset_id, + input_reserve.saturating_add(pool_fee_amount), + bought_asset_id, + output_reserve, + )?; + + // Ensure paid amount is less then maximum allowed price + if sold_asset_amount <= max_amount_in { + // Transfer sold token amount from user to vault and bought token amount from vault to user + ::Currency::transfer( + sold_asset_id.into(), + &sender, + &vault, + sold_asset_amount + .checked_sub( + &buy_and_burn_amount + .checked_add(&treasury_amount) + .and_then(|v| v.checked_add(&pool_fee_amount)) + .ok_or_else(|| DispatchError::from(Error::::SoldAmountTooLow))?, + ) + .ok_or_else(|| DispatchError::from(Error::::SoldAmountTooLow))?, + ExistenceRequirement::KeepAlive, + )?; + ::Currency::transfer( + bought_asset_id, + &vault, + &sender, + bought_asset_amount, + ExistenceRequirement::KeepAlive, + )?; + + // Apply changes in token pools, adding sold amount and removing bought amount + // Neither should fall to zero let alone underflow, due to how pool destruction works + // Won't overflow due to earlier ensure + let input_reserve_updated = input_reserve.saturating_add( + sold_asset_amount + .checked_sub(&treasury_amount) + .and_then(|v| v.checked_sub(&buy_and_burn_amount)) + .ok_or_else(|| DispatchError::from(Error::::MathOverflow))?, + ); + + let output_reserve_updated = output_reserve.saturating_sub(bought_asset_amount); + Pallet::::set_reserves( + sold_asset_id, + input_reserve_updated, + bought_asset_id, + output_reserve_updated, + )?; + + log!( + info, + "buy_asset: ({:?}, {:?}, {:?}, {:?}, {:?}) -> {:?}", + sender, + sold_asset_id, + bought_asset_id, + bought_asset_amount, + max_amount_in, + sold_asset_amount + ); + + log!( + info, + "pool-state: [({:?}, {:?}) -> {:?}, ({:?}, {:?}) -> {:?}]", + sold_asset_id, + bought_asset_id, + input_reserve_updated, + bought_asset_id, + sold_asset_id, + output_reserve_updated + ); + + Pallet::::deposit_event(Event::AssetsSwapped( + sender.clone(), + vec![sold_asset_id, bought_asset_id], + sold_asset_amount, + bought_asset_amount, + )); + } + // Settle tokens which goes to treasury and for buy and burn purpose + Pallet::::settle_treasury_and_burn(sold_asset_id, buy_and_burn_amount, treasury_amount)?; + + if sold_asset_amount > max_amount_in { + if err_upon_bad_slippage { + return Err(DispatchError::from(Error::::InsufficientInputAmount)) + } else { + Pallet::::deposit_event(Event::BuyAssetFailedDueToSlippage( + sender, + sold_asset_id, + sold_asset_amount, + bought_asset_id, + bought_asset_amount, + max_amount_in, + )); + } + } + + Ok(sold_asset_amount) + } + + fn do_multiswap_buy_asset( + sender: T::AccountId, + swap_token_list: Vec>, + bought_asset_amount: BalanceOf, + max_amount_in: BalanceOf, + ) -> Result, DispatchError> { + frame_support::storage::with_storage_layer(|| -> Result, DispatchError> { + // pre_validate has already confirmed that swap_token_list.len()>1 + let atomic_pairs: Vec<(CurrencyIdOf, CurrencyIdOf)> = swap_token_list + .clone() + .into_iter() + .zip(swap_token_list.clone().into_iter().skip(1)) + .collect(); + + let mut atomic_sold_asset_amount = BalanceOf::::zero(); + let mut atomic_bought_asset_amount = bought_asset_amount; + + let mut atomic_swap_buy_amounts_rev: Vec> = Default::default(); + // Calc + // We can do this using calculate_buy_price_id chain due to the check in pre_validation + // that ensures that no pool is touched twice. So the reserves in question are consistent + for (atomic_sold_asset, atomic_bought_asset) in atomic_pairs.iter().rev() { + atomic_sold_asset_amount = Self::calculate_buy_price_id( + *atomic_sold_asset, + *atomic_bought_asset, + atomic_bought_asset_amount, + )?; + + atomic_swap_buy_amounts_rev.push(atomic_bought_asset_amount); + // Prep the next loop + atomic_bought_asset_amount = atomic_sold_asset_amount; + } + + ensure!(atomic_sold_asset_amount <= max_amount_in, Error::::InsufficientInputAmount); + + // Ensure user has enough tokens to sell + ::Currency::ensure_can_withdraw( + // Naked unwrap is fine due to pre validation len check + { *swap_token_list.get(0).ok_or(Error::::MultiswapShouldBeAtleastTwoHops)? } + .into(), + &sender, + atomic_sold_asset_amount, + WithdrawReasons::all(), + Default::default(), + ) + .or(Err(Error::::NotEnoughAssets))?; + + // Execute here + for ((atomic_sold_asset, atomic_bought_asset), atomic_swap_buy_amount) in + atomic_pairs.iter().zip(atomic_swap_buy_amounts_rev.iter().rev()) + { + let _ = , CurrencyIdOf>>::buy_asset( + sender.clone(), + *atomic_sold_asset, + *atomic_bought_asset, + *atomic_swap_buy_amount, + BalanceOf::::max_value(), + // We using most possible slippage so this should be irrelevant + true, + )?; + } + + return Ok(atomic_sold_asset_amount) + }) + } + + fn multiswap_buy_asset( + sender: T::AccountId, + swap_token_list: Vec>, + bought_asset_amount: BalanceOf, + max_amount_in: BalanceOf, + _err_upon_bad_slippage: bool, + _err_upon_non_slippage_fail: bool, + ) -> Result, DispatchError> { + let ( + fee_swap_buy_and_burn_amount, + fee_swap_treasury_amount, + fee_swap_pool_fee_amount, + fee_swap_input_reserve, + fee_swap_output_reserve, + fee_swap_sold_asset_id, + fee_swap_bought_asset_id, + ) = as PreValidateSwaps, CurrencyIdOf>>::pre_validate_multiswap_buy_asset( + &sender, + swap_token_list.clone(), + bought_asset_amount, + max_amount_in, + )?; + + // First execute all atomic swaps in a storage layer + // And then the finally sold amount is compared + // The bool in error represents if the fail is due to bad final slippage + match , CurrencyIdOf>>::do_multiswap_buy_asset( + sender.clone(), + swap_token_list.clone(), + bought_asset_amount, + max_amount_in, + ) { + Ok(sold_asset_amount) => { + Pallet::::deposit_event(Event::AssetsSwapped( + sender.clone(), + swap_token_list.clone(), + sold_asset_amount, + bought_asset_amount, + )); + TotalNumberOfSwaps::::mutate(|v|{*v=v.saturating_add(One::one())}); + Ok(sold_asset_amount) + }, + Err(e) => { + let vault = Pallet::::account_id(); + let treasury_account: T::AccountId = Self::treasury_account_id(); + let bnb_treasury_account: T::AccountId = Self::bnb_treasury_account_id(); + + // Transfer of fees, before tx can fail on min amount out + ::Currency::transfer( + fee_swap_sold_asset_id, + &sender, + &vault, + fee_swap_pool_fee_amount, + ExistenceRequirement::KeepAlive, + )?; + + ::Currency::transfer( + fee_swap_sold_asset_id, + &sender, + &treasury_account, + fee_swap_treasury_amount, + ExistenceRequirement::KeepAlive, + )?; + + ::Currency::transfer( + fee_swap_sold_asset_id, + &sender, + &bnb_treasury_account, + fee_swap_buy_and_burn_amount, + ExistenceRequirement::KeepAlive, + )?; + + // Add pool fee to pool + // 2R 1W + Pallet::::set_reserves( + fee_swap_sold_asset_id, + fee_swap_input_reserve.saturating_add(fee_swap_pool_fee_amount), + fee_swap_bought_asset_id, + fee_swap_output_reserve, + )?; + + // Settle tokens which goes to treasury and for buy and burn purpose + Pallet::::settle_treasury_and_burn( + fee_swap_sold_asset_id, + fee_swap_buy_and_burn_amount, + fee_swap_treasury_amount, + )?; + + if let DispatchError::Module(module_err) = e { + Pallet::::deposit_event(Event::MultiSwapAssetFailedOnAtomicSwap( + sender.clone(), + swap_token_list.clone(), + bought_asset_amount, + module_err, + )); + Ok(Default::default()) + } else { + Err(DispatchError::from(Error::::UnexpectedFailure)) + } + }, + } + } + + fn mint_liquidity( + sender: T::AccountId, + first_asset_id: CurrencyIdOf, + second_asset_id: CurrencyIdOf, + first_asset_amount: BalanceOf, + expected_second_asset_amount: BalanceOf, + activate_minted_liquidity: bool, + ) -> Result<(CurrencyIdOf, BalanceOf, BalanceOf), DispatchError> { + let vault = Pallet::::account_id(); + + // Ensure pool exists + ensure!( + (LiquidityAssets::::contains_key((first_asset_id, second_asset_id)) || + LiquidityAssets::::contains_key((second_asset_id, first_asset_id))), + Error::::NoSuchPool, + ); + + // Get liquidity token id + let liquidity_asset_id = Pallet::::get_liquidity_asset(first_asset_id, second_asset_id)?; + + // Get token reserves + let (first_asset_reserve, second_asset_reserve) = + Pallet::::get_reserves(first_asset_id, second_asset_id)?; + let total_liquidity_assets: BalanceOf = + ::Currency::total_issuance(liquidity_asset_id.into()); + + // The pool is empty and we are basically creating a new pool and reusing the existing one + let second_asset_amount = if !(first_asset_reserve.is_zero() && + second_asset_reserve.is_zero()) && + !total_liquidity_assets.is_zero() + { + // Calculation of required second asset amount and received liquidity token amount + ensure!(!first_asset_reserve.is_zero(), Error::::DivisionByZero); + + multiply_by_rational_with_rounding( + first_asset_amount.into(), + second_asset_reserve.into(), + first_asset_reserve.into(), + Rounding::Down, + ) + .ok_or(Error::::UnexpectedFailure)? + .checked_add(1) + .ok_or_else(|| DispatchError::from(Error::::MathOverflow))? + .try_into() + .map_err(|_| DispatchError::from(Error::::MathOverflow))? + } else { + expected_second_asset_amount + }; + + ensure!( + second_asset_amount <= expected_second_asset_amount, + Error::::SecondAssetAmountExceededExpectations, + ); + + // Ensure minting amounts are not zero + ensure!( + !first_asset_amount.is_zero() && !second_asset_amount.is_zero(), + Error::::ZeroAmount, + ); + + // We calculate the required liquidity token amount and also validate asset amounts + let liquidity_assets_minted = if total_liquidity_assets.is_zero() { + Pallet::::calculate_initial_liquidity(first_asset_amount, second_asset_amount)? + } else { + multiply_by_rational_with_rounding( + first_asset_amount.into(), + total_liquidity_assets.into(), + first_asset_reserve.into(), + Rounding::Down, + ) + .ok_or(Error::::UnexpectedFailure)? + .try_into() + .map_err(|_| DispatchError::from(Error::::MathOverflow))? + }; + + // Ensure user has enough withdrawable tokens to create pool in amounts required + + ::Currency::ensure_can_withdraw( + first_asset_id.into(), + &sender, + first_asset_amount, + WithdrawReasons::all(), + // Does not fail due to earlier ensure + Default::default(), + ) + .or(Err(Error::::NotEnoughAssets))?; + + ::Currency::ensure_can_withdraw( + second_asset_id, + &sender, + second_asset_amount, + WithdrawReasons::all(), + // Does not fail due to earlier ensure + Default::default(), + ) + .or(Err(Error::::NotEnoughAssets))?; + + // Transfer of token amounts from user to vault + ::Currency::transfer( + first_asset_id, + &sender, + &vault, + first_asset_amount, + ExistenceRequirement::KeepAlive, + )?; + ::Currency::transfer( + second_asset_id, + &sender, + &vault, + second_asset_amount, + ExistenceRequirement::KeepAlive, + )?; + + // Creating new liquidity tokens to user + ::Currency::mint(liquidity_asset_id, &sender, liquidity_assets_minted)?; + + if , + CurrencyIdOf, + >>::is_enabled(liquidity_asset_id) && + activate_minted_liquidity + { + // The reserve from free_balance will not fail the asset were just minted into free_balance + , + CurrencyIdOf, + >>::activate_liquidity( + sender.clone(), + liquidity_asset_id, + liquidity_assets_minted, + Some(ActivateKind::AvailableBalance), + )?; + } + + // Apply changes in token pools, adding minted amounts + // Won't overflow due earlier ensure + let first_asset_reserve_updated = first_asset_reserve.saturating_add(first_asset_amount); + let second_asset_reserve_updated = second_asset_reserve.saturating_add(second_asset_amount); + Pallet::::set_reserves( + first_asset_id, + first_asset_reserve_updated, + second_asset_id, + second_asset_reserve_updated, + )?; + + log!( + info, + "mint_liquidity: ({:?}, {:?}, {:?}, {:?}) -> ({:?}, {:?}, {:?})", + sender, + first_asset_id, + second_asset_id, + first_asset_amount, + second_asset_amount, + liquidity_asset_id, + liquidity_assets_minted + ); + + log!( + info, + "pool-state: [({:?}, {:?}) -> {:?}, ({:?}, {:?}) -> {:?}]", + first_asset_id, + second_asset_id, + first_asset_reserve_updated, + second_asset_id, + first_asset_id, + second_asset_reserve_updated + ); + + Pallet::::deposit_event(Event::LiquidityMinted( + sender, + first_asset_id, + first_asset_amount, + second_asset_id, + second_asset_amount, + liquidity_asset_id, + liquidity_assets_minted, + )); + + Ok((liquidity_asset_id, liquidity_assets_minted, second_asset_amount)) + } + + fn do_compound_rewards( + sender: T::AccountId, + liquidity_asset_id: CurrencyIdOf, + amount_permille: Permill, + ) -> DispatchResult { + let (first_asset_id, second_asset_id) = + LiquidityPools::::get(liquidity_asset_id).ok_or(Error::::NoSuchLiquidityAsset)?; + + ensure!( + !T::DisabledTokens::contains(&first_asset_id) && + !T::DisabledTokens::contains(&second_asset_id), + Error::::FunctionNotAvailableForThisToken + ); + + let rewards_id: CurrencyIdOf = Self::native_token_id(); + ensure!( + first_asset_id == rewards_id || second_asset_id == rewards_id, + Error::::FunctionNotAvailableForThisToken + ); + + let rewards_claimed = , + CurrencyIdOf, + >>::claim_rewards_all(sender.clone(), liquidity_asset_id)?; + + let rewards_256 = U256::from(rewards_claimed.into()) + .saturating_mul(amount_permille.deconstruct().into()) + .div(Permill::one().deconstruct()); + let rewards_128 = u128::try_from(rewards_256) + .map_err(|_| DispatchError::from(Error::::MathOverflow))?; + let rewards = BalanceOf::::try_from(rewards_128) + .map_err(|_| DispatchError::from(Error::::MathOverflow))?; + + , CurrencyIdOf>>::provide_liquidity_with_conversion( + sender, + first_asset_id, + second_asset_id, + rewards_id, + rewards, + true, + )?; + + Ok(()) + } + + fn provide_liquidity_with_conversion( + sender: T::AccountId, + first_asset_id: CurrencyIdOf, + second_asset_id: CurrencyIdOf, + provided_asset_id: CurrencyIdOf, + provided_asset_amount: BalanceOf, + activate_minted_liquidity: bool, + ) -> Result<(CurrencyIdOf, BalanceOf), DispatchError> { + // checks + ensure!(!provided_asset_amount.is_zero(), Error::::ZeroAmount,); + + ensure!(!(Self::is_pool_empty(first_asset_id, second_asset_id)?), Error::::PoolIsEmpty); + + let (first_reserve, second_reserve) = + Pallet::::get_reserves(first_asset_id, second_asset_id)?; + + let (reserve, other_reserve, other_asset_id) = if provided_asset_id == first_asset_id { + (first_reserve, second_reserve, second_asset_id) + } else if provided_asset_id == second_asset_id { + (second_reserve, first_reserve, first_asset_id) + } else { + return Err(DispatchError::from(Error::::FunctionNotAvailableForThisToken)) + }; + + // Ensure user has enough tokens to sell + ::Currency::ensure_can_withdraw( + provided_asset_id, + &sender, + provided_asset_amount, + WithdrawReasons::all(), + // Does not fail due to earlier ensure + Default::default(), + ) + .or(Err(Error::::NotEnoughAssets))?; + + // calculate sell + let swap_amount = + Pallet::::calculate_balanced_sell_amount(provided_asset_amount, reserve)?; + + let bought_amount = Pallet::::calculate_sell_price(reserve, other_reserve, swap_amount)?; + + let _ = + , CurrencyIdOf>>::sell_asset( + sender.clone(), + provided_asset_id, + other_asset_id, + swap_amount, + bought_amount, + true, + )?; + + let mint_amount = provided_asset_amount + .checked_sub(&swap_amount) + .ok_or_else(|| DispatchError::from(Error::::MathOverflow))?; + + log!( + info, + "provide_liquidity_with_conversion: ({:?}, {:?}, {:?}, {:?}, {:?}) -> ({:?}, {:?})", + sender, + first_asset_id, + second_asset_id, + provided_asset_id, + provided_asset_amount, + mint_amount, + bought_amount + ); + + // we swap the order of the pairs to handle rounding + // we spend all of the Y + // and have some surplus amount of X that equals to the rounded part of Y + let (lp, lp_amount, _) = , + CurrencyIdOf, + >>::mint_liquidity( + sender, + other_asset_id, + provided_asset_id, + bought_amount, + BalanceOf::::max_value(), + activate_minted_liquidity, + )?; + Ok((lp, lp_amount)) + } + + fn burn_liquidity( + sender: T::AccountId, + first_asset_id: CurrencyIdOf, + second_asset_id: CurrencyIdOf, + liquidity_asset_amount: BalanceOf, + ) -> Result<(BalanceOf, BalanceOf), DispatchError> { + let vault = Pallet::::account_id(); + + ensure!( + !T::DisabledTokens::contains(&first_asset_id) && + !T::DisabledTokens::contains(&second_asset_id), + Error::::FunctionNotAvailableForThisToken + ); + + let liquidity_asset_id = Pallet::::get_liquidity_asset(first_asset_id, second_asset_id)?; + + // First let's check how much we can actually burn + let max_instant_unreserve_amount = + ::ActivationReservesProvider::get_max_instant_unreserve_amount( + liquidity_asset_id, + &sender, + ); + + // Get token reserves and liquidity asset id + let (first_asset_reserve, second_asset_reserve) = + Pallet::::get_reserves(first_asset_id, second_asset_id)?; + + // Ensure user has enought liquidity tokens to burn + let liquidity_token_available_balance = + ::Currency::available_balance(liquidity_asset_id.into(), &sender); + + ensure!( + liquidity_token_available_balance + .checked_add(&max_instant_unreserve_amount) + .ok_or(Error::::MathOverflow)? >= + liquidity_asset_amount, + Error::::NotEnoughAssets, + ); + + // Given the above ensure passes we only need to know how much to deactivate before burning + // Because once deactivated we will be burning the entire liquidity_asset_amount from available balance + // to_be_deactivated will ofcourse then also be greater than max_instant_unreserve_amount + // If pool is not promoted max_instant_unreserve_amount is 0, so liquidity_token_available_balance >= liquidity_asset_amount + // which would mean to_be_deactivated is 0, skipping deactivation + let to_be_deactivated = + liquidity_asset_amount.saturating_sub(liquidity_token_available_balance); + + // deactivate liquidity + , + CurrencyIdOf, + >>::deactivate_liquidity(sender.clone(), liquidity_asset_id, to_be_deactivated)?; + + // Calculate first and second token amounts depending on liquidity amount to burn + let (first_asset_amount, second_asset_amount) = Pallet::::get_burn_amount_reserves( + first_asset_reserve, + second_asset_reserve, + liquidity_asset_id, + liquidity_asset_amount, + )?; + + let total_liquidity_assets: BalanceOf = + ::Currency::total_issuance(liquidity_asset_id.into()); + + // If all liquidity assets are being burned then + // both asset amounts must be equal to their reserve values + // All storage values related to this pool must be destroyed + if liquidity_asset_amount == total_liquidity_assets { + ensure!( + (first_asset_reserve == first_asset_amount) && + (second_asset_reserve == second_asset_amount), + Error::::UnexpectedFailure + ); + } else { + ensure!( + (first_asset_reserve >= first_asset_amount) && + (second_asset_reserve >= second_asset_amount), + Error::::UnexpectedFailure + ); + } + // If all liquidity assets are not being burned then + // both asset amounts must be less than their reserve values + + // Ensure not withdrawing zero amounts + ensure!( + !first_asset_amount.is_zero() && !second_asset_amount.is_zero(), + Error::::ZeroAmount, + ); + + // Transfer withdrawn amounts from vault to user + ::Currency::transfer( + first_asset_id, + &vault, + &sender, + first_asset_amount, + ExistenceRequirement::KeepAlive, + )?; + ::Currency::transfer( + second_asset_id, + &vault, + &sender, + second_asset_amount, + ExistenceRequirement::KeepAlive, + )?; + + log!( + info, + "burn_liquidity: ({:?}, {:?}, {:?}, {:?}) -> ({:?}, {:?})", + sender, + first_asset_id, + second_asset_id, + liquidity_asset_amount, + first_asset_amount, + second_asset_amount + ); + + // Is liquidity asset amount empty? + if liquidity_asset_amount == total_liquidity_assets { + log!( + info, + "pool-state: [({:?}, {:?}) -> Removed, ({:?}, {:?}) -> Removed]", + first_asset_id, + second_asset_id, + second_asset_id, + first_asset_id, + ); + Pallet::::set_reserves( + first_asset_id, + BalanceOf::::zero(), + second_asset_id, + BalanceOf::::zero(), + )?; + } else { + // Apply changes in token pools, removing withdrawn amounts + // Cannot underflow due to earlier ensure + // check was executed in get_reserves call + let first_asset_reserve_updated = + first_asset_reserve.saturating_sub(first_asset_amount); + let second_asset_reserve_updated = + second_asset_reserve.saturating_sub(second_asset_amount); + Pallet::::set_reserves( + first_asset_id, + first_asset_reserve_updated, + second_asset_id, + second_asset_reserve_updated, + )?; + + log!( + info, + "pool-state: [({:?}, {:?}) -> {:?}, ({:?}, {:?}) -> {:?}]", + first_asset_id, + second_asset_id, + first_asset_reserve_updated, + second_asset_id, + first_asset_id, + second_asset_reserve_updated + ); + } + + // Destroying burnt liquidity tokens + // MAX: 3R 1W + ::Currency::burn_and_settle( + liquidity_asset_id.into(), + &sender, + liquidity_asset_amount, + )?; + + Pallet::::deposit_event(Event::LiquidityBurned( + sender, + first_asset_id, + first_asset_amount, + second_asset_id, + second_asset_amount, + liquidity_asset_id, + liquidity_asset_amount, + )); + + Ok((first_asset_amount, second_asset_amount)) + } + + // This function has not been verified + fn get_tokens_required_for_minting( + liquidity_asset_id: CurrencyIdOf, + liquidity_token_amount: BalanceOf, + ) -> Result<(CurrencyIdOf, BalanceOf, CurrencyIdOf, BalanceOf), DispatchError> { + let (first_asset_id, second_asset_id) = + LiquidityPools::::get(liquidity_asset_id).ok_or(Error::::NoSuchLiquidityAsset)?; + let (first_asset_reserve, second_asset_reserve) = + Pallet::::get_reserves(first_asset_id, second_asset_id)?; + let total_liquidity_assets: BalanceOf = + ::Currency::total_issuance(liquidity_asset_id.into()); + + ensure!(!total_liquidity_assets.is_zero(), Error::::DivisionByZero); + let second_asset_amount: BalanceOf = multiply_by_rational_with_rounding( + liquidity_token_amount.into(), + second_asset_reserve.into(), + total_liquidity_assets.into(), + Rounding::Down, + ) + .ok_or(Error::::UnexpectedFailure)? + .checked_add(1) + .ok_or_else(|| DispatchError::from(Error::::MathOverflow))? + .try_into() + .map_err(|_| DispatchError::from(Error::::MathOverflow))?; + + let first_asset_amount: BalanceOf = multiply_by_rational_with_rounding( + liquidity_token_amount.into(), + first_asset_reserve.into(), + total_liquidity_assets.into(), + Rounding::Down, + ) + .ok_or(Error::::UnexpectedFailure)? + .checked_add(1) + .ok_or_else(|| DispatchError::from(Error::::MathOverflow))? + .try_into() + .map_err(|_| DispatchError::from(Error::::MathOverflow))?; + + log!( + info, + "get_tokens_required_for_minting: ({:?}, {:?}) -> ({:?}, {:?}, {:?}, {:?})", + liquidity_asset_id, + liquidity_token_amount, + first_asset_id, + first_asset_amount, + second_asset_id, + second_asset_amount, + ); + + Ok((first_asset_id, first_asset_amount, second_asset_id, second_asset_amount)) + } + + fn is_liquidity_token(liquidity_asset_id: CurrencyIdOf) -> bool { + LiquidityPools::::get(liquidity_asset_id).is_some() + } +} + +pub trait AssetMetadataMutationTrait { + fn set_asset_info( + asset: CurrencyId, + name: Vec, + symbol: Vec, + decimals: u32, + ) -> DispatchResult; +} + +impl Valuate, CurrencyIdOf> for Pallet { + fn get_liquidity_asset( + first_asset_id: CurrencyIdOf, + second_asset_id: CurrencyIdOf, + ) -> Result, DispatchError> { + Pallet::::get_liquidity_asset(first_asset_id, second_asset_id) + } + + fn get_liquidity_token_mga_pool( + liquidity_token_id: CurrencyIdOf, + ) -> Result<(CurrencyIdOf, CurrencyIdOf), DispatchError> { + let (first_token_id, second_token_id) = + LiquidityPools::::get(liquidity_token_id).ok_or(Error::::NoSuchLiquidityAsset)?; + let native_currency_id = Self::native_token_id(); + match native_currency_id { + _ if native_currency_id == first_token_id => Ok((first_token_id, second_token_id)), + _ if native_currency_id == second_token_id => Ok((second_token_id, first_token_id)), + _ => Err(Error::::NotPairedWithNativeAsset.into()), + } + } + + fn valuate_liquidity_token( + liquidity_token_id: CurrencyIdOf, + liquidity_token_amount: BalanceOf, + ) -> BalanceOf { + let (mga_token_id, other_token_id) = + match Self::get_liquidity_token_mga_pool(liquidity_token_id) { + Ok(pool) => pool, + Err(_) => return Default::default(), + }; + + let mga_token_reserve = match Pallet::::get_reserves(mga_token_id, other_token_id) { + Ok(reserves) => reserves.0, + Err(_) => return Default::default(), + }; + + let liquidity_token_reserve: BalanceOf = + ::Currency::total_issuance(liquidity_token_id.into()); + + if liquidity_token_reserve.is_zero() { + return Default::default() + } + + multiply_by_rational_with_rounding( + mga_token_reserve.into(), + liquidity_token_amount.into(), + liquidity_token_reserve.into(), + Rounding::Down, + ) + .map(SaturatedConversion::saturated_into) + .unwrap_or(BalanceOf::::max_value()) + } + + fn valuate_non_liquidity_token( + non_liquidity_token_id: CurrencyIdOf, + amount: BalanceOf, + ) -> BalanceOf { + let native_token_id = Pallet::::native_token_id(); + + let (native_reserves, token_reserves) = + match Pallet::::get_reserves(native_token_id, non_liquidity_token_id) { + Ok(reserves) => reserves, + Err(_) => return Default::default(), + }; + + multiply_by_rational_with_rounding( + amount.into(), + native_reserves.into(), + token_reserves.into(), + Rounding::Down, + ) + .map(SaturatedConversion::saturated_into) + .unwrap_or(BalanceOf::::max_value()) + } + + fn scale_liquidity_by_mga_valuation( + mga_valuation: BalanceOf, + liquidity_token_amount: BalanceOf, + mga_token_amount: BalanceOf, + ) -> BalanceOf { + if mga_valuation.is_zero() { + return Default::default() + } + + multiply_by_rational_with_rounding( + liquidity_token_amount.into(), + mga_token_amount.into(), + mga_valuation.into(), + Rounding::Down, + ) + .map(SaturatedConversion::saturated_into) + .unwrap_or(BalanceOf::::max_value()) + } + + fn get_pool_state(liquidity_token_id: CurrencyIdOf) -> Option<(BalanceOf, BalanceOf)> { + let (mga_token_id, other_token_id) = + match Self::get_liquidity_token_mga_pool(liquidity_token_id) { + Ok(pool) => pool, + Err(_) => return None, + }; + + let mga_token_reserve = match Pallet::::get_reserves(mga_token_id, other_token_id) { + Ok(reserves) => reserves.0, + Err(_) => return None, + }; + + let liquidity_token_reserve: BalanceOf = + ::Currency::total_issuance(liquidity_token_id.into()); + + if liquidity_token_reserve.is_zero() { + return None + } + + Some((mga_token_reserve, liquidity_token_reserve)) + } + + fn get_reserves( + first_asset_id: CurrencyIdOf, + second_asset_id: CurrencyIdOf, + ) -> Result<(BalanceOf, BalanceOf), DispatchError> { + Pallet::::get_reserves(first_asset_id, second_asset_id) + } + + fn is_liquidity_token(liquidity_asset_id: CurrencyIdOf) -> bool { + LiquidityPools::::get(liquidity_asset_id).is_some() + } +} + +impl PoolCreateApi, CurrencyIdOf> for Pallet { + fn pool_exists(first: CurrencyIdOf, second: CurrencyIdOf) -> bool { + Pools::::contains_key((first, second)) || Pools::::contains_key((second, first)) + } + + fn pool_create( + account: T::AccountId, + first: CurrencyIdOf, + first_amount: BalanceOf, + second: CurrencyIdOf, + second_amount: BalanceOf, + ) -> Option<(CurrencyIdOf, BalanceOf)> { + match , CurrencyIdOf>>::create_pool( + account, + first, + first_amount, + second, + second_amount, + ) { + Ok(_) => LiquidityAssets::::get((first, second)).map(|asset_id| { + (asset_id, ::Currency::total_issuance(asset_id.into())) + }), + Err(e) => { + log!(error, "cannot create pool {:?}!", e); + None + }, + } + } +} + +impl Inspect for Pallet { + type CurrencyId = CurrencyIdOf; + type Balance = BalanceOf; + + fn get_pool_info(pool_id: Self::CurrencyId) -> Option> { + LiquidityPools::::get(pool_id) + } + + fn get_pool_reserves(pool_id: Self::CurrencyId) -> Option> { + let info = Self::get_pool_info(pool_id)?; + Some(Pools::::get(info)) + } + + fn get_non_empty_pools() -> Option> { + Self::get_liq_tokens_for_trading().ok() + } +} + +impl ComputeBalances for Pallet { + fn get_dy( + _: Self::CurrencyId, + asset_in: Self::CurrencyId, + asset_out: Self::CurrencyId, + dx: Self::Balance, + ) -> Option { + Self::calculate_sell_price_id(asset_in, asset_out, dx).ok() + } + + fn get_dy_with_impact( + _: Self::CurrencyId, + asset_in: Self::CurrencyId, + asset_out: Self::CurrencyId, + dx: Self::Balance, + ) -> Option<(Self::Balance, Self::Balance)> { + let mut reserves = Self::get_reserves(asset_in, asset_out).ok()?; + let dy = Self::calculate_sell_price(reserves.0, reserves.1, dx).ok()?; + reserves.0 = reserves.0 + dx; + reserves.1 = reserves.1 - dy; + let dy2 = Self::calculate_sell_price(reserves.0, reserves.1, dx).ok()?; + Some((dy, dy2)) + } + + fn get_dx( + _: Self::CurrencyId, + asset_in: Self::CurrencyId, + asset_out: Self::CurrencyId, + dy: Self::Balance, + ) -> Option { + Self::calculate_buy_price_id(asset_in, asset_out, dy).ok() + } + + fn get_dx_with_impact( + _: Self::CurrencyId, + asset_in: Self::CurrencyId, + asset_out: Self::CurrencyId, + dy: Self::Balance, + ) -> Option<(Self::Balance, Self::Balance)> { + let mut reserves = Self::get_reserves(asset_in, asset_out).ok()?; + let dx = Self::calculate_buy_price(reserves.0, reserves.1, dy).ok()?; + reserves.0 = reserves.0 + dx; + reserves.1 = reserves.1 - dy; + let dx2 = Self::calculate_buy_price(reserves.0, reserves.1, dy).ok()?; + Some((dx, dx2)) + } + + fn get_burn_amounts( + pool_id: Self::CurrencyId, + lp_burn_amount: Self::Balance, + ) -> Option<(Self::Balance, Self::Balance)> { + let pool = Self::get_pool_info(pool_id)?; + Self::get_burn_amount(pool.0, pool.1, lp_burn_amount).ok() + } + + fn get_mint_amount( + pool_id: Self::CurrencyId, + amounts: (Self::Balance, Self::Balance), + ) -> Option { + if amounts.0.is_zero() && amounts.1.is_zero() { + return None; + } + + // could use + // calculate_balanced_sell_amount + // calculate_sell_price + // expected_amount_for_minting + // the provide_liq_with_conversion does a swap inside which alters the state + // so expected_amount_for_minting returns a diff amount + // would need quite some work "fake swap" in memory and compute, + // so return None for now + if amounts.0.is_zero() || amounts.1.is_zero() { + return None; + } + + let pool = Self::get_pool_info(pool_id)?; + let reserves = Pools::::get(pool); + let supply = ::Currency::total_issuance(pool_id); + + let exp_1 = Self::get_expected_amount_for_mint(pool_id, pool.0, amounts.0)?; + let exp_0 = Self::get_expected_amount_for_mint(pool_id, pool.1, amounts.1)?; + + let (amount, reserve) = if exp_1 == amounts.1 { + (amounts.0, reserves.0) + } else if exp_0 == amounts.0 { + (amounts.1, reserves.1) + } else { + return None; + }; + + multiply_by_rational_with_rounding( + amount.into(), + supply.into(), + reserve.into(), + Rounding::Down, + )? + .try_into() + .ok() + } + + // copypasta from mint_liquidity + fn get_expected_amount_for_mint( + pool_id: Self::CurrencyId, + asset_id: Self::CurrencyId, + amount: Self::Balance, + ) -> Option { + let (first_asset_id, second_asset_id) = LiquidityPools::::get(pool_id)?; + let (first_asset_reserve, second_asset_reserve) = + Pallet::::get_reserves(first_asset_id, second_asset_id).ok()?; + + let (b, c) = if asset_id == first_asset_id { + (second_asset_reserve, first_asset_reserve) + } else { + (first_asset_reserve, second_asset_reserve) + }; + + multiply_by_rational_with_rounding(amount.into(), b.into(), c.into(), Rounding::Down)? + .checked_add(1)? + .try_into() + .ok() + } +} + +impl TreasuryBurn for Pallet { + fn settle_treasury_and_burn( + asset_id: Self::CurrencyId, + burn_amount: Self::Balance, + treasury_amount: Self::Balance, + ) -> DispatchResult { + Self::settle_treasury_and_burn(asset_id, burn_amount, treasury_amount) + } +} diff --git a/gasp-node/pallets/xyk/src/mock.rs b/gasp-node/pallets/xyk/src/mock.rs new file mode 100644 index 000000000..bce635ecc --- /dev/null +++ b/gasp-node/pallets/xyk/src/mock.rs @@ -0,0 +1,477 @@ +// Copyright (C) 2020 Mangata team + +use super::*; + +use crate as xyk; +use frame_support::{ + construct_runtime, derive_impl, parameter_types, + traits::{ + tokens::currency::MultiTokenCurrency, ConstU128, ConstU32, Contains, Everything, Nothing, + }, + PalletId, +}; +use frame_system as system; +pub use mangata_support::pools::{PoolInfo, ValuateFor}; +use mangata_types::assets::CustomMetadata; +use orml_tokens::{MultiTokenCurrencyAdapter, MultiTokenCurrencyExtended}; +use orml_traits::{asset_registry::AssetMetadata, parameter_type_with_key}; +use sp_runtime::{traits::AccountIdConversion, BuildStorage, Perbill, Percent}; +use std::{collections::HashMap, sync::Mutex}; + +pub const NATIVE_CURRENCY_ID: u32 = 0; + +pub(crate) type AccountId = u128; +pub(crate) type Amount = i128; +pub(crate) type Balance = u128; +pub(crate) type TokenId = u32; + +type Block = frame_system::mocking::MockBlock; + +construct_runtime!( + pub enum Test { + System: frame_system, + Tokens: orml_tokens, + XykStorage: xyk, + ProofOfStake: pallet_proof_of_stake, + Vesting: pallet_vesting_mangata, + Issuance: pallet_issuance, + } +); + +#[derive_impl(frame_system::config_preludes::TestDefaultConfig as frame_system::DefaultConfig)] +impl frame_system::Config for Test { + type AccountId = AccountId; + type Block = Block; + type Lookup = sp_runtime::traits::IdentityLookup; +} + +parameter_type_with_key! { + pub ExistentialDeposits: |currency_id: TokenId| -> Balance { + match currency_id { + _ => 0, + } + }; +} + +pub struct DustRemovalWhitelist; +impl Contains for DustRemovalWhitelist { + fn contains(a: &AccountId) -> bool { + *a == TreasuryAccount::get() + } +} + +parameter_types! { + pub TreasuryAccount: AccountId = TreasuryPalletId::get().into_account_truncating(); + pub const MaxLocks: u32 = 50; +} + +impl orml_tokens::Config for Test { + type RuntimeEvent = RuntimeEvent; + type Balance = Balance; + type Amount = Amount; + type CurrencyId = TokenId; + type WeightInfo = (); + type ExistentialDeposits = ExistentialDeposits; + type MaxLocks = MaxLocks; + type DustRemovalWhitelist = DustRemovalWhitelist; + type MaxReserves = (); + type ReserveIdentifier = [u8; 8]; + type CurrencyHooks = (); + type NontransferableTokens = Nothing; + type NontransferableTokensAllowList = Nothing; +} + +parameter_types! { + pub const NativeCurrencyId: u32 = NATIVE_CURRENCY_ID; + pub const TreasuryPalletId: PalletId = PalletId(*b"py/trsry"); + pub const BnbTreasurySubAccDerive: [u8; 4] = *b"bnbt"; +} + +parameter_types! { + pub const MinVestedTransfer: Balance = 0; + pub UnvestedFundsAllowedWithdrawReasons: WithdrawReasons = + WithdrawReasons::except(WithdrawReasons::TRANSFER | WithdrawReasons::RESERVE); +} + +impl pallet_vesting_mangata::Config for Test { + type RuntimeEvent = RuntimeEvent; + type Tokens = MultiTokenCurrencyAdapter; + type BlockNumberToBalance = sp_runtime::traits::ConvertInto; + type MinVestedTransfer = MinVestedTransfer; + type WeightInfo = pallet_vesting_mangata::weights::SubstrateWeight; + type UnvestedFundsAllowedWithdrawReasons = UnvestedFundsAllowedWithdrawReasons; + type BlockNumberProvider = System; + // `VestingInfo` encode length is 36bytes. 28 schedules gets encoded as 1009 bytes, which is the + // highest number of schedules that encodes less than 2^10. + const MAX_VESTING_SCHEDULES: u32 = 28; +} + +parameter_types! { + pub LiquidityMiningIssuanceVault: AccountId = LiquidityMiningIssuanceVaultId::get().into_account_truncating(); + pub const StakingIssuanceVaultId: PalletId = PalletId(*b"py/stkiv"); + pub StakingIssuanceVault: AccountId = StakingIssuanceVaultId::get().into_account_truncating(); + pub const SequencerIssuanceVaultId: PalletId = PalletId(*b"py/seqiv"); + pub SequencerIssuanceVault: AccountId = SequencerIssuanceVaultId::get().into_account_truncating(); + pub const MgaTokenId: TokenId = 0u32; + + + pub const TotalCrowdloanAllocation: Balance = 200_000_000; + pub const LinearIssuanceAmount: Balance = 100_000_000_000_000_000; + pub const LinearIssuanceBlocks: u32 = 22_222u32; + pub const LiquidityMiningSplit: Perbill = Perbill::from_parts(555555556); + pub const StakingSplit: Perbill = Perbill::from_parts(344444444); + pub const SequencerSplit: Perbill = Perbill::from_parts(100000000); + pub const ImmediateTGEReleasePercent: Percent = Percent::from_percent(20); + pub const TGEReleasePeriod: u32 = 100u32; // 2 years + pub const TGEReleaseBegin: u32 = 10u32; // Two weeks into chain start + pub const BlocksPerRound: u32 = 5u32; + pub const HistoryLimit: u32 = 10u32; +} + +// #[cfg(not(feature = "runtime-benchmarks"))] +// impl pallet_issuance::Config for Test { +// type RuntimeEvent = RuntimeEvent; +// type NativeCurrencyId = MgaTokenId; +// type Tokens = orml_tokens::MultiTokenCurrencyAdapter; +// type BlocksPerRound = BlocksPerRound; +// type HistoryLimit = HistoryLimit; +// type LiquidityMiningIssuanceVault = LiquidityMiningIssuanceVault; +// type StakingIssuanceVault = StakingIssuanceVault; +// type TotalCrowdloanAllocation = TotalCrowdloanAllocation; +// type LinearIssuanceAmount = LinearIssuanceAmount; +// type LinearIssuanceBlocks = LinearIssuanceBlocks; +// type LiquidityMiningSplit = LiquidityMiningSplit; +// type StakingSplit = StakingSplit; +// type ImmediateTGEReleasePercent = ImmediateTGEReleasePercent; +// type TGEReleasePeriod = TGEReleasePeriod; +// type TGEReleaseBegin = TGEReleaseBegin; +// type VestingProvider = Vesting; +// type WeightInfo = (); +// } +// +impl pallet_issuance::Config for Test { + type RuntimeEvent = RuntimeEvent; + type NativeCurrencyId = MgaTokenId; + type Tokens = orml_tokens::MultiTokenCurrencyAdapter; + type BlocksPerRound = BlocksPerRound; + type HistoryLimit = HistoryLimit; + type LiquidityMiningIssuanceVault = LiquidityMiningIssuanceVault; + type StakingIssuanceVault = StakingIssuanceVault; + type SequencersIssuanceVault = SequencerIssuanceVault; + type TotalCrowdloanAllocation = TotalCrowdloanAllocation; + type LinearIssuanceAmount = LinearIssuanceAmount; + type LinearIssuanceBlocks = LinearIssuanceBlocks; + type LiquidityMiningSplit = LiquidityMiningSplit; + type StakingSplit = StakingSplit; + type SequencersSplit = SequencerSplit; + type ImmediateTGEReleasePercent = ImmediateTGEReleasePercent; + type TGEReleasePeriod = TGEReleasePeriod; + type TGEReleaseBegin = TGEReleaseBegin; + type VestingProvider = Vesting; + type WeightInfo = (); + type LiquidityMiningApi = ProofOfStake; +} + +impl XykBenchmarkingConfig for Test {} + +parameter_types! { + pub const LiquidityMiningIssuanceVaultId: PalletId = PalletId(*b"py/lqmiv"); + pub FakeLiquidityMiningIssuanceVault: AccountId = LiquidityMiningIssuanceVaultId::get().into_account_truncating(); +} + +pub struct DummyBlacklistedPool; + +impl Contains<(TokenId, TokenId)> for DummyBlacklistedPool { + fn contains(pair: &(TokenId, TokenId)) -> bool { + pair == &(1_u32, 9_u32) || pair == &(9_u32, 1_u32) + } +} + +pub struct MockAssetRegister; + +lazy_static::lazy_static! { + static ref ASSET_REGISTER: Mutex>>> = { + let m = HashMap::new(); + Mutex::new(m) + }; +} + +#[cfg(test)] +impl MockAssetRegister { + pub fn instance( + ) -> &'static Mutex>>> { + &ASSET_REGISTER + } +} + +impl AssetMetadataMutationTrait for MockAssetRegister { + fn set_asset_info( + asset: TokenId, + name: Vec, + symbol: Vec, + decimals: u32, + ) -> DispatchResult { + let meta = AssetMetadata { + name: BoundedVec::truncate_from(name), + symbol: BoundedVec::truncate_from(symbol), + decimals, + additional: Default::default(), + existential_deposit: 0, + }; + let mut register = ASSET_REGISTER.lock().unwrap(); + register.insert(asset, meta); + Ok(()) + } +} + +pub struct MockMaintenanceStatusProvider; + +lazy_static::lazy_static! { + static ref MAINTENANCE_STATUS: Mutex = { + let m: bool = false; + Mutex::new(m) + }; +} + +#[cfg(test)] +impl MockMaintenanceStatusProvider { + pub fn instance() -> &'static Mutex { + &MAINTENANCE_STATUS + } +} + +impl MockMaintenanceStatusProvider { + pub fn set_maintenance(value: bool) { + let mut mutex = Self::instance().lock().unwrap(); + *mutex = value; + } +} + +impl GetMaintenanceStatusTrait for MockMaintenanceStatusProvider { + fn is_maintenance() -> bool { + let mutex = Self::instance().lock().unwrap(); + *mutex + } + + fn is_upgradable() -> bool { + unimplemented!() + } +} + +#[cfg(not(feature = "runtime-benchmarks"))] +impl Config for Test { + type RuntimeEvent = RuntimeEvent; + type MaintenanceStatusProvider = MockMaintenanceStatusProvider; + type ActivationReservesProvider = TokensActivationPassthrough; + type Currency = MultiTokenCurrencyAdapter; + type NativeCurrencyId = NativeCurrencyId; + type TreasuryPalletId = TreasuryPalletId; + type BnbTreasurySubAccDerive = BnbTreasurySubAccDerive; + type PoolFeePercentage = ConstU128<20>; + type TreasuryFeePercentage = ConstU128<5>; + type BuyAndBurnFeePercentage = ConstU128<5>; + type LiquidityMiningRewards = ProofOfStake; + type WeightInfo = (); + type VestingProvider = Vesting; + type DisallowedPools = DummyBlacklistedPool; + type DisabledTokens = Nothing; + type AssetMetadataMutation = MockAssetRegister; + type FeeLockWeight = (); +} + +#[cfg(feature = "runtime-benchmarks")] +impl Config for Test { + type RuntimeEvent = RuntimeEvent; + type MaintenanceStatusProvider = MockMaintenanceStatusProvider; + type ActivationReservesProvider = TokensActivationPassthrough; + type Currency = MultiTokenCurrencyAdapter; + type NativeCurrencyId = NativeCurrencyId; + type TreasuryPalletId = TreasuryPalletId; + type BnbTreasurySubAccDerive = BnbTreasurySubAccDerive; + type PoolFeePercentage = ConstU128<20>; + type TreasuryFeePercentage = ConstU128<5>; + type BuyAndBurnFeePercentage = ConstU128<5>; + type LiquidityMiningRewards = ProofOfStake; + type WeightInfo = (); + type VestingProvider = Vesting; + type DisallowedPools = Nothing; + type DisabledTokens = Nothing; + type AssetMetadataMutation = MockAssetRegister; + type FeeLockWeight = (); +} + +mockall::mock! { + pub ValuationApi {} + + impl mangata_support::pools::Valuate for ValuationApi { + type CurrencyId = TokenId; + type Balance = Balance; + + fn find_paired_pool(base_id: TokenId, asset_id: TokenId) -> Result, DispatchError>; + + fn check_can_valuate(base_id: TokenId, pool_id: TokenId) -> Result<(), DispatchError>; + + fn check_pool_exist(pool_id: TokenId) -> Result<(), DispatchError>; + + fn get_reserve_and_lp_supply(base_id: TokenId, pool_id: TokenId) -> Option<(Balance, Balance)>; + + fn get_valuation_for_paired(base_id: TokenId, pool_id: TokenId, amount: Balance) -> Balance; + + fn find_valuation(base_id: TokenId, asset_id: TokenId, amount: Balance) -> Result; + } +} +impl ValuateFor for MockValuationApi {} + +#[cfg(not(feature = "runtime-benchmarks"))] +impl pallet_proof_of_stake::Config for Test { + type RuntimeEvent = RuntimeEvent; + type ActivationReservesProvider = TokensActivationPassthrough; + type NativeCurrencyId = NativeCurrencyId; + type Currency = MultiTokenCurrencyAdapter; + type LiquidityMiningIssuanceVault = FakeLiquidityMiningIssuanceVault; + type RewardsDistributionPeriod = ConstU32<10>; + type WeightInfo = (); + type RewardsSchedulesLimit = ConstU32<10>; + type Min3rdPartyRewardValutationPerSession = ConstU128<10>; + type Min3rdPartyRewardVolume = ConstU128<10>; + type ValuationApi = MockValuationApi; + type SchedulesPerBlock = ConstU32<5>; + type NontransferableTokens = Nothing; +} + +#[cfg(feature = "runtime-benchmarks")] +impl pallet_proof_of_stake::Config for Test { + type RuntimeEvent = RuntimeEvent; + type ActivationReservesProvider = TokensActivationPassthrough; + type NativeCurrencyId = NativeCurrencyId; + type Currency = MultiTokenCurrencyAdapter; + type LiquidityMiningIssuanceVault = FakeLiquidityMiningIssuanceVault; + type RewardsDistributionPeriod = ConstU32<1200>; + type WeightInfo = (); + type RewardsSchedulesLimit = ConstU32<10>; + type Min3rdPartyRewardValutationPerSession = ConstU128<10>; + type Min3rdPartyRewardVolume = ConstU128<10>; + type ValuationApi = MockValuationApi; + type SchedulesPerBlock = ConstU32<5>; + type NontransferableTokens = Nothing; +} + +pub struct TokensActivationPassthrough(PhantomData); + +impl ActivationReservesProviderTrait + for TokensActivationPassthrough +where + ::Currency: + MultiTokenReservableCurrency, +{ + fn get_max_instant_unreserve_amount(token_id: TokenId, account_id: &AccountId) -> Balance { + ProofOfStake::get_rewards_info(account_id, token_id).activated_amount + } + + fn can_activate( + token_id: TokenId, + account_id: &AccountId, + amount: Balance, + _use_balance_from: Option, + ) -> bool { + ::Currency::can_reserve(token_id, account_id, amount) + } + + fn activate( + token_id: TokenId, + account_id: &AccountId, + amount: Balance, + _use_balance_from: Option, + ) -> DispatchResult { + ::Currency::reserve(token_id, account_id, amount) + } + + fn deactivate(token_id: TokenId, account_id: &AccountId, amount: Balance) -> Balance { + ::Currency::unreserve(token_id, account_id, amount) + } +} + +impl Pallet +where + ::Currency: MultiTokenReservableCurrency + + MultiTokenCurrencyExtended, +{ + pub fn balance(id: TokenId, who: AccountId) -> Balance { + ::Currency::free_balance(id.into(), &who).into() + } + pub fn reserved(id: TokenId, who: AccountId) -> Balance { + ::Currency::reserved_balance(id.into(), &who).into() + } + pub fn total_supply(id: TokenId) -> Balance { + ::Currency::total_issuance(id.into()).into() + } + pub fn transfer( + currency_id: TokenId, + source: AccountId, + dest: AccountId, + value: Balance, + ) -> DispatchResult { + ::Currency::transfer( + currency_id, + &source, + &dest, + value, + ExistenceRequirement::KeepAlive, + ) + } + pub fn create_new_token(who: &AccountId, amount: Balance) -> TokenId { + ::Currency::create(who, amount).expect("Token creation failed") + } + + pub fn mint_token(token_id: TokenId, who: &AccountId, amount: Balance) { + ::Currency::mint(token_id, who, amount).expect("Token minting failed") + } +} + +// This function basically just builds a genesis storage key/value store according to +// our desired mockup. +pub fn new_test_ext() -> sp_io::TestExternalities { + let mut ext: sp_io::TestExternalities = + system::GenesisConfig::::default().build_storage().unwrap().into(); + ext.execute_with(|| { + System::set_block_number(1); + MockMaintenanceStatusProvider::set_maintenance(false); + }); + ext +} + +pub(crate) fn events() -> Vec> { + System::events() + .into_iter() + .map(|r| r.event) + .filter_map(|e| if let RuntimeEvent::XykStorage(inner) = e { Some(inner) } else { None }) + .collect::>() +} + +/// Compares the system events with passed in events +/// Prints highlighted diff iff assert_eq fails +#[macro_export] +macro_rules! assert_eq_events { + ($events:expr) => { + match &$events { + e => similar_asserts::assert_eq!(*e, $crate::mock::events()), + } + }; +} + +/// Panics if an event is not found in the system log of events +#[macro_export] +macro_rules! assert_event_emitted { + ($event:expr) => { + match &$event { + e => { + assert!( + $crate::mock::events().iter().find(|x| *x == e).is_some(), + "Event {:?} was not found in events: \n {:?}", + e, + crate::mock::events() + ); + }, + } + }; +} diff --git a/gasp-node/pallets/xyk/src/tests.rs b/gasp-node/pallets/xyk/src/tests.rs new file mode 100644 index 000000000..59d3326f2 --- /dev/null +++ b/gasp-node/pallets/xyk/src/tests.rs @@ -0,0 +1,2666 @@ +// Copyright (C) 2020 Mangata team +#![cfg(not(feature = "runtime-benchmarks"))] +#![allow(non_snake_case)] + +use log::{info, warn}; + +use super::{Event, *}; +use crate::mock::*; +use frame_support::{assert_err, assert_err_ignore_postinfo, dispatch::GetDispatchInfo}; +use mangata_support::traits::LiquidityMiningApi; +use mangata_types::assets::CustomMetadata; +use orml_traits::asset_registry::AssetMetadata; +use serial_test::serial; +use sp_runtime::{traits::Dispatchable, Permill}; +use test_case::test_case; + +const DUMMY_USER_ID: u128 = 2; +const TRADER_ID: u128 = 3; + +fn initialize() { + // creating asset with assetId 0 and minting to accountId 2 + System::set_block_number(1); + let acc_id: u128 = 2; + let amount: u128 = 1000000000000000000000; + // creates token with ID = 0; + XykStorage::create_new_token(&DUMMY_USER_ID, amount); + // creates token with ID = 1; + XykStorage::create_new_token(&DUMMY_USER_ID, amount); + // creates token with ID = 2; + XykStorage::create_new_token(&DUMMY_USER_ID, amount); + // creates token with ID = 3; + XykStorage::create_new_token(&DUMMY_USER_ID, amount); + // creates token with ID = 4; + XykStorage::create_new_token(&DUMMY_USER_ID, amount); + + XykStorage::create_pool( + RuntimeOrigin::signed(DUMMY_USER_ID), + 1, + 40000000000000000000, + 4, + 60000000000000000000, + ) + .unwrap(); + + let pool_created_event = crate::mock::RuntimeEvent::XykStorage( + crate::Event::::PoolCreated(acc_id, 1, 40000000000000000000, 4, 60000000000000000000), + ); + + assert!(System::events().iter().any(|record| record.event == pool_created_event)); +} + +fn multi_initialize() { + // creating asset with assetId 0 and minting to accountId 2 + System::set_block_number(1); + let _acc_id: u128 = 2; + let amount: u128 = 1000000000000000000000; + // creates token with ID = 0; + XykStorage::create_new_token(&DUMMY_USER_ID, amount); + // creates token with ID = 1; + XykStorage::create_new_token(&DUMMY_USER_ID, amount); + // creates token with ID = 2; + XykStorage::create_new_token(&DUMMY_USER_ID, amount); + // creates token with ID = 3; + XykStorage::create_new_token(&DUMMY_USER_ID, amount); + // creates token with ID = 4; + XykStorage::create_new_token(&DUMMY_USER_ID, amount); + // creates token with ID = 5; + XykStorage::create_new_token(&DUMMY_USER_ID, amount); + + // creates token with ID = 0; + XykStorage::mint_token(0, &TRADER_ID, amount); + // creates token with ID = 1; + XykStorage::mint_token(1, &TRADER_ID, amount); + // creates token with ID = 2; + XykStorage::mint_token(2, &TRADER_ID, amount); + // creates token with ID = 3; + XykStorage::mint_token(3, &TRADER_ID, amount); + // creates token with ID = 4; + XykStorage::mint_token(4, &TRADER_ID, amount); + // creates token with ID = 5; + XykStorage::mint_token(5, &TRADER_ID, amount); + + XykStorage::create_pool( + RuntimeOrigin::signed(DUMMY_USER_ID), + 1, + 40000000000000000000, + 2, + 60000000000000000000, + ) + .unwrap(); + + XykStorage::create_pool( + RuntimeOrigin::signed(DUMMY_USER_ID), + 2, + 40000000000000000000, + 3, + 60000000000000000000, + ) + .unwrap(); + + XykStorage::create_pool( + RuntimeOrigin::signed(DUMMY_USER_ID), + 3, + 40000000000000000000, + 4, + 60000000000000000000, + ) + .unwrap(); + + XykStorage::create_pool( + RuntimeOrigin::signed(DUMMY_USER_ID), + 4, + 40000000000000000000, + 5, + 60000000000000000000, + ) + .unwrap(); +} + +fn initialize_buy_and_burn() { + // creating asset with assetId 0 and minting to accountId 2 + let acc_id: u128 = 2; + let amount: u128 = 1000000000000000; + + XykStorage::create_new_token(&acc_id, amount); + XykStorage::create_new_token(&acc_id, amount); + XykStorage::create_new_token(&acc_id, amount); // token id 2 is dummy + XykStorage::create_new_token(&acc_id, amount); // token id 3 is LT for mga-dummy + XykStorage::create_new_token(&acc_id, amount); + XykStorage::create_new_token(&acc_id, amount); + XykStorage::create_pool(RuntimeOrigin::signed(2), 0, 100000000000000, 1, 100000000000000) + .unwrap(); + XykStorage::create_pool(RuntimeOrigin::signed(2), 1, 100000000000000, 4, 100000000000000) + .unwrap(); +} + +#[test] +#[serial] +fn set_info_should_work() { + new_test_ext().execute_with(|| { + // creating asset with assetId 0 and minting to accountId 2 + let acc_id: u128 = 2; + let amount: u128 = 1000000000000000000000; + XykStorage::create_new_token(&acc_id, amount); + XykStorage::create_new_token(&acc_id, amount); + + XykStorage::create_pool( + RuntimeOrigin::signed(2), + 0, + 40000000000000000000, + 1, + 60000000000000000000, + ) + .unwrap(); + + assert_eq!( + *MockAssetRegister::instance().lock().unwrap().get(&2u32).unwrap(), + AssetMetadata { + name: BoundedVec::truncate_from(b"LiquidityPoolToken0x00000002".to_vec()), + symbol: BoundedVec::truncate_from(b"TKN0x00000000-TKN0x00000001".to_vec()), + decimals: 18u32, + additional: CustomMetadata::default(), + existential_deposit: 0u128, + } + ); + }); +} + +#[test] +#[serial] +fn set_info_should_work_with_small_numbers() { + new_test_ext().execute_with(|| { + // creating asset with assetId 0 and minting to accountId 2 + let acc_id: u128 = 2; + let amount: u128 = 1000000000000000000000; + const N: u32 = 12345u32; + + for _ in 0..N { + XykStorage::create_new_token(&acc_id, amount); + } + + XykStorage::create_pool( + RuntimeOrigin::signed(2), + 15, + 40000000000000000000, + 12233, + 60000000000000000000, + ) + .unwrap(); + + assert_eq!( + *MockAssetRegister::instance().lock().unwrap().get(&N).unwrap(), + AssetMetadata { + name: BoundedVec::truncate_from(b"LiquidityPoolToken0x00003039".to_vec()), + symbol: BoundedVec::truncate_from(b"TKN0x0000000F-TKN0x00002FC9".to_vec()), + decimals: 18u32, + additional: CustomMetadata::default(), + existential_deposit: 0u128, + } + ); + }); +} + +#[test] +#[serial] +#[ignore] +fn set_info_should_work_with_large_numbers() { + new_test_ext().execute_with(|| { + // creating asset with assetId 0 and minting to accountId 2 + let acc_id: u128 = 2; + let amount: u128 = 1000000000000000000000; + const N: u32 = 1524501234u32; + + for _ in 0..N { + XykStorage::create_new_token(&acc_id, amount); + } + + XykStorage::create_pool( + RuntimeOrigin::signed(2), + 15000000, + 40000000000000000000, + 12233000, + 60000000000000000000, + ) + .unwrap(); + + assert_eq!( + *MockAssetRegister::instance().lock().unwrap().get(&1524501234u32).unwrap(), + AssetMetadata { + name: BoundedVec::truncate_from(b"LiquidityPoolToken0x5ADE0AF2".to_vec()), + symbol: BoundedVec::truncate_from(b"TKN0x00E4E1C0-TKN0x00BAA928".to_vec()), + decimals: 18u32, + additional: CustomMetadata::default(), + existential_deposit: 0u128, + } + ); + }); +} + +#[test] +#[serial] +fn buy_and_burn_sell_mangata() { + new_test_ext().execute_with(|| { + initialize_buy_and_burn(); + + XykStorage::multiswap_sell_asset(RuntimeOrigin::signed(2), vec![0, 1], 50000000000000, 0) + .unwrap(); + + assert_eq!(XykStorage::asset_pool((0, 1)), (149949999999998, 66733400066734)); + assert_eq!(XykStorage::balance(0, 2), 850000000000000); + assert_eq!(XykStorage::balance(1, 2), 833266599933266); + assert_eq!(XykStorage::balance(0, XykStorage::account_id()), 149949999999998); + assert_eq!(XykStorage::balance(1, XykStorage::account_id()), 166733400066734); + assert_eq!(XykStorage::balance(0, XykStorage::treasury_account_id()), 25000000001); + assert_eq!(XykStorage::balance(1, XykStorage::treasury_account_id()), 0); + assert_eq!(XykStorage::balance(0, XykStorage::bnb_treasury_account_id()), 0); + assert_eq!(XykStorage::balance(1, XykStorage::bnb_treasury_account_id()), 0); + }); +} + +#[test] +#[serial] +fn buy_and_burn_sell_has_mangata_pair() { + new_test_ext().execute_with(|| { + initialize_buy_and_burn(); + + XykStorage::multiswap_sell_asset(RuntimeOrigin::signed(2), vec![1, 4], 50000000000000, 0) + .unwrap(); + + assert_eq!(XykStorage::asset_pool((0, 1)), (99950024987505, 100050000000002)); + assert_eq!(XykStorage::asset_pool((1, 4)), (149949999999998, 66733400066734)); + assert_eq!(XykStorage::balance(1, 2), 750000000000000); // user acc: regular trade result + assert_eq!(XykStorage::balance(4, 2), 933266599933266); // user acc: regular trade result + assert_eq!(XykStorage::balance(0, XykStorage::account_id()), 99950024987505); + assert_eq!(XykStorage::balance(1, XykStorage::account_id()), 250000000000000); + assert_eq!(XykStorage::balance(4, XykStorage::account_id()), 66733400066734); // vault: regular trade result + assert_eq!(XykStorage::balance(0, XykStorage::treasury_account_id()), 24987506247); // 24987506247 mangata in treasury + assert_eq!(XykStorage::balance(1, XykStorage::treasury_account_id()), 0); + assert_eq!(XykStorage::balance(0, XykStorage::bnb_treasury_account_id()), 0); + assert_eq!(XykStorage::balance(1, XykStorage::bnb_treasury_account_id()), 0); + }); +} + +#[test] +#[serial] +fn buy_and_burn_sell_none_have_mangata_pair() { + new_test_ext().execute_with(|| { + initialize_buy_and_burn(); + + XykStorage::multiswap_sell_asset(RuntimeOrigin::signed(2), vec![4, 1], 50000000000000, 0) + .unwrap(); + + assert_eq!(XykStorage::asset_pool((0, 1)), (100000000000000, 100000000000000)); + assert_eq!(XykStorage::asset_pool((1, 4)), (66733400066734, 149949999999998)); + assert_eq!(XykStorage::balance(1, 2), 833266599933266); // user acc: regular trade result + assert_eq!(XykStorage::balance(4, 2), 850000000000000); // user acc: regular trade result + assert_eq!(XykStorage::balance(0, XykStorage::account_id()), 100000000000000); + assert_eq!(XykStorage::balance(1, XykStorage::account_id()), 166733400066734); + assert_eq!(XykStorage::balance(4, XykStorage::account_id()), 149949999999998); // vault: regular trade result + assert_eq!(XykStorage::balance(0, XykStorage::treasury_account_id()), 0); // 24987506247 mangata in treasury + assert_eq!(XykStorage::balance(4, XykStorage::treasury_account_id()), 25000000001); + assert_eq!(XykStorage::balance(0, XykStorage::bnb_treasury_account_id()), 0); + assert_eq!(XykStorage::balance(4, XykStorage::bnb_treasury_account_id()), 25000000001); + }); +} + +#[test] +#[serial] +fn buy_and_burn_buy_where_sold_is_mangata() { + new_test_ext().execute_with(|| { + initialize_buy_and_burn(); + + XykStorage::multiswap_buy_asset( + RuntimeOrigin::signed(2), + vec![0, 1], + 33266599933266, + 50000000000001, + ) + .unwrap(); + + assert_eq!(XykStorage::asset_pool((0, 1)), (149949999999999, 66733400066734)); + assert_eq!(XykStorage::balance(0, 2), 850000000000001); + assert_eq!(XykStorage::balance(1, 2), 833266599933266); + + assert_eq!(XykStorage::balance(0, XykStorage::account_id()), 149949999999999); + assert_eq!(XykStorage::balance(1, XykStorage::account_id()), 166733400066734); + assert_eq!(XykStorage::balance(0, XykStorage::treasury_account_id()), 25000000000); + assert_eq!(XykStorage::balance(1, XykStorage::treasury_account_id()), 0); + assert_eq!(XykStorage::balance(0, XykStorage::bnb_treasury_account_id()), 0); + assert_eq!(XykStorage::balance(1, XykStorage::bnb_treasury_account_id()), 0); + }); +} + +#[test] +#[serial] +fn buy_and_burn_buy_where_sold_has_mangata_pair() { + new_test_ext().execute_with(|| { + initialize_buy_and_burn(); + + XykStorage::multiswap_buy_asset( + RuntimeOrigin::signed(2), + vec![1, 4], + 33266599933266, + 50000000000001, + ) + .unwrap(); + + assert_eq!(XykStorage::asset_pool((0, 1)), (99950024987507, 100050000000000)); + assert_eq!(XykStorage::asset_pool((1, 4)), (149949999999999, 66733400066734)); + assert_eq!(XykStorage::balance(1, 2), 750000000000001); // user acc: regular trade result + assert_eq!(XykStorage::balance(4, 2), 933266599933266); // user acc: regular trade result + assert_eq!(XykStorage::balance(0, XykStorage::account_id()), 99950024987507); + assert_eq!(XykStorage::balance(1, XykStorage::account_id()), 249999999999999); + assert_eq!(XykStorage::balance(4, XykStorage::account_id()), 66733400066734); // vault: regular trade result + assert_eq!(XykStorage::balance(0, XykStorage::treasury_account_id()), 24987506246); // 24987506247 mangata in treasury + assert_eq!(XykStorage::balance(1, XykStorage::treasury_account_id()), 0); + assert_eq!(XykStorage::balance(0, XykStorage::bnb_treasury_account_id()), 0); + assert_eq!(XykStorage::balance(1, XykStorage::bnb_treasury_account_id()), 0); + }); +} + +#[test] +#[serial] +fn buy_and_burn_buy_none_have_mangata_pair() { + new_test_ext().execute_with(|| { + initialize_buy_and_burn(); + + XykStorage::multiswap_buy_asset( + RuntimeOrigin::signed(2), + vec![4, 1], + 33266599933266, + 50000000000001, + ) + .unwrap(); + + assert_eq!(XykStorage::asset_pool((0, 1)), (100000000000000, 100000000000000)); + assert_eq!(XykStorage::asset_pool((1, 4)), (66733400066734, 149949999999999)); + assert_eq!(XykStorage::balance(1, 2), 833266599933266); // user acc: regular trade result + assert_eq!(XykStorage::balance(4, 2), 850000000000001); // user acc: regular trade result + assert_eq!(XykStorage::balance(0, XykStorage::account_id()), 100000000000000); + assert_eq!(XykStorage::balance(1, XykStorage::account_id()), 166733400066734); + assert_eq!(XykStorage::balance(4, XykStorage::account_id()), 149949999999999); // vault: regular trade result + assert_eq!(XykStorage::balance(0, XykStorage::treasury_account_id()), 0); // 24987506247 mangata in treasury + assert_eq!(XykStorage::balance(4, XykStorage::treasury_account_id()), 25000000000); + assert_eq!(XykStorage::balance(0, XykStorage::bnb_treasury_account_id()), 0); + assert_eq!(XykStorage::balance(4, XykStorage::bnb_treasury_account_id()), 25000000000); + }); +} + +#[test] +#[serial] +fn multi() { + new_test_ext().execute_with(|| { + let acc_id: u128 = 2; + let amount: u128 = 2000000000000000000000000; + + XykStorage::create_new_token(&acc_id, amount); + XykStorage::create_new_token(&acc_id, amount); + XykStorage::create_new_token(&acc_id, amount); + XykStorage::create_new_token(&acc_id, amount); + XykStorage::create_new_token(&acc_id, amount); + XykStorage::create_pool( + RuntimeOrigin::signed(2), + 1, + 1000000000000000000000000, + 4, + 500000000000000000000000, + ) + .unwrap(); + assert_eq!( + XykStorage::asset_pool((1, 4)), + (1000000000000000000000000, 500000000000000000000000) + ); + assert_eq!(XykStorage::liquidity_asset((1, 4)), Some(5)); // liquidity assetId corresponding to newly created pool + assert_eq!(XykStorage::liquidity_pool(5), Some((1, 4))); // liquidity assetId corresponding to newly created pool + assert_eq!(XykStorage::total_supply(5), 750000000000000000000000); // total liquidity assets + assert_eq!(XykStorage::balance(5, 2), 750000000000000000000000); // amount of liquidity assets owned by user by creating pool / initial minting + assert_eq!(XykStorage::balance(1, 2), 1000000000000000000000000); // amount of asset 1 in user acc after creating pool / initial minting + assert_eq!(XykStorage::balance(4, 2), 1500000000000000000000000); // amount of asset 2 in user acc after creating pool / initial minting + assert_eq!(XykStorage::balance(1, XykStorage::account_id()), 1000000000000000000000000); // amount of asset 0 in vault acc after creating pool + assert_eq!(XykStorage::balance(4, XykStorage::account_id()), 500000000000000000000000); // amount of asset 1 in vault acc after creating pool + + XykStorage::mint_liquidity( + RuntimeOrigin::signed(2), + 1, + 4, + 500000000000000000000000, + 5000000000000000000000000, + ) + .unwrap(); + + assert_eq!( + XykStorage::asset_pool((1, 4)), + (1500000000000000000000000, 750000000000000000000001) + ); + assert_eq!(XykStorage::total_supply(5), 1125000000000000000000000); // total liquidity assets + assert_eq!(XykStorage::balance(5, 2), 1125000000000000000000000); // amount of liquidity assets owned by user by creating pool / initial minting + assert_eq!(XykStorage::balance(1, 2), 500000000000000000000000); // amount of asset 0 in user acc after creating pool / initial minting + assert_eq!(XykStorage::balance(4, 2), 1249999999999999999999999); // amount of asset 1 in user acc after creating pool / initial minting + assert_eq!(XykStorage::balance(1, XykStorage::account_id()), 1500000000000000000000000); // amount of asset 1 in vault acc after creating pool + assert_eq!(XykStorage::balance(4, XykStorage::account_id()), 750000000000000000000001); // amount of asset 2 in vault acc after creating pool + + XykStorage::burn_liquidity(RuntimeOrigin::signed(2), 1, 4, 225000000000000000000000) + .unwrap(); + + assert_eq!( + XykStorage::asset_pool((1, 4)), + (1200000000000000000000000, 600000000000000000000001) + ); + assert_eq!(XykStorage::total_supply(5), 900000000000000000000000); // total liquidity assets + assert_eq!(XykStorage::balance(5, 2), 900000000000000000000000); // amount of liquidity assets owned by user by creating pool / initial minting + assert_eq!(XykStorage::balance(1, 2), 800000000000000000000000); // amount of asset 0 in user acc after creating pool / initial minting + assert_eq!(XykStorage::balance(4, 2), 1399999999999999999999999); // amount of asset 1 in user acc after creating pool / initial minting + assert_eq!(XykStorage::balance(1, XykStorage::account_id()), 1200000000000000000000000); // amount of asset 1 in vault acc after creating pool + assert_eq!(XykStorage::balance(4, XykStorage::account_id()), 600000000000000000000001); // amount of asset 2 in vault acc after creating pool + + XykStorage::burn_liquidity(RuntimeOrigin::signed(2), 1, 4, 225000000000000000000000) + .unwrap(); + + assert_eq!( + XykStorage::asset_pool((1, 4)), + (900000000000000000000000, 450000000000000000000001) + ); + assert_eq!(XykStorage::total_supply(5), 675000000000000000000000); // total liquidity assets + assert_eq!(XykStorage::balance(5, 2), 675000000000000000000000); // amount of liquidity assets owned by user by creating pool / initial minting + assert_eq!(XykStorage::balance(1, 2), 1100000000000000000000000); // amount of asset 0 in user acc after creating pool / initial minting + assert_eq!(XykStorage::balance(4, 2), 1549999999999999999999999); // amount of asset 1 in user acc after creating pool / initial minting + assert_eq!(XykStorage::balance(1, XykStorage::account_id()), 900000000000000000000000); // amount of asset 1 in vault acc after creating pool + assert_eq!(XykStorage::balance(4, XykStorage::account_id()), 450000000000000000000001); // amount of asset 2 in vault acc after creating pool + + XykStorage::mint_liquidity( + RuntimeOrigin::signed(2), + 1, + 4, + 1000000000000000000000000, + 10000000000000000000000000, + ) + .unwrap(); + + assert_eq!( + XykStorage::asset_pool((1, 4)), + (1900000000000000000000000, 950000000000000000000003) + ); + assert_eq!(XykStorage::total_supply(5), 1425000000000000000000000); // total liquidity assets + assert_eq!(XykStorage::balance(5, 2), 1425000000000000000000000); // amount of liquidity assets owned by user by creating pool / initial minting + assert_eq!(XykStorage::balance(1, 2), 100000000000000000000000); // amount of asset 0 in user acc after creating pool / initial minting + assert_eq!(XykStorage::balance(4, 2), 1049999999999999999999997); // amount of asset 1 in user acc after creating pool / initial minting + assert_eq!(XykStorage::balance(1, XykStorage::account_id()), 1900000000000000000000000); // amount of asset 1 in vault acc after creating pool + assert_eq!(XykStorage::balance(4, XykStorage::account_id()), 950000000000000000000003); + // amount of asset 0 in vault acc after creating pool + }); +} + +#[test] +#[serial] +fn create_pool_W() { + new_test_ext().execute_with(|| { + initialize(); + + assert_eq!(XykStorage::asset_pool((1, 4)), (40000000000000000000, 60000000000000000000)); + assert_eq!(XykStorage::liquidity_asset((1, 4)), Some(5)); // liquidity assetId corresponding to newly created pool + assert_eq!(XykStorage::liquidity_pool(5), Some((1, 4))); // liquidity assetId corresponding to newly created pool + assert_eq!(XykStorage::total_supply(5), 50000000000000000000); // total liquidity assets + assert_eq!(XykStorage::balance(5, 2), 50000000000000000000); // amount of liquidity assets owned by user by creating pool / initial minting + assert_eq!(XykStorage::balance(1, 2), 960000000000000000000); // amount of asset 0 in user acc after creating pool / initial minting + assert_eq!(XykStorage::balance(4, 2), 940000000000000000000); // amount of asset 1 in user acc after creating pool / initial minting + assert_eq!(XykStorage::balance(1, XykStorage::account_id()), 40000000000000000000); // amount of asset 0 in vault acc after creating pool + assert_eq!(XykStorage::balance(4, XykStorage::account_id()), 60000000000000000000); + // amount of asset 1 in vault acc after creating pool + }); +} + +#[test] +#[serial] +fn create_pool_N_already_exists() { + new_test_ext().execute_with(|| { + initialize(); + + assert_err!( + XykStorage::create_pool(RuntimeOrigin::signed(2), 1, 500000, 4, 500000,), + Error::::PoolAlreadyExists, + ); + }); +} + +#[test] +#[serial] +fn create_pool_N_already_exists_other_way() { + new_test_ext().execute_with(|| { + initialize(); + + assert_err!( + XykStorage::create_pool(RuntimeOrigin::signed(2), 4, 500000, 1, 500000,), + Error::::PoolAlreadyExists, + ); + }); +} + +#[test] +#[serial] +fn create_pool_N_not_enough_first_asset() { + new_test_ext().execute_with(|| { + let acc_id: u128 = 2; + let amount: u128 = 1000000; + XykStorage::create_new_token(&acc_id, amount); + XykStorage::create_new_token(&acc_id, amount); + + assert_err!( + XykStorage::create_pool(RuntimeOrigin::signed(2), 0, 1500000, 1, 500000,), + Error::::NotEnoughAssets, + ); //asset 0 issued to user 1000000, trying to create pool using 1500000 + }); +} + +#[test] +#[serial] +fn create_pool_N_not_enough_second_asset() { + new_test_ext().execute_with(|| { + let acc_id: u128 = 2; + let amount: u128 = 1000000; + XykStorage::create_new_token(&acc_id, amount); + XykStorage::create_new_token(&acc_id, amount); + + assert_err!( + XykStorage::create_pool(RuntimeOrigin::signed(2), 0, 500000, 1, 1500000,), + Error::::NotEnoughAssets, + ); //asset 1 issued to user 1000000, trying to create pool using 1500000 + }); +} + +#[test] +#[serial] +fn create_pool_N_same_asset() { + new_test_ext().execute_with(|| { + initialize(); + + assert_err!( + XykStorage::create_pool(RuntimeOrigin::signed(2), 0, 500000, 0, 500000,), + Error::::SameAsset, + ); + }); +} + +#[test] +#[serial] +fn create_pool_N_zero_first_amount() { + new_test_ext().execute_with(|| { + initialize(); + + assert_err!( + XykStorage::create_pool(RuntimeOrigin::signed(2), 0, 0, 1, 500000,), + Error::::ZeroAmount, + ); + }); +} + +#[test] +#[serial] +fn create_pool_N_zero_second_amount() { + new_test_ext().execute_with(|| { + initialize(); + + assert_err!( + XykStorage::create_pool(RuntimeOrigin::signed(2), 0, 500000, 1, 0,), + Error::::ZeroAmount, + ); + }); +} + +#[test] +#[serial] +fn sell_W() { + new_test_ext().execute_with(|| { + System::set_block_number(1); + initialize(); + + XykStorage::multiswap_sell_asset( + RuntimeOrigin::signed(2), + vec![1, 4], + 20000000000000000000, + 0, + ) + .unwrap(); // selling 20000000000000000000 assetId 0 of pool 0 1 + + assert_eq!(XykStorage::balance(1, 2), 940000000000000000000); // amount in user acc after selling + assert_eq!(XykStorage::balance(4, 2), 959959959959959959959); // amount in user acc after buying + assert_eq!(XykStorage::asset_pool((1, 4)), (59979999999999999998, 40040040040040040041)); // amount of asset 0 in pool map + // assert_eq!(XykStorage::asset_pool2((1, 0)), 40040040040040040041); // amount of asset 1 in pool map + assert_eq!(XykStorage::balance(1, 2), 940000000000000000000); // amount of asset 0 on account 2 + assert_eq!(XykStorage::balance(4, 2), 959959959959959959959); // amount of asset 1 on account 2 + assert_eq!(XykStorage::balance(1, XykStorage::account_id()), 59979999999999999998); // amount of asset 0 in vault acc after creating pool + assert_eq!(XykStorage::balance(4, XykStorage::account_id()), 40040040040040040041); // amount of asset 1 in vault acc after creating pool + + let assets_swapped_event = + crate::mock::RuntimeEvent::XykStorage(crate::Event::::AssetsSwapped( + 2, + vec![1_u32, 4_u32], + 20000000000000000000, + 19959959959959959959, + )); + + assert!(System::events().iter().any(|record| record.event == assets_swapped_event)); + }); +} + +#[test] +#[serial] +fn sell_W_other_way() { + new_test_ext().execute_with(|| { + initialize(); + + XykStorage::multiswap_sell_asset( + RuntimeOrigin::signed(2), + vec![4, 1], + 30000000000000000000, + 0, + ) + .unwrap(); // selling 30000000000000000000 assetId 1 of pool 0 1 + + assert_eq!(XykStorage::balance(1, 2), 973306639973306639973); // amount of asset 0 in user acc after selling + assert_eq!(XykStorage::balance(4, 2), 910000000000000000000); // amount of asset 1 in user acc after buying + // assert_eq!(XykStorage::asset_pool((1, 2)), 26684462240017795575); // amount of asset 0 in pool map + // assert_eq!(XykStorage::asset_pool((1, 0)), 90000000000000000000); // amount of asset 1 in pool map + assert_eq!(XykStorage::asset_pool((1, 4)), (26693360026693360027, 89969999999999999998)); + assert_eq!(XykStorage::balance(1, 2), 973306639973306639973); // amount of asset 0 on account 2 + assert_eq!(XykStorage::balance(4, 2), 910000000000000000000); // amount of asset 1 on account 2 + assert_eq!(XykStorage::balance(1, XykStorage::account_id()), 26693360026693360027); // amount of asset 0 in vault acc after creating pool + assert_eq!(XykStorage::balance(4, XykStorage::account_id()), 89969999999999999998); + // amount of asset 1 in vault acc after creating pool + }); +} + +#[test] +#[serial] +fn sell_N_no_such_pool() { + new_test_ext().execute_with(|| { + initialize(); + + assert_err_ignore_postinfo!( + XykStorage::multiswap_sell_asset(RuntimeOrigin::signed(2), vec![0, 10], 250000, 0), + Error::::NoSuchPool, + ); // selling 250000 assetId 0 of pool 0 10 (only pool 0 1 exists) + }); +} +#[test] +#[serial] +fn sell_N_not_enough_selling_assset() { + new_test_ext().execute_with(|| { + initialize(); + + assert_err_ignore_postinfo!( + // XykStorage::multiswap_sell_asset(RuntimeOrigin::signed(2), vec![1, 4], 1000000000000000000000, 0), + XykStorage::sell_asset(RuntimeOrigin::signed(2), 1, 4, 1000000000000000000000, 0), + orml_tokens::Error::::BalanceTooLow, + ); // selling 1000000000000000000000 assetId 0 of pool 0 1 (user has only 960000000000000000000) + }); +} + +#[test] +#[serial] +fn sell_W_insufficient_output_amount() { + new_test_ext().execute_with(|| { + initialize(); + + let input_balance_before = XykStorage::balance(1, 2); + let output_balance_before = XykStorage::balance(4, 2); + + let mut expected_events = + vec![Event::PoolCreated(2, 1, 40000000000000000000, 4, 60000000000000000000)]; + assert_eq_events!(expected_events.clone()); + + assert_ok!(XykStorage::multiswap_sell_asset( + RuntimeOrigin::signed(2), + vec![1, 4], + 250000, + 500000 + )); // selling 250000 assetId 0 of pool 0 1, by the formula user should get 166333 asset 1, but is requesting 500000 + + let mut new_events_0 = + vec![Event::SellAssetFailedDueToSlippage(2, 1, 250000, 4, 373874, 500000)]; + + expected_events.append(&mut new_events_0); + assert_eq_events!(expected_events.clone()); + + let input_balance_after = XykStorage::balance(1, 2); + let output_balance_after = XykStorage::balance(4, 2); + + assert_ne!(input_balance_before, input_balance_after); + assert_eq!(output_balance_before, output_balance_after); + }); +} + +#[test] +#[serial] +fn sell_N_insufficient_output_amount_inner_function_error_upon_bad_slippage() { + new_test_ext().execute_with(|| { + initialize(); + + assert_err!( + >::sell_asset( + 2, 1, 4, 250000, 500000, true + ), + Error::::InsufficientOutputAmount, + ); // selling 250000 assetId 0 of pool 0 1, by the formula user should get 166333 asset 1, but is requesting 500000 + }); +} + +#[test] +#[serial] +fn sell_W_insufficient_output_amount_inner_function_NO_error_upon_bad_slippage() { + new_test_ext().execute_with(|| { + initialize(); + + assert_ok!(>::sell_asset( + 2, 1, 4, 250000, 500000, false + ),); // selling 250000 assetId 0 of pool 0 1, by the formula user should get 166333 asset 1, but is requesting 500000 + }); +} + +#[test] +#[serial] +fn sell_N_zero_amount() { + new_test_ext().execute_with(|| { + initialize(); + + assert_err_ignore_postinfo!( + XykStorage::multiswap_sell_asset(RuntimeOrigin::signed(2), vec![1, 4], 0, 500000), + Error::::ZeroAmount, + ); // selling 0 assetId 0 of pool 0 1 + }); +} + +#[test] +#[serial] +fn multiswap_sell_W() { + new_test_ext().execute_with(|| { + System::set_block_number(1); + multi_initialize(); + + assert_eq!(XykStorage::balance(1, TRADER_ID), 1000000000000000000000); + assert_eq!(XykStorage::balance(2, TRADER_ID), 1000000000000000000000); + assert_eq!(XykStorage::balance(3, TRADER_ID), 1000000000000000000000); + assert_eq!(XykStorage::balance(4, TRADER_ID), 1000000000000000000000); + assert_eq!(XykStorage::balance(5, TRADER_ID), 1000000000000000000000); + + assert_eq!(XykStorage::asset_pool((1, 2)), (40000000000000000000, 60000000000000000000)); + assert_eq!(XykStorage::asset_pool((2, 3)), (40000000000000000000, 60000000000000000000)); + assert_eq!(XykStorage::asset_pool((3, 4)), (40000000000000000000, 60000000000000000000)); + assert_eq!(XykStorage::asset_pool((4, 5)), (40000000000000000000, 60000000000000000000)); + assert_eq!(XykStorage::balance(1, XykStorage::account_id()), 40000000000000000000); + assert_eq!(XykStorage::balance(2, XykStorage::account_id()), 100000000000000000000); + assert_eq!(XykStorage::balance(3, XykStorage::account_id()), 100000000000000000000); + assert_eq!(XykStorage::balance(4, XykStorage::account_id()), 100000000000000000000); + assert_eq!(XykStorage::balance(5, XykStorage::account_id()), 60000000000000000000); + + assert_ok!(XykStorage::multiswap_sell_asset( + RuntimeOrigin::signed(TRADER_ID), + vec![1, 2, 3, 4, 5], + 20000000000000000000, + 0 + )); + + assert_eq!(XykStorage::balance(1, TRADER_ID), 980000000000000000000); + assert_eq!(XykStorage::balance(2, TRADER_ID), 1000000000000000000000); + assert_eq!(XykStorage::balance(3, TRADER_ID), 1000000000000000000000); + assert_eq!(XykStorage::balance(4, TRADER_ID), 1000000000000000000000); + assert_eq!(XykStorage::balance(5, TRADER_ID), 1019903585399816558050); + + assert_eq!(XykStorage::asset_pool((1, 2)), (59979999999999999998, 40040040040040040041)); + assert_eq!(XykStorage::asset_pool((2, 3)), (59939999999999999999, 40066724398222064173)); + assert_eq!(XykStorage::asset_pool((3, 4)), (59913342326176157891, 40084527730110691659)); + assert_eq!(XykStorage::asset_pool((4, 5)), (59895556797619419031, 40096414600183441950)); + assert_eq!(XykStorage::balance(1, XykStorage::account_id()), 59979999999999999998); + assert_eq!(XykStorage::balance(2, XykStorage::account_id()), 99980040040040040040); + assert_eq!(XykStorage::balance(3, XykStorage::account_id()), 99980066724398222064); + assert_eq!(XykStorage::balance(4, XykStorage::account_id()), 99980084527730110690); + assert_eq!(XykStorage::balance(5, XykStorage::account_id()), 40096414600183441950); + + let assets_swapped_event = + crate::mock::RuntimeEvent::XykStorage(crate::Event::::AssetsSwapped( + TRADER_ID, + vec![1, 2, 3, 4, 5], + 20000000000000000000, + 19903585399816558050, + )); + + assert!(System::events().iter().any(|record| record.event == assets_swapped_event)); + }); +} + +#[test] +#[serial] +fn multiswap_sell_bad_slippage_charges_fee_W() { + new_test_ext().execute_with(|| { + System::set_block_number(1); + multi_initialize(); + + assert_eq!(XykStorage::balance(1, TRADER_ID), 1000000000000000000000); + assert_eq!(XykStorage::balance(2, TRADER_ID), 1000000000000000000000); + assert_eq!(XykStorage::balance(3, TRADER_ID), 1000000000000000000000); + assert_eq!(XykStorage::balance(4, TRADER_ID), 1000000000000000000000); + assert_eq!(XykStorage::balance(5, TRADER_ID), 1000000000000000000000); + + assert_eq!(XykStorage::asset_pool((1, 2)), (40000000000000000000, 60000000000000000000)); + assert_eq!(XykStorage::asset_pool((2, 3)), (40000000000000000000, 60000000000000000000)); + assert_eq!(XykStorage::asset_pool((3, 4)), (40000000000000000000, 60000000000000000000)); + assert_eq!(XykStorage::asset_pool((4, 5)), (40000000000000000000, 60000000000000000000)); + assert_eq!(XykStorage::balance(1, XykStorage::account_id()), 40000000000000000000); + assert_eq!(XykStorage::balance(2, XykStorage::account_id()), 100000000000000000000); + assert_eq!(XykStorage::balance(3, XykStorage::account_id()), 100000000000000000000); + assert_eq!(XykStorage::balance(4, XykStorage::account_id()), 100000000000000000000); + assert_eq!(XykStorage::balance(5, XykStorage::account_id()), 60000000000000000000); + + assert_ok!(XykStorage::multiswap_sell_asset( + RuntimeOrigin::signed(TRADER_ID), + vec![1, 2, 3, 4, 5], + 20000000000000000000, + 20000000000000000000 + )); + + assert_eq!(XykStorage::balance(1, TRADER_ID), 999939999999999999997); + assert_eq!(XykStorage::balance(2, TRADER_ID), 1000000000000000000000); + assert_eq!(XykStorage::balance(3, TRADER_ID), 1000000000000000000000); + assert_eq!(XykStorage::balance(4, TRADER_ID), 1000000000000000000000); + assert_eq!(XykStorage::balance(5, TRADER_ID), 1000000000000000000000); + + assert_eq!(XykStorage::asset_pool((1, 2)), (40040000000000000001, 60000000000000000000)); + assert_eq!(XykStorage::asset_pool((2, 3)), (40000000000000000000, 60000000000000000000)); + assert_eq!(XykStorage::asset_pool((3, 4)), (40000000000000000000, 60000000000000000000)); + assert_eq!(XykStorage::asset_pool((4, 5)), (40000000000000000000, 60000000000000000000)); + assert_eq!(XykStorage::balance(1, XykStorage::account_id()), 40040000000000000001); + assert_eq!(XykStorage::balance(2, XykStorage::account_id()), 100000000000000000000); + assert_eq!(XykStorage::balance(3, XykStorage::account_id()), 100000000000000000000); + assert_eq!(XykStorage::balance(4, XykStorage::account_id()), 100000000000000000000); + assert_eq!(XykStorage::balance(5, XykStorage::account_id()), 60000000000000000000); + + assert_event_emitted!(crate::Event::::MultiSwapAssetFailedOnAtomicSwap( + TRADER_ID, + vec![1, 2, 3, 4, 5], + 20000000000000000000, + module_err(Error::::InsufficientOutputAmount) + )); + }); +} + +#[test] +#[serial] +fn multiswap_sell_bad_atomic_swap_charges_fee_W() { + new_test_ext().execute_with(|| { + System::set_block_number(1); + multi_initialize(); + + assert_eq!(XykStorage::balance(1, TRADER_ID), 1000000000000000000000); + assert_eq!(XykStorage::balance(2, TRADER_ID), 1000000000000000000000); + assert_eq!(XykStorage::balance(3, TRADER_ID), 1000000000000000000000); + assert_eq!(XykStorage::balance(4, TRADER_ID), 1000000000000000000000); + assert_eq!(XykStorage::balance(5, TRADER_ID), 1000000000000000000000); + + assert_eq!(XykStorage::asset_pool((1, 2)), (40000000000000000000, 60000000000000000000)); + assert_eq!(XykStorage::asset_pool((2, 3)), (40000000000000000000, 60000000000000000000)); + assert_eq!(XykStorage::asset_pool((3, 4)), (40000000000000000000, 60000000000000000000)); + assert_eq!(XykStorage::asset_pool((4, 5)), (40000000000000000000, 60000000000000000000)); + assert_eq!(XykStorage::balance(1, XykStorage::account_id()), 40000000000000000000); + assert_eq!(XykStorage::balance(2, XykStorage::account_id()), 100000000000000000000); + assert_eq!(XykStorage::balance(3, XykStorage::account_id()), 100000000000000000000); + assert_eq!(XykStorage::balance(4, XykStorage::account_id()), 100000000000000000000); + assert_eq!(XykStorage::balance(5, XykStorage::account_id()), 60000000000000000000); + + assert_ok!(XykStorage::multiswap_sell_asset( + RuntimeOrigin::signed(TRADER_ID), + vec![1, 2, 3, 4, 5], + 20000000000000000000, + 20000000000000000000 + )); + + assert_eq!(XykStorage::balance(1, TRADER_ID), 999939999999999999997); + assert_eq!(XykStorage::balance(2, TRADER_ID), 1000000000000000000000); + assert_eq!(XykStorage::balance(3, TRADER_ID), 1000000000000000000000); + assert_eq!(XykStorage::balance(4, TRADER_ID), 1000000000000000000000); + assert_eq!(XykStorage::balance(5, TRADER_ID), 1000000000000000000000); + + assert_eq!(XykStorage::asset_pool((1, 2)), (40040000000000000001, 60000000000000000000)); + assert_eq!(XykStorage::asset_pool((2, 3)), (40000000000000000000, 60000000000000000000)); + assert_eq!(XykStorage::asset_pool((3, 4)), (40000000000000000000, 60000000000000000000)); + assert_eq!(XykStorage::asset_pool((4, 5)), (40000000000000000000, 60000000000000000000)); + assert_eq!(XykStorage::balance(1, XykStorage::account_id()), 40040000000000000001); + assert_eq!(XykStorage::balance(2, XykStorage::account_id()), 100000000000000000000); + assert_eq!(XykStorage::balance(3, XykStorage::account_id()), 100000000000000000000); + assert_eq!(XykStorage::balance(4, XykStorage::account_id()), 100000000000000000000); + assert_eq!(XykStorage::balance(5, XykStorage::account_id()), 60000000000000000000); + + // let assets_swapped_event = crate::mock::RuntimeEvent::XykStorage( + // crate::Event::::MultiSellAssetFailedOnAtomicSwap( + // TRADER_ID, + // vec![1, 2, 3, 6, 5], + // 20000000000000000000, + // Error::::PoolAlreadyExists.into() + // ), + // ); + // + // assert!(System::events().iter().any(|record| record.event == assets_swapped_event)); + }); +} + +#[test] +#[serial] +fn multiswap_sell_not_enough_assets_pay_fees_fails_early_W() { + new_test_ext().execute_with(|| { + System::set_block_number(1); + multi_initialize(); + + assert_eq!(XykStorage::balance(1, TRADER_ID), 1000000000000000000000); + assert_eq!(XykStorage::balance(2, TRADER_ID), 1000000000000000000000); + assert_eq!(XykStorage::balance(3, TRADER_ID), 1000000000000000000000); + assert_eq!(XykStorage::balance(4, TRADER_ID), 1000000000000000000000); + assert_eq!(XykStorage::balance(5, TRADER_ID), 1000000000000000000000); + + assert_eq!(XykStorage::asset_pool((1, 2)), (40000000000000000000, 60000000000000000000)); + assert_eq!(XykStorage::asset_pool((2, 3)), (40000000000000000000, 60000000000000000000)); + assert_eq!(XykStorage::asset_pool((3, 4)), (40000000000000000000, 60000000000000000000)); + assert_eq!(XykStorage::asset_pool((4, 5)), (40000000000000000000, 60000000000000000000)); + assert_eq!(XykStorage::balance(1, XykStorage::account_id()), 40000000000000000000); + assert_eq!(XykStorage::balance(2, XykStorage::account_id()), 100000000000000000000); + assert_eq!(XykStorage::balance(3, XykStorage::account_id()), 100000000000000000000); + assert_eq!(XykStorage::balance(4, XykStorage::account_id()), 100000000000000000000); + assert_eq!(XykStorage::balance(5, XykStorage::account_id()), 60000000000000000000); + + assert_err_ignore_postinfo!( + XykStorage::multiswap_sell_asset( + RuntimeOrigin::signed(TRADER_ID), + vec![1, 2, 3, 4, 5], + 2000000000000000000000000, + 0 + ), + Error::::NotEnoughAssets + ); + + assert_eq!(XykStorage::balance(1, TRADER_ID), 1000000000000000000000); + assert_eq!(XykStorage::balance(2, TRADER_ID), 1000000000000000000000); + assert_eq!(XykStorage::balance(3, TRADER_ID), 1000000000000000000000); + assert_eq!(XykStorage::balance(4, TRADER_ID), 1000000000000000000000); + assert_eq!(XykStorage::balance(5, TRADER_ID), 1000000000000000000000); + + assert_eq!(XykStorage::asset_pool((1, 2)), (40000000000000000000, 60000000000000000000)); + assert_eq!(XykStorage::asset_pool((2, 3)), (40000000000000000000, 60000000000000000000)); + assert_eq!(XykStorage::asset_pool((3, 4)), (40000000000000000000, 60000000000000000000)); + assert_eq!(XykStorage::asset_pool((4, 5)), (40000000000000000000, 60000000000000000000)); + assert_eq!(XykStorage::balance(1, XykStorage::account_id()), 40000000000000000000); + assert_eq!(XykStorage::balance(2, XykStorage::account_id()), 100000000000000000000); + assert_eq!(XykStorage::balance(3, XykStorage::account_id()), 100000000000000000000); + assert_eq!(XykStorage::balance(4, XykStorage::account_id()), 100000000000000000000); + assert_eq!(XykStorage::balance(5, XykStorage::account_id()), 60000000000000000000); + }); +} + +#[test] +#[serial] +fn multiswap_sell_just_enough_assets_pay_fee_but_not_to_swap_W() { + new_test_ext().execute_with(|| { + System::set_block_number(1); + multi_initialize(); + + assert_eq!(XykStorage::balance(1, TRADER_ID), 1000000000000000000000); + assert_eq!(XykStorage::balance(2, TRADER_ID), 1000000000000000000000); + assert_eq!(XykStorage::balance(3, TRADER_ID), 1000000000000000000000); + assert_eq!(XykStorage::balance(4, TRADER_ID), 1000000000000000000000); + assert_eq!(XykStorage::balance(5, TRADER_ID), 1000000000000000000000); + + assert_eq!(XykStorage::asset_pool((1, 2)), (40000000000000000000, 60000000000000000000)); + assert_eq!(XykStorage::asset_pool((2, 3)), (40000000000000000000, 60000000000000000000)); + assert_eq!(XykStorage::asset_pool((3, 4)), (40000000000000000000, 60000000000000000000)); + assert_eq!(XykStorage::asset_pool((4, 5)), (40000000000000000000, 60000000000000000000)); + assert_eq!(XykStorage::balance(1, XykStorage::account_id()), 40000000000000000000); + assert_eq!(XykStorage::balance(2, XykStorage::account_id()), 100000000000000000000); + assert_eq!(XykStorage::balance(3, XykStorage::account_id()), 100000000000000000000); + assert_eq!(XykStorage::balance(4, XykStorage::account_id()), 100000000000000000000); + assert_eq!(XykStorage::balance(5, XykStorage::account_id()), 60000000000000000000); + + assert_ok!(XykStorage::multiswap_sell_asset( + RuntimeOrigin::signed(TRADER_ID), + vec![1, 2, 3, 4, 5], + 2000000000000000000000, + 0 + )); + + assert_eq!(XykStorage::balance(1, TRADER_ID), 993999999999999999997); + assert_eq!(XykStorage::balance(2, TRADER_ID), 1000000000000000000000); + assert_eq!(XykStorage::balance(3, TRADER_ID), 1000000000000000000000); + assert_eq!(XykStorage::balance(4, TRADER_ID), 1000000000000000000000); + assert_eq!(XykStorage::balance(5, TRADER_ID), 1000000000000000000000); + + assert_eq!(XykStorage::asset_pool((1, 2)), (44000000000000000001, 60000000000000000000)); + assert_eq!(XykStorage::asset_pool((2, 3)), (40000000000000000000, 60000000000000000000)); + assert_eq!(XykStorage::asset_pool((3, 4)), (40000000000000000000, 60000000000000000000)); + assert_eq!(XykStorage::asset_pool((4, 5)), (40000000000000000000, 60000000000000000000)); + assert_eq!(XykStorage::balance(1, XykStorage::account_id()), 44000000000000000001); + assert_eq!(XykStorage::balance(2, XykStorage::account_id()), 100000000000000000000); + assert_eq!(XykStorage::balance(3, XykStorage::account_id()), 100000000000000000000); + assert_eq!(XykStorage::balance(4, XykStorage::account_id()), 100000000000000000000); + assert_eq!(XykStorage::balance(5, XykStorage::account_id()), 60000000000000000000); + + assert_event_emitted!(crate::Event::::MultiSwapAssetFailedOnAtomicSwap( + TRADER_ID, + vec![1, 2, 3, 4, 5], + 2000000000000000000000, + module_err(Error::::NotEnoughAssets) + )); + }); +} + +#[test] +#[serial] +fn multiswap_sell_with_two_hops_W() { + new_test_ext().execute_with(|| { + System::set_block_number(1); + multi_initialize(); + + assert_ok!(XykStorage::multiswap_sell_asset( + RuntimeOrigin::signed(TRADER_ID), + vec![1, 2, 3], + 20000000000000000000, + 0 + )); + + let assets_swapped_event = + crate::mock::RuntimeEvent::XykStorage(crate::Event::::AssetsSwapped( + TRADER_ID, + vec![1, 2, 3], + 20000000000000000000, + 19933275601777935827, + )); + + assert!(System::events().iter().any(|record| record.event == assets_swapped_event)); + }); +} + +#[test] +#[serial] +fn multiswap_sell_with_single_hops_W() { + new_test_ext().execute_with(|| { + System::set_block_number(1); + multi_initialize(); + assert_ok!(XykStorage::multiswap_sell_asset( + RuntimeOrigin::signed(TRADER_ID), + vec![1, 2], + 20000000000000000000, + 0 + ),); + }); +} + +#[test] +#[serial] +fn multiswap_sell_same_pool_works_W() { + new_test_ext().execute_with(|| { + System::set_block_number(1); + multi_initialize(); + + assert_eq!(XykStorage::balance(1, TRADER_ID), 1000000000000000000000); + assert_eq!(XykStorage::balance(2, TRADER_ID), 1000000000000000000000); + + assert_eq!(XykStorage::asset_pool((1, 2)), (40000000000000000000, 60000000000000000000)); + assert_eq!(XykStorage::balance(1, XykStorage::account_id()), 40000000000000000000); + assert_eq!(XykStorage::balance(2, XykStorage::account_id()), 100000000000000000000); + + assert_ok!(XykStorage::multiswap_sell_asset( + RuntimeOrigin::signed(TRADER_ID), + vec![1, 2, 1], + 20000000000000000000, + 0 + )); + + assert_eq!(XykStorage::balance(1, TRADER_ID), 999913320173720252676); + assert_eq!(XykStorage::balance(2, TRADER_ID), 1000000000000000000000); + + assert_eq!(XykStorage::asset_pool((1, 2)), (40066679826279747322, 59980040040040040040)); + assert_eq!(XykStorage::balance(1, XykStorage::account_id()), 40066679826279747322); + assert_eq!(XykStorage::balance(2, XykStorage::account_id()), 99980040040040040040); + + let assets_swapped_event = + crate::mock::RuntimeEvent::XykStorage(crate::Event::::AssetsSwapped( + TRADER_ID, + vec![1, 2, 1], + 20000000000000000000, + 19913320173720252676, + )); + + assert!(System::events().iter().any(|record| record.event == assets_swapped_event)); + }); +} + +#[test] +#[serial] +fn multiswap_sell_loop_works_W() { + new_test_ext().execute_with(|| { + System::set_block_number(1); + multi_initialize(); + + assert_eq!(XykStorage::balance(1, TRADER_ID), 1000000000000000000000); + assert_eq!(XykStorage::balance(2, TRADER_ID), 1000000000000000000000); + assert_eq!(XykStorage::balance(3, TRADER_ID), 1000000000000000000000); + + assert_eq!(XykStorage::asset_pool((1, 2)), (40000000000000000000, 60000000000000000000)); + assert_eq!(XykStorage::asset_pool((2, 3)), (40000000000000000000, 60000000000000000000)); + assert_eq!(XykStorage::balance(1, XykStorage::account_id()), 40000000000000000000); + assert_eq!(XykStorage::balance(2, XykStorage::account_id()), 100000000000000000000); + assert_eq!(XykStorage::balance(3, XykStorage::account_id()), 100000000000000000000); + + assert_ok!(XykStorage::multiswap_sell_asset( + RuntimeOrigin::signed(TRADER_ID), + vec![1, 2, 3, 2, 1, 2], + 20000000000000000000, + 0 + )); + + assert_eq!(XykStorage::balance(1, TRADER_ID), 980000000000000000000); + assert_eq!(XykStorage::balance(2, TRADER_ID), 1019787116737807948784); + assert_eq!(XykStorage::balance(3, TRADER_ID), 1000000000000000000000); + + assert_eq!(XykStorage::asset_pool((1, 2)), (59960144443715038028, 40106459299365557069)); + assert_eq!(XykStorage::asset_pool((2, 3)), (40066590593459994181, 59980066724398222064)); + assert_eq!(XykStorage::balance(1, XykStorage::account_id()), 59960144443715038028); + assert_eq!(XykStorage::balance(2, XykStorage::account_id()), 80173049892825551250); + assert_eq!(XykStorage::balance(3, XykStorage::account_id()), 99980066724398222064); + + let assets_swapped_event = + crate::mock::RuntimeEvent::XykStorage(crate::Event::::AssetsSwapped( + TRADER_ID, + vec![1, 2, 3, 2, 1, 2], + 20000000000000000000, + 19787116737807948784, + )); + + assert!(System::events().iter().any(|record| record.event == assets_swapped_event)); + }); +} + +#[test] +#[serial] +fn multiswap_sell_zero_amount_does_not_work_N() { + new_test_ext().execute_with(|| { + System::set_block_number(1); + multi_initialize(); + + assert_err_ignore_postinfo!( + XykStorage::multiswap_sell_asset( + RuntimeOrigin::signed(TRADER_ID), + vec![1, 2, 3], + 0, + 20000000000000000000 + ), + Error::::ZeroAmount + ); + }); +} + +#[test] +#[serial] +fn buy_W() { + new_test_ext().execute_with(|| { + System::set_block_number(1); + initialize(); + + // buying 30000000000000000000 assetId 1 of pool 0 1 + XykStorage::multiswap_buy_asset( + RuntimeOrigin::signed(2), + vec![1, 4], + 30000000000000000000, + 3000000000000000000000, + ) + .unwrap(); + assert_eq!(XykStorage::balance(1, 2), 919879638916750250752); // amount in user acc after selling + assert_eq!(XykStorage::balance(4, 2), 970000000000000000000); // amount in user acc after buying + assert_eq!(XykStorage::asset_pool((1, 4)), (80080240722166499498, 30000000000000000000)); + assert_eq!(XykStorage::balance(1, 2), 919879638916750250752); // amount of asset 0 on account 2 + assert_eq!(XykStorage::balance(4, 2), 970000000000000000000); // amount of asset 1 on account 2 + assert_eq!(XykStorage::balance(1, XykStorage::account_id()), 80080240722166499498); // amount of asset 0 in vault acc after creating pool + assert_eq!(XykStorage::balance(4, XykStorage::account_id()), 30000000000000000000); // amount of asset 1 in vault acc after creating pool + + let assets_swapped_event = + crate::mock::RuntimeEvent::XykStorage(crate::Event::::AssetsSwapped( + 2, + vec![1_u32, 4_u32], + 40120361083249749248, + 30000000000000000000, + )); + + assert!(System::events().iter().any(|record| record.event == assets_swapped_event)); + }); +} + +#[test] +#[serial] +fn buy_W_other_way() { + new_test_ext().execute_with(|| { + initialize(); + + // buying 30000000000000000000 assetId 0 of pool 0 1 + XykStorage::multiswap_buy_asset( + RuntimeOrigin::signed(2), + vec![4, 1], + 30000000000000000000, + 3000000000000000000000, + ) + .unwrap(); + assert_eq!(XykStorage::balance(1, XykStorage::account_id()), 10000000000000000000); // amount of asset 1 in vault acc after creating pool + assert_eq!(XykStorage::balance(4, XykStorage::account_id()), 240361083249749247743); // amount of asset 2 in vault acc after creating pool + assert_eq!(XykStorage::balance(1, 2), 990000000000000000000); // amount in user acc after selling + assert_eq!(XykStorage::balance(4, 2), 759458375125376128385); // amount in user acc after buying + assert_eq!(XykStorage::asset_pool((1, 4)), (10000000000000000000, 240361083249749247743)); + assert_eq!(XykStorage::balance(1, 2), 990000000000000000000); // amount of asset 0 on account 2 + assert_eq!(XykStorage::balance(4, 2), 759458375125376128385); // amount of asset 1 on account 2 + assert_eq!(XykStorage::balance(1, XykStorage::account_id()), 10000000000000000000); // amount of asset 0 in vault acc after creating pool + assert_eq!(XykStorage::balance(4, XykStorage::account_id()), 240361083249749247743); + // amount of asset 1 in vault acc after creating pool + }); +} + +#[test] +#[serial] +fn buy_N_no_such_pool() { + new_test_ext().execute_with(|| { + initialize(); + + // buying 150000 assetId 1 of pool 0 10 (only pool 0 1 exists) + assert_err_ignore_postinfo!( + XykStorage::multiswap_buy_asset(RuntimeOrigin::signed(2), vec![0, 10], 150000, 5000000), + Error::::NoSuchPool, + ); + }); +} + +fn module_err(e: Error) -> ModuleError { + if let DispatchError::Module(module_err) = DispatchError::from(e).stripped() { + module_err + } else { + panic!("cannot convert error"); + } +} + +#[test] +#[serial] +fn buy_N_not_enough_reserve() { + new_test_ext().execute_with(|| { + initialize(); + + // buying 70000000000000000000 assetId 0 of pool 0 1 , only 60000000000000000000 in reserve + assert_err_ignore_postinfo!( + XykStorage::multiswap_buy_asset( + RuntimeOrigin::signed(2), + vec![1, 4], + 70000000000000000000, + 5000000000000000000000 + ), + Error::::NotEnoughReserve, + ); + }); +} + +#[test] +#[serial] +fn buy_N_not_enough_selling_assset() { + new_test_ext().execute_with(|| { + initialize(); + + // buying 59000000000000000000 assetId 1 of pool 0 1 should sell 2.36E+21 assetId 0, only 9.6E+20 in acc + assert_err_ignore_postinfo!( + XykStorage::multiswap_buy_asset( + RuntimeOrigin::signed(2), + vec![1, 4], + 59000000000000000000, + 59000000000000000000000 + ), + Error::::NotEnoughAssets, + ); + }); +} + +#[test] +#[serial] +fn buy_W_insufficient_input_amount() { + new_test_ext().execute_with(|| { + initialize(); + + let mut expected_events = + vec![Event::PoolCreated(2, 1, 40000000000000000000, 4, 60000000000000000000)]; + assert_eq_events!(expected_events.clone()); + + let input_balance_before = XykStorage::balance(1, 2); + let output_balance_before = XykStorage::balance(4, 2); + + // buying 150000 liquidity assetId 1 of pool 0 1 + assert_ok!(XykStorage::multiswap_buy_asset( + RuntimeOrigin::signed(2), + vec![1, 4], + 150000, + 10 + )); + let mut new_events_0 = + vec![Event::BuyAssetFailedDueToSlippage(2, 1, 100301, 4, 150000, 10)]; + + expected_events.append(&mut new_events_0); + assert_eq_events!(expected_events.clone()); + + let input_balance_after = XykStorage::balance(1, 2); + let output_balance_after = XykStorage::balance(4, 2); + + assert_ne!(input_balance_before, input_balance_after); + assert_eq!(output_balance_before, output_balance_after); + }); +} + +#[test] +#[serial] +fn buy_N_insufficient_input_amount_inner_function_error_upon_bad_slippage() { + new_test_ext().execute_with(|| { + initialize(); + + // buying 150000 liquidity assetId 1 of pool 0 1 + assert_err!( + >::buy_asset( + 2, 1, 4, 150000, 10, true + ), + Error::::InsufficientInputAmount, + ); + }); +} + +#[test] +#[serial] +fn buy_W_insufficient_input_amount_inner_function_NO_error_upon_bad_slippage() { + new_test_ext().execute_with(|| { + initialize(); + + // buying 150000 liquidity assetId 1 of pool 0 1 + assert_ok!(>::buy_asset( + 2, 1, 4, 150000, 10, false + )); + }); +} + +#[test] +#[serial] +fn buy_N_zero_amount() { + new_test_ext().execute_with(|| { + initialize(); + + assert_err_ignore_postinfo!( + XykStorage::multiswap_buy_asset(RuntimeOrigin::signed(2), vec![1, 4], 0, 0), + Error::::ZeroAmount, + ); // buying 0 assetId 0 of pool 0 1 + }); +} + +#[test] +#[serial] +fn multiswap_buy_W() { + new_test_ext().execute_with(|| { + System::set_block_number(1); + multi_initialize(); + + assert_eq!(XykStorage::balance(1, TRADER_ID), 1000000000000000000000); + assert_eq!(XykStorage::balance(2, TRADER_ID), 1000000000000000000000); + assert_eq!(XykStorage::balance(3, TRADER_ID), 1000000000000000000000); + assert_eq!(XykStorage::balance(4, TRADER_ID), 1000000000000000000000); + assert_eq!(XykStorage::balance(5, TRADER_ID), 1000000000000000000000); + + assert_eq!(XykStorage::asset_pool((1, 2)), (40000000000000000000, 60000000000000000000)); + assert_eq!(XykStorage::asset_pool((2, 3)), (40000000000000000000, 60000000000000000000)); + assert_eq!(XykStorage::asset_pool((3, 4)), (40000000000000000000, 60000000000000000000)); + assert_eq!(XykStorage::asset_pool((4, 5)), (40000000000000000000, 60000000000000000000)); + assert_eq!(XykStorage::balance(1, XykStorage::account_id()), 40000000000000000000); + assert_eq!(XykStorage::balance(2, XykStorage::account_id()), 100000000000000000000); + assert_eq!(XykStorage::balance(3, XykStorage::account_id()), 100000000000000000000); + assert_eq!(XykStorage::balance(4, XykStorage::account_id()), 100000000000000000000); + assert_eq!(XykStorage::balance(5, XykStorage::account_id()), 60000000000000000000); + + assert_ok!(XykStorage::multiswap_buy_asset( + RuntimeOrigin::signed(TRADER_ID), + vec![1, 2, 3, 4, 5], + 20000000000000000000, + 200000000000000000000 + )); + + assert_eq!(XykStorage::balance(1, TRADER_ID), 979503362184986098460); + assert_eq!(XykStorage::balance(2, TRADER_ID), 1000000000000000000000); + assert_eq!(XykStorage::balance(3, TRADER_ID), 1000000000000000000000); + assert_eq!(XykStorage::balance(4, TRADER_ID), 1000000000000000000000); + assert_eq!(XykStorage::balance(5, TRADER_ID), 1020000000000000000000); + + assert_eq!(XykStorage::asset_pool((1, 2)), (60476141177198887638, 39711990180100104523)); + assert_eq!(XykStorage::asset_pool((2, 3)), (60267721810079995581, 39849140591034781894)); + assert_eq!(XykStorage::asset_pool((3, 4)), (60130708549556252886, 39939819458375125376)); + assert_eq!(XykStorage::asset_pool((4, 5)), (60040120361083249748, 40000000000000000000)); + assert_eq!(XykStorage::balance(1, XykStorage::account_id()), 60476141177198887638); + assert_eq!(XykStorage::balance(2, XykStorage::account_id()), 99979711990180100104); + assert_eq!(XykStorage::balance(3, XykStorage::account_id()), 99979849140591034780); + assert_eq!(XykStorage::balance(4, XykStorage::account_id()), 99979939819458375124); + assert_eq!(XykStorage::balance(5, XykStorage::account_id()), 40000000000000000000); + + let assets_swapped_event = + crate::mock::RuntimeEvent::XykStorage(crate::Event::::AssetsSwapped( + TRADER_ID, + vec![1, 2, 3, 4, 5], + 20496637815013901540, + 20000000000000000000, + )); + + assert!(System::events().iter().any(|record| record.event == assets_swapped_event)); + }); +} + +#[test] +#[serial] +fn multiswap_buy_bad_slippage_charges_fee_W() { + new_test_ext().execute_with(|| { + System::set_block_number(1); + multi_initialize(); + + assert_eq!(XykStorage::balance(1, TRADER_ID), 1000000000000000000000); + assert_eq!(XykStorage::balance(2, TRADER_ID), 1000000000000000000000); + assert_eq!(XykStorage::balance(3, TRADER_ID), 1000000000000000000000); + assert_eq!(XykStorage::balance(4, TRADER_ID), 1000000000000000000000); + assert_eq!(XykStorage::balance(5, TRADER_ID), 1000000000000000000000); + + assert_eq!(XykStorage::asset_pool((1, 2)), (40000000000000000000, 60000000000000000000)); + assert_eq!(XykStorage::asset_pool((2, 3)), (40000000000000000000, 60000000000000000000)); + assert_eq!(XykStorage::asset_pool((3, 4)), (40000000000000000000, 60000000000000000000)); + assert_eq!(XykStorage::asset_pool((4, 5)), (40000000000000000000, 60000000000000000000)); + assert_eq!(XykStorage::balance(1, XykStorage::account_id()), 40000000000000000000); + assert_eq!(XykStorage::balance(2, XykStorage::account_id()), 100000000000000000000); + assert_eq!(XykStorage::balance(3, XykStorage::account_id()), 100000000000000000000); + assert_eq!(XykStorage::balance(4, XykStorage::account_id()), 100000000000000000000); + assert_eq!(XykStorage::balance(5, XykStorage::account_id()), 60000000000000000000); + + assert_ok!(XykStorage::multiswap_buy_asset( + RuntimeOrigin::signed(TRADER_ID), + vec![1, 2, 3, 4, 5], + 20000000000000000000, + 200000000000000000 + )); + + assert_eq!(XykStorage::balance(1, TRADER_ID), 999999399999999999997); + assert_eq!(XykStorage::balance(2, TRADER_ID), 1000000000000000000000); + assert_eq!(XykStorage::balance(3, TRADER_ID), 1000000000000000000000); + assert_eq!(XykStorage::balance(4, TRADER_ID), 1000000000000000000000); + assert_eq!(XykStorage::balance(5, TRADER_ID), 1000000000000000000000); + + assert_eq!(XykStorage::asset_pool((1, 2)), (40000400000000000001, 60000000000000000000)); + assert_eq!(XykStorage::asset_pool((2, 3)), (40000000000000000000, 60000000000000000000)); + assert_eq!(XykStorage::asset_pool((3, 4)), (40000000000000000000, 60000000000000000000)); + assert_eq!(XykStorage::asset_pool((4, 5)), (40000000000000000000, 60000000000000000000)); + assert_eq!(XykStorage::balance(1, XykStorage::account_id()), 40000400000000000001); + assert_eq!(XykStorage::balance(2, XykStorage::account_id()), 100000000000000000000); + assert_eq!(XykStorage::balance(3, XykStorage::account_id()), 100000000000000000000); + assert_eq!(XykStorage::balance(4, XykStorage::account_id()), 100000000000000000000); + assert_eq!(XykStorage::balance(5, XykStorage::account_id()), 60000000000000000000); + + assert_event_emitted!(crate::Event::::MultiSwapAssetFailedOnAtomicSwap( + TRADER_ID, + vec![1, 2, 3, 4, 5], + 20000000000000000000, + module_err(Error::::InsufficientInputAmount) + )); + }); +} + +#[test] +#[serial] +fn multiswap_buy_bad_atomic_swap_charges_fee_W() { + new_test_ext().execute_with(|| { + System::set_block_number(1); + multi_initialize(); + + assert_eq!(XykStorage::balance(1, TRADER_ID), 1000000000000000000000); + assert_eq!(XykStorage::balance(2, TRADER_ID), 1000000000000000000000); + assert_eq!(XykStorage::balance(3, TRADER_ID), 1000000000000000000000); + assert_eq!(XykStorage::balance(4, TRADER_ID), 1000000000000000000000); + assert_eq!(XykStorage::balance(5, TRADER_ID), 1000000000000000000000); + + assert_eq!(XykStorage::asset_pool((1, 2)), (40000000000000000000, 60000000000000000000)); + assert_eq!(XykStorage::asset_pool((2, 3)), (40000000000000000000, 60000000000000000000)); + assert_eq!(XykStorage::asset_pool((3, 4)), (40000000000000000000, 60000000000000000000)); + assert_eq!(XykStorage::asset_pool((4, 5)), (40000000000000000000, 60000000000000000000)); + assert_eq!(XykStorage::balance(1, XykStorage::account_id()), 40000000000000000000); + assert_eq!(XykStorage::balance(2, XykStorage::account_id()), 100000000000000000000); + assert_eq!(XykStorage::balance(3, XykStorage::account_id()), 100000000000000000000); + assert_eq!(XykStorage::balance(4, XykStorage::account_id()), 100000000000000000000); + assert_eq!(XykStorage::balance(5, XykStorage::account_id()), 60000000000000000000); + + assert_ok!(XykStorage::multiswap_buy_asset( + RuntimeOrigin::signed(TRADER_ID), + vec![1, 2, 3, 4, 5], + 20000000000000000000, + 200000000000000000 + )); + + assert_eq!(XykStorage::balance(1, TRADER_ID), 999999399999999999997); + assert_eq!(XykStorage::balance(2, TRADER_ID), 1000000000000000000000); + assert_eq!(XykStorage::balance(3, TRADER_ID), 1000000000000000000000); + assert_eq!(XykStorage::balance(4, TRADER_ID), 1000000000000000000000); + assert_eq!(XykStorage::balance(5, TRADER_ID), 1000000000000000000000); + + assert_eq!(XykStorage::asset_pool((1, 2)), (40000400000000000001, 60000000000000000000)); + assert_eq!(XykStorage::asset_pool((2, 3)), (40000000000000000000, 60000000000000000000)); + assert_eq!(XykStorage::asset_pool((3, 4)), (40000000000000000000, 60000000000000000000)); + assert_eq!(XykStorage::asset_pool((4, 5)), (40000000000000000000, 60000000000000000000)); + assert_eq!(XykStorage::balance(1, XykStorage::account_id()), 40000400000000000001); + assert_eq!(XykStorage::balance(2, XykStorage::account_id()), 100000000000000000000); + assert_eq!(XykStorage::balance(3, XykStorage::account_id()), 100000000000000000000); + assert_eq!(XykStorage::balance(4, XykStorage::account_id()), 100000000000000000000); + assert_eq!(XykStorage::balance(5, XykStorage::account_id()), 60000000000000000000); + + // let assets_swapped_event = crate::mock::RuntimeEvent::XykStorage( + // crate::Event::::MultiBuyAssetFailedOnAtomicSwap( + // TRADER_ID, + // vec![1, 2, 3, 6, 5], + // 20000000000000000000, + // ), + // ); + // + // assert!(System::events().iter().any(|record| record.event == assets_swapped_event)); + }); +} + +#[test] +#[serial] +fn multiswap_buy_not_enough_assets_pay_fees_fails_early_W() { + new_test_ext().execute_with(|| { + System::set_block_number(1); + multi_initialize(); + + assert_eq!(XykStorage::balance(1, TRADER_ID), 1000000000000000000000); + assert_eq!(XykStorage::balance(2, TRADER_ID), 1000000000000000000000); + assert_eq!(XykStorage::balance(3, TRADER_ID), 1000000000000000000000); + assert_eq!(XykStorage::balance(4, TRADER_ID), 1000000000000000000000); + assert_eq!(XykStorage::balance(5, TRADER_ID), 1000000000000000000000); + + assert_eq!(XykStorage::asset_pool((1, 2)), (40000000000000000000, 60000000000000000000)); + assert_eq!(XykStorage::asset_pool((2, 3)), (40000000000000000000, 60000000000000000000)); + assert_eq!(XykStorage::asset_pool((3, 4)), (40000000000000000000, 60000000000000000000)); + assert_eq!(XykStorage::asset_pool((4, 5)), (40000000000000000000, 60000000000000000000)); + assert_eq!(XykStorage::balance(1, XykStorage::account_id()), 40000000000000000000); + assert_eq!(XykStorage::balance(2, XykStorage::account_id()), 100000000000000000000); + assert_eq!(XykStorage::balance(3, XykStorage::account_id()), 100000000000000000000); + assert_eq!(XykStorage::balance(4, XykStorage::account_id()), 100000000000000000000); + assert_eq!(XykStorage::balance(5, XykStorage::account_id()), 60000000000000000000); + + assert_err_ignore_postinfo!( + XykStorage::multiswap_buy_asset( + RuntimeOrigin::signed(TRADER_ID), + vec![1, 2, 3, 4, 5], + 2000000000000000000000000, + 2000000000000000000000000 + ), + Error::::NotEnoughAssets + ); + + assert_eq!(XykStorage::balance(1, TRADER_ID), 1000000000000000000000); + assert_eq!(XykStorage::balance(2, TRADER_ID), 1000000000000000000000); + assert_eq!(XykStorage::balance(3, TRADER_ID), 1000000000000000000000); + assert_eq!(XykStorage::balance(4, TRADER_ID), 1000000000000000000000); + assert_eq!(XykStorage::balance(5, TRADER_ID), 1000000000000000000000); + + assert_eq!(XykStorage::asset_pool((1, 2)), (40000000000000000000, 60000000000000000000)); + assert_eq!(XykStorage::asset_pool((2, 3)), (40000000000000000000, 60000000000000000000)); + assert_eq!(XykStorage::asset_pool((3, 4)), (40000000000000000000, 60000000000000000000)); + assert_eq!(XykStorage::asset_pool((4, 5)), (40000000000000000000, 60000000000000000000)); + assert_eq!(XykStorage::balance(1, XykStorage::account_id()), 40000000000000000000); + assert_eq!(XykStorage::balance(2, XykStorage::account_id()), 100000000000000000000); + assert_eq!(XykStorage::balance(3, XykStorage::account_id()), 100000000000000000000); + assert_eq!(XykStorage::balance(4, XykStorage::account_id()), 100000000000000000000); + assert_eq!(XykStorage::balance(5, XykStorage::account_id()), 60000000000000000000); + }); +} + +#[test] +#[serial] +fn multiswap_buy_just_enough_assets_pay_fee_but_not_to_swap_W() { + new_test_ext().execute_with(|| { + System::set_block_number(1); + multi_initialize(); + + assert_ok!(::Currency::transfer( + 1u32.into(), + &TRADER_ID, + &DUMMY_USER_ID, + (1000000000000000000000u128 - 1000000u128).into(), + ExistenceRequirement::KeepAlive + )); + + assert_eq!(XykStorage::balance(1, TRADER_ID), 1000000); + assert_eq!(XykStorage::balance(2, TRADER_ID), 1000000000000000000000); + assert_eq!(XykStorage::balance(3, TRADER_ID), 1000000000000000000000); + assert_eq!(XykStorage::balance(4, TRADER_ID), 1000000000000000000000); + assert_eq!(XykStorage::balance(5, TRADER_ID), 1000000000000000000000); + + assert_eq!(XykStorage::asset_pool((1, 2)), (40000000000000000000, 60000000000000000000)); + assert_eq!(XykStorage::asset_pool((2, 3)), (40000000000000000000, 60000000000000000000)); + assert_eq!(XykStorage::asset_pool((3, 4)), (40000000000000000000, 60000000000000000000)); + assert_eq!(XykStorage::asset_pool((4, 5)), (40000000000000000000, 60000000000000000000)); + assert_eq!(XykStorage::balance(1, XykStorage::account_id()), 40000000000000000000); + assert_eq!(XykStorage::balance(2, XykStorage::account_id()), 100000000000000000000); + assert_eq!(XykStorage::balance(3, XykStorage::account_id()), 100000000000000000000); + assert_eq!(XykStorage::balance(4, XykStorage::account_id()), 100000000000000000000); + assert_eq!(XykStorage::balance(5, XykStorage::account_id()), 60000000000000000000); + + assert_ok!(XykStorage::multiswap_buy_asset( + RuntimeOrigin::signed(TRADER_ID), + vec![1, 2, 3, 4, 5], + 100000000, + 20000000 + )); + + assert_eq!(XykStorage::balance(1, TRADER_ID), 939997); + assert_eq!(XykStorage::balance(2, TRADER_ID), 1000000000000000000000); + assert_eq!(XykStorage::balance(3, TRADER_ID), 1000000000000000000000); + assert_eq!(XykStorage::balance(4, TRADER_ID), 1000000000000000000000); + assert_eq!(XykStorage::balance(5, TRADER_ID), 1000000000000000000000); + + assert_eq!(XykStorage::asset_pool((1, 2)), (40000000000000040001, 60000000000000000000)); + assert_eq!(XykStorage::asset_pool((2, 3)), (40000000000000000000, 60000000000000000000)); + assert_eq!(XykStorage::asset_pool((3, 4)), (40000000000000000000, 60000000000000000000)); + assert_eq!(XykStorage::asset_pool((4, 5)), (40000000000000000000, 60000000000000000000)); + assert_eq!(XykStorage::balance(1, XykStorage::account_id()), 40000000000000040001); + assert_eq!(XykStorage::balance(2, XykStorage::account_id()), 100000000000000000000); + assert_eq!(XykStorage::balance(3, XykStorage::account_id()), 100000000000000000000); + assert_eq!(XykStorage::balance(4, XykStorage::account_id()), 100000000000000000000); + assert_eq!(XykStorage::balance(5, XykStorage::account_id()), 60000000000000000000); + + assert_event_emitted!(Event::::MultiSwapAssetFailedOnAtomicSwap( + TRADER_ID, + vec![1, 2, 3, 4, 5], + 100000000, + module_err(Error::::NotEnoughAssets) + )); + }); +} + +#[test] +#[serial] +fn multiswap_buy_with_two_hops_W() { + new_test_ext().execute_with(|| { + System::set_block_number(1); + multi_initialize(); + + assert_ok!(XykStorage::multiswap_buy_asset( + RuntimeOrigin::signed(TRADER_ID), + vec![1, 2, 3], + 20000000000000000000, + 2000000000000000000000 + )); + + let assets_swapped_event = + crate::mock::RuntimeEvent::XykStorage(crate::Event::::AssetsSwapped( + TRADER_ID, + vec![1, 2, 3], + 20150859408965218106, + 20000000000000000000, + )); + + assert!(System::events().iter().any(|record| record.event == assets_swapped_event)); + }); +} + +#[test] +#[serial] +fn multiswap_buy_with_single_hops_W() { + new_test_ext().execute_with(|| { + System::set_block_number(1); + multi_initialize(); + assert_ok!(XykStorage::multiswap_buy_asset( + RuntimeOrigin::signed(TRADER_ID), + vec![2, 1], + 20000000000000000000, + 40000000000000000000, + ),); + }); +} + +#[test] +#[serial] +fn multiswap_buy_same_pool_does_not_work_N() { + new_test_ext().execute_with(|| { + System::set_block_number(1); + multi_initialize(); + + assert_err_ignore_postinfo!( + XykStorage::multiswap_buy_asset( + RuntimeOrigin::signed(TRADER_ID), + vec![1, 2, 1], + 20000000000000000000, + 20000000000000000000 + ), + Error::::MultiBuyAssetCantHaveSamePoolAtomicSwaps + ); + }); +} + +#[test] +#[serial] +fn multiswap_buy_loop_does_not_work_N() { + new_test_ext().execute_with(|| { + System::set_block_number(1); + multi_initialize(); + + assert_err_ignore_postinfo!( + XykStorage::multiswap_buy_asset( + RuntimeOrigin::signed(TRADER_ID), + vec![1, 2, 3, 2, 1, 2], + 20000000000000000000, + 20000000000000000000 + ), + Error::::MultiBuyAssetCantHaveSamePoolAtomicSwaps + ); + }); +} + +#[test] +#[serial] +fn multiswap_buy_zero_amount_does_not_work_N() { + new_test_ext().execute_with(|| { + System::set_block_number(1); + multi_initialize(); + + assert_err_ignore_postinfo!( + XykStorage::multiswap_buy_asset( + RuntimeOrigin::signed(TRADER_ID), + vec![1, 2, 3], + 0, + 20000000000000000000 + ), + Error::::ZeroAmount + ); + assert_err_ignore_postinfo!( + XykStorage::multiswap_buy_asset( + RuntimeOrigin::signed(TRADER_ID), + vec![1, 2, 3], + 20000000000000000000, + 0 + ), + Error::::ZeroAmount + ); + }); +} + +#[test] +#[serial] +fn burn_all_liq_and_mint_it_again() { + new_test_ext().execute_with(|| { + initialize(); + + let (asset_value_1, asset_value_4) = XykStorage::asset_pool((1, 4)); + let liq_token_id = XykStorage::liquidity_asset((1, 4)); + let total_issuance_of_liq_amount: u128 = + ::Currency::total_issuance(liq_token_id.unwrap()).into(); + + //burn half of the liquidity + XykStorage::burn_liquidity( + RuntimeOrigin::signed(2), + 1, + 4, + total_issuance_of_liq_amount / 2, + ) + .unwrap(); + + let (divided_asset_value_1, divided_asset_value_4) = XykStorage::asset_pool((1, 4)); + let divided_total_issuance_of_liq_amount: u128 = + ::Currency::total_issuance(liq_token_id.unwrap()).into(); + + assert_eq!(divided_asset_value_1, asset_value_1 / 2); + assert_eq!(divided_asset_value_4, asset_value_4 / 2); + assert_eq!(divided_total_issuance_of_liq_amount, total_issuance_of_liq_amount / 2); + + //burn second half of liquidity + XykStorage::burn_liquidity( + RuntimeOrigin::signed(2), + 1, + 4, + total_issuance_of_liq_amount / 2, + ) + .unwrap(); + + let (empty_asset_value_1, empty_asset_value_4) = XykStorage::asset_pool((1, 4)); + let empty_total_issuance_of_liq_amount: u128 = + ::Currency::total_issuance(liq_token_id.unwrap()).into(); + + assert_eq!(empty_asset_value_1, 0); + assert_eq!(empty_asset_value_4, 0); + assert_eq!(empty_total_issuance_of_liq_amount, 0); + + // we will try to create a new pool but it needs to fail. + assert_err!( + XykStorage::create_pool(RuntimeOrigin::signed(2), 1, asset_value_1, 4, asset_value_4), + Error::::PoolAlreadyExists, + ); + + // we will try to sell assets but it needs to fail + let user_assets_1_value = XykStorage::balance(1, DUMMY_USER_ID); + let user_assets_4_value = XykStorage::balance(4, DUMMY_USER_ID); + + assert_eq!(user_assets_1_value, 1000000000000000000000); + assert_eq!(user_assets_4_value, 1000000000000000000000); + + // selling the assets should fail due to empty pools + assert_err_ignore_postinfo!( + XykStorage::multiswap_sell_asset( + RuntimeOrigin::signed(DUMMY_USER_ID), + vec![1, 4], + 20000000000000000000, + 1 + ), + Error::::PoolIsEmpty, + ); + + // selling the assets should fail also for multiswap due to empty pools + assert_err_ignore_postinfo!( + XykStorage::multiswap_sell_asset( + RuntimeOrigin::signed(DUMMY_USER_ID), + vec![1, 4, 1, 4], + 20000000000000000000, + 1 + ), + Error::::PoolIsEmpty, + ); + + // buy asset should fail due to empty pools + assert_err_ignore_postinfo!( + XykStorage::multiswap_buy_asset( + RuntimeOrigin::signed(DUMMY_USER_ID), + vec![1, 4], + 20000000000000000000, + 1 + ), + Error::::PoolIsEmpty, + ); + + // buy asset should fail also for multiswap due to empty pools + assert_err_ignore_postinfo!( + XykStorage::multiswap_buy_asset( + RuntimeOrigin::signed(DUMMY_USER_ID), + vec![1, 4, 1, 4], + 20000000000000000000, + 1 + ), + Error::::PoolIsEmpty, + ); + + // calculate_buy_price_id should fail as pool is empty + assert_err!(XykStorage::calculate_buy_price_id(1, 4, 20000), Error::::PoolIsEmpty,); + + // calculate_sell_price_id should fail as pool is empty + assert_err!(XykStorage::calculate_sell_price_id(1, 4, 20000), Error::::PoolIsEmpty,); + + // get_burn_amount should fail as pool is empty + assert_err!(XykStorage::get_burn_amount(1, 4, 20000), Error::::PoolIsEmpty,); + + let user_assets_1_value_after_sell = XykStorage::balance(1, DUMMY_USER_ID); + let user_assets_4_value_after_sell = XykStorage::balance(4, DUMMY_USER_ID); + + //check that no asset sold + assert_eq!(user_assets_1_value_after_sell, 1000000000000000000000); + assert_eq!(user_assets_4_value_after_sell, 1000000000000000000000); + + // minting liq again and checking if the liq. asset is generated + let _ = XykStorage::mint_liquidity( + RuntimeOrigin::signed(2), + 1, + 4, + asset_value_1, + asset_value_4, + ); + + let liq_token_id_after_burn_and_mint = XykStorage::liquidity_asset((1, 4)); + + assert_eq!(liq_token_id, liq_token_id_after_burn_and_mint); + + let total_issuance_after_burn_and_mint: u128 = + ::Currency::total_issuance(liq_token_id_after_burn_and_mint.unwrap()) + .into(); + + assert_eq!(total_issuance_of_liq_amount, total_issuance_after_burn_and_mint); + + let (asset_value_1_after_burn_and_mint, asset_value_4_after_burn_and_mint) = + XykStorage::asset_pool((1, 4)); + + assert_eq!(asset_value_1, asset_value_1_after_burn_and_mint); + assert_eq!(asset_value_4, asset_value_4_after_burn_and_mint); + }); +} + +#[test] +#[serial] +fn mint_W() { + new_test_ext().execute_with(|| { + initialize(); + // minting pool 0 1 with 20000000000000000000 assetId 0 + XykStorage::mint_liquidity( + RuntimeOrigin::signed(2), + 1, + 4, + 20000000000000000000, + 30000000000000000001, + ) + .unwrap(); + + assert_eq!(XykStorage::total_supply(5), 75000000000000000000); // total liquidity assets + assert_eq!(XykStorage::balance(5, 2), 75000000000000000000); // amount of liquidity assets owned by user by creating pool and minting + assert_eq!(XykStorage::asset_pool((1, 4)), (60000000000000000000, 90000000000000000001)); + assert_eq!(XykStorage::balance(1, 2), 940000000000000000000); // amount of asset 1 in user acc after minting + assert_eq!(XykStorage::balance(4, 2), 909999999999999999999); // amount of asset 2 in user acc after minting + assert_eq!(XykStorage::balance(1, XykStorage::account_id()), 60000000000000000000); // amount of asset 0 in vault acc after creating pool + assert_eq!(XykStorage::balance(4, XykStorage::account_id()), 90000000000000000001); // amount of asset 1 in vault acc after creating pool + let liquidity_minted_event = + crate::mock::RuntimeEvent::XykStorage(crate::Event::::LiquidityMinted( + 2, + 1, + 20000000000000000000, + 4, + 30000000000000000001, + 5, + 25000000000000000000, + )); + + assert!(System::events().iter().any(|record| record.event == liquidity_minted_event)); + }); +} + +#[test] +#[serial] +fn mint_W_other_way() { + new_test_ext().execute_with(|| { + initialize(); + // minting pool 0 1 with 30000000000000000000 assetId 1 + XykStorage::mint_liquidity( + RuntimeOrigin::signed(2), + 4, + 1, + 30000000000000000000, + 300000000000000000000, + ) + .unwrap(); + + assert_eq!(XykStorage::total_supply(5), 75000000000000000000); // total liquidity assets + assert_eq!(XykStorage::balance(5, 2), 75000000000000000000); // amount of liquidity assets owned by user by creating pool and minting + assert_eq!(XykStorage::asset_pool((1, 4)), (60000000000000000001, 90000000000000000000)); + assert_eq!(XykStorage::balance(1, 2), 939999999999999999999); // amount of asset 0 in user acc after minting + assert_eq!(XykStorage::balance(4, 2), 910000000000000000000); // amount of asset 1 in user acc after minting + assert_eq!(XykStorage::balance(1, XykStorage::account_id()), 60000000000000000001); // amount of asset 0 in vault acc after creating pool + assert_eq!(XykStorage::balance(4, XykStorage::account_id()), 90000000000000000000); + // amount of asset 1 in vault acc after creating pool + }); +} + +#[test] +#[serial] +fn mint_N_no_such_pool() { + new_test_ext().execute_with(|| { + initialize(); + assert_err!( + XykStorage::mint_liquidity(RuntimeOrigin::signed(2), 0, 10, 250000, 250000), + Error::::NoSuchPool, + ); // minting pool 0 10 with 250000 assetId 0 (only pool 0 1 exists) + }); +} + +#[test] +#[serial] +fn mint_N_not_enough_first_asset() { + new_test_ext().execute_with(|| { + initialize(); + assert_err!( + XykStorage::mint_liquidity( + RuntimeOrigin::signed(2), + 1, + 4, + 1000000000000000000000, + 10000000000000000000000 + ), + Error::::NotEnoughAssets, + ); // minting pool 0 1 with 1000000000000000000000 assetId 0 (user has only 960000000000000000000) + }); +} + +#[test] +#[serial] +fn mint_N_not_enough_second_asset() { + new_test_ext().execute_with(|| { + initialize(); + assert_err!( + XykStorage::mint_liquidity( + RuntimeOrigin::signed(2), + 4, + 1, + 1000000000000000000000, + 10000000000000000000000, + ), + Error::::NotEnoughAssets, + ); // minting pool 0 1 with 1000000000000000000000 assetId 1 (user has only 940000000000000000000) + }); +} + +#[test] +#[serial] +fn min_N_zero_amount() { + new_test_ext().execute_with(|| { + initialize(); + assert_err!( + XykStorage::mint_liquidity(RuntimeOrigin::signed(2), 1, 4, 0, 10), + Error::::ZeroAmount, + ); // minting pool 0 1 with 0 assetId 1 + }); +} + +#[test] +#[serial] +fn mint_N_second_asset_amount_exceeded_expectations() { + new_test_ext().execute_with(|| { + initialize(); + assert_err!( + XykStorage::mint_liquidity(RuntimeOrigin::signed(2), 1, 4, 250000, 10), + Error::::SecondAssetAmountExceededExpectations, + ); // minting pool 0 10 with 250000 assetId 0 (only pool 0 1 exists) + }); +} + +#[test] +#[serial] +fn burn_W() { + new_test_ext().execute_with(|| { + initialize(); + XykStorage::burn_liquidity(RuntimeOrigin::signed(2), 1, 4, 25000000000000000000).unwrap(); // burning 20000000000000000000 asset 0 of pool 0 1 + + assert_eq!(XykStorage::balance(5, 2), 25000000000000000000); // amount of liquidity assets owned by user by creating pool and burning + assert_eq!(XykStorage::asset_pool((1, 4)), (20000000000000000000, 30000000000000000000)); + assert_eq!(XykStorage::balance(1, 2), 980000000000000000000); // amount of asset 0 in user acc after burning + assert_eq!(XykStorage::balance(4, 2), 970000000000000000000); // amount of asset 1 in user acc after burning + assert_eq!(XykStorage::balance(1, XykStorage::account_id()), 20000000000000000000); // amount of asset 0 in vault acc after creating pool + assert_eq!(XykStorage::balance(4, XykStorage::account_id()), 30000000000000000000); // amount of asset 1 in vault acc after creating pool + + let liquidity_burned = + crate::mock::RuntimeEvent::XykStorage(crate::Event::::LiquidityBurned( + 2, + 1, + 20000000000000000000, + 4, + 30000000000000000000, + 5, + 25000000000000000000, + )); + + assert!(System::events().iter().any(|record| record.event == liquidity_burned)); + }); +} + +#[test] +#[serial] +fn burn_W_other_way() { + new_test_ext().execute_with(|| { + initialize(); + XykStorage::burn_liquidity(RuntimeOrigin::signed(2), 4, 1, 25000000000000000000).unwrap(); // burning 30000000000000000000 asset 1 of pool 0 1 + + assert_eq!(XykStorage::balance(5, 2), 25000000000000000000); // amount of liquidity assets owned by user by creating pool and burning + assert_eq!(XykStorage::asset_pool((1, 4)), (20000000000000000000, 30000000000000000000)); + assert_eq!(XykStorage::balance(1, 2), 980000000000000000000); // amount of asset 0 in user acc after burning + assert_eq!(XykStorage::balance(4, 2), 970000000000000000000); // amount of asset 1 in user acc after burning + assert_eq!(XykStorage::balance(1, XykStorage::account_id()), 20000000000000000000); // amount of asset 0 in vault acc after creating pool + assert_eq!(XykStorage::balance(4, XykStorage::account_id()), 30000000000000000000); + // amount of asset 1 in vault acc after creating pool + }); +} + +#[test] +#[serial] +fn burn_N_not_enough_liquidity_asset() { + new_test_ext().execute_with(|| { + initialize(); + // burning pool 0 1 with 500000000000000000000 liquidity asset amount (user has only 100000000000000000000 liquidity asset amount) + assert_err!( + XykStorage::burn_liquidity(RuntimeOrigin::signed(2), 1, 4, 500000000000000000000,), + Error::::NotEnoughAssets, + ); + }); +} + +#[test] +#[serial] +fn burn_N_no_such_pool() { + new_test_ext().execute_with(|| { + initialize(); + // burning pool 0 10 with 250000 assetId 0 (only pool 0 1 exists) + assert_err!( + XykStorage::burn_liquidity(RuntimeOrigin::signed(2), 0, 10, 250000,), + Error::::NoSuchPool, + ); + }); +} + +#[test] +#[serial] +fn burn_N_zero_amount() { + new_test_ext().execute_with(|| { + initialize(); + assert_err!( + XykStorage::burn_liquidity(RuntimeOrigin::signed(2), 1, 4, 0,), + Error::::ZeroAmount, + ); // burning pool 0 1 with 0 assetId 1 + }); +} + +#[test] +#[serial] +fn buy_assets_with_small_expected_amount_does_not_cause_panic() { + new_test_ext().execute_with(|| { + initialize(); + let first_token_balance = XykStorage::balance(1, DUMMY_USER_ID); + let _ = XykStorage::multiswap_buy_asset( + RuntimeOrigin::signed(2), + vec![1, 4], + 1, + first_token_balance, + ); + }); +} + +#[test] +#[serial] +fn swaps_are_annotated_as_PaysNo() { + new_test_ext().execute_with(|| { + let calls = [ + mock::RuntimeCall::XykStorage(Call::sell_asset { + sold_asset_id: 0u32, + bought_asset_id: 1u32, + sold_asset_amount: 10u128, + min_amount_out: 1u128, + }), + mock::RuntimeCall::XykStorage(Call::buy_asset { + sold_asset_id: 0u32, + bought_asset_id: 1u32, + bought_asset_amount: 10u128, + max_amount_in: 1u128, + }), + mock::RuntimeCall::XykStorage(Call::multiswap_buy_asset { + swap_token_list: vec![0, 2], + bought_asset_amount: 10u128, + max_amount_in: 1u128, + }), + mock::RuntimeCall::XykStorage(Call::multiswap_sell_asset { + swap_token_list: vec![0, 2], + sold_asset_amount: 10, + min_amount_out: 0, + }), + ]; + + assert!(calls + .iter() + .map(|call| call.get_dispatch_info()) + .all(|dispatch| dispatch.pays_fee == Pays::No)); + }); +} + +#[test] +#[serial] +fn successful_swaps_are_free() { + new_test_ext().execute_with(|| { + initialize(); + XykStorage::create_pool(RuntimeOrigin::signed(2), 1, 1000, 0, 1000).unwrap(); + + let calls = [ + mock::RuntimeCall::XykStorage(Call::sell_asset { + sold_asset_id: 0u32, + bought_asset_id: 1u32, + sold_asset_amount: 10u128, + min_amount_out: 1u128, + }), + mock::RuntimeCall::XykStorage(Call::buy_asset { + sold_asset_id: 0u32, + bought_asset_id: 1u32, + bought_asset_amount: 10u128, + max_amount_in: 1u128, + }), + mock::RuntimeCall::XykStorage(Call::multiswap_buy_asset { + swap_token_list: vec![0, 1], + bought_asset_amount: 10u128, + max_amount_in: 1u128, + }), + mock::RuntimeCall::XykStorage(Call::multiswap_sell_asset { + swap_token_list: vec![0, 1], + sold_asset_amount: 10, + min_amount_out: 0, + }), + ]; + + assert!(calls.iter().all(|call| matches!( + call.clone().dispatch(RuntimeOrigin::signed(2)), + Ok(post_info) if post_info.pays_fee == Pays::No + ))); + }); +} + +#[test] +#[serial] +fn unsuccessful_swaps_are_not_free() { + new_test_ext().execute_with(|| { + initialize(); + // XykStorage::create_pool(RuntimeOrigin::signed(2), 1, 1000, 0, 1000).unwrap(); + + let calls = [ + mock::RuntimeCall::XykStorage(Call::sell_asset { + sold_asset_id: 0u32, + bought_asset_id: 1u32, + sold_asset_amount: 10u128, + min_amount_out: 1u128, + }), + mock::RuntimeCall::XykStorage(Call::buy_asset { + sold_asset_id: 0u32, + bought_asset_id: 1u32, + bought_asset_amount: 10u128, + max_amount_in: 1u128, + }), + mock::RuntimeCall::XykStorage(Call::multiswap_buy_asset { + swap_token_list: vec![0, 1], + bought_asset_amount: 10u128, + max_amount_in: 1u128, + }), + mock::RuntimeCall::XykStorage(Call::multiswap_sell_asset { + swap_token_list: vec![0, 1], + sold_asset_amount: 10, + min_amount_out: 0, + }), + ]; + + assert!(calls.iter().all(|call| { + matches!( + call.clone().dispatch(RuntimeOrigin::signed(2)), + Err(err) if err.post_info.pays_fee == Pays::Yes + && err.post_info.actual_weight.unwrap().ref_time() > 0 + ) + })); + }); +} + +#[test] +#[serial] +fn unsuccessful_buy_assets_charges_fee() { + new_test_ext().execute_with(|| { + System::set_block_number(1); + + let post_info = + XykStorage::multiswap_buy_asset(RuntimeOrigin::signed(2), vec![100, 200], 0, 0) + .unwrap_err() + .post_info; + assert_eq!(post_info.pays_fee, Pays::Yes); + + let post_info = + XykStorage::multiswap_sell_asset(RuntimeOrigin::signed(2), vec![100, 200], 0, 0) + .unwrap_err() + .post_info; + assert_eq!(post_info.pays_fee, Pays::Yes); + }); +} + +#[test] +#[serial] +#[ignore] +fn successful_sell_assets_does_not_charge_fee() { + new_test_ext().execute_with(|| { + initialize(); + let first_token_balance = XykStorage::balance(1, DUMMY_USER_ID); + let post_info = XykStorage::multiswap_sell_asset( + RuntimeOrigin::signed(2), + vec![1, 4], + first_token_balance, + 0, + ) + .unwrap(); + assert_eq!(post_info.pays_fee, Pays::No); + }); +} + +#[test] +#[serial] +fn unsuccessful_sell_assets_charges_fee() { + new_test_ext().execute_with(|| { + System::set_block_number(1); + + //try to sell non owned, non existing tokens + let post_info = + XykStorage::multiswap_sell_asset(RuntimeOrigin::signed(2), vec![100, 200], 0, 0) + .unwrap_err() + .post_info; + assert_eq!(post_info.pays_fee, Pays::Yes); + }); +} + +#[test] +#[serial] +fn PoolCreateApi_test_pool_exists_return_false_for_non_existing_pool() { + new_test_ext().execute_with(|| { + System::set_block_number(1); + assert!(!>::pool_exists( + 1_u32, 4_u32 + )); + }); +} + +#[test] +#[serial] +fn PoolCreateApi_pool_exists_return_true_for_existing_pool() { + new_test_ext().execute_with(|| { + initialize(); + + XykStorage::create_pool(RuntimeOrigin::signed(2), 0, 500000, 1, 10000).unwrap(); + assert!(>::pool_exists( + 0_u32, 1_u32 + )); + }); +} + +#[test] +#[serial] +fn PoolCreateApi_pool_create_creates_a_pool() { + new_test_ext().execute_with(|| { + initialize(); + + let first_asset_id = 0_u32; + let first_asset_amount = 10_000_u128; + let second_asset_id = 1_u32; + let second_asset_amount = 5_000_u128; + assert!(!>::pool_exists( + first_asset_id, + second_asset_id + )); + + let liq_token_id = Tokens::next_asset_id(); + let liq_token_amount = (first_asset_amount + second_asset_amount) / 2; + + assert_eq!( + >::pool_create( + DUMMY_USER_ID, + first_asset_id, + first_asset_amount, + second_asset_id, + second_asset_amount + ), + Some((liq_token_id, liq_token_amount)) + ); + + assert_ne!(liq_token_id, Tokens::next_asset_id()); + assert_eq!(liq_token_amount, XykStorage::balance(liq_token_id, DUMMY_USER_ID)); + + assert!(>::pool_exists( + 0_u32, 1_u32 + )); + }); +} + +#[test] +#[serial] +fn test_create_blacklisted_pool() { + new_test_ext().execute_with(|| { + let blaclisted_first_asset_id = 1; + let blaclisted_second_asset_id = 9; + + assert_err!( + XykStorage::create_pool( + RuntimeOrigin::signed(2), + blaclisted_first_asset_id, + 100000000000000, + blaclisted_second_asset_id, + 100000000000000 + ), + Error::::DisallowedPool + ); + + assert_err!( + XykStorage::create_pool( + RuntimeOrigin::signed(2), + blaclisted_second_asset_id, + 100000000000000, + blaclisted_first_asset_id, + 100000000000000 + ), + Error::::DisallowedPool + ); + }); +} + +#[test_case(200_000_000_000_000_000_000_u128, 1_000_000_000_000_u128, 1_u128 ; "swap plus 1 leftover")] +#[test_case(2_000_u128, 100_u128, 2_u128 ; "swap plus 2 leftover")] +#[test_case(1_000_000_000_000_000_000_000_000_000, 135_463_177_684_253_389, 2_u128 ; "benchmark case")] +#[serial] +fn test_compound_calculate_balanced_swap_for_liquidity(amount: u128, reward: u128, surplus: u128) { + new_test_ext().execute_with(|| { + System::set_block_number(1); + + let acc_id: u128 = 2; + let pool = amount / 2; + XykStorage::create_new_token(&acc_id, amount); + XykStorage::create_new_token(&acc_id, amount); + XykStorage::create_pool(RuntimeOrigin::signed(2), 0, pool, 1, pool).unwrap(); + let balance_before_0 = XykStorage::balance(0, 2); + let balance_before_1 = XykStorage::balance(1, 2); + + let swap_amount = XykStorage::calculate_balanced_sell_amount(reward, pool).unwrap(); + let swapped_amount = XykStorage::calculate_sell_price(pool, pool, swap_amount).unwrap(); + + XykStorage::multiswap_sell_asset(RuntimeOrigin::signed(2), vec![0, 1], swap_amount, 0) + .unwrap(); + + XykStorage::mint_liquidity(RuntimeOrigin::signed(2), 1, 0, swapped_amount, u128::MAX) + .unwrap(); + + assert_eq!(XykStorage::balance(0, 2), balance_before_0 - reward + surplus); + assert_eq!(XykStorage::balance(1, 2), balance_before_1); + }); +} + +#[test_case(100_000, 1_000, 2, 1 ; "surplus of 1")] +#[test_case(100_000_000_000, 1_000, 2, 1 ; "large reserve, surplus of 1")] +#[test_case(100_000_000_000, 1_000_000_000, 1_000_000, 52815 ; "small pool, large surplus")] +#[test_case(1_000_000_000, 100_000, 2, 2 ; "benchmark precision test")] +#[serial] +fn test_compound_provide_liquidity(amount: u128, reward: u128, pool_r: u128, surplus: u128) { + new_test_ext().execute_with(|| { + System::set_block_number(1); + + let acc_id: u128 = 2; + let pool = amount / pool_r; + XykStorage::create_new_token(&acc_id, amount); + XykStorage::create_new_token(&acc_id, amount); + XykStorage::create_pool(RuntimeOrigin::signed(2), 0, pool, 1, pool).unwrap(); + let balance_before_0 = XykStorage::balance(0, 2); + let balance_before_1 = XykStorage::balance(1, 2); + + XykStorage::provide_liquidity_with_conversion(RuntimeOrigin::signed(2), 2, 0, reward) + .unwrap(); + + assert_eq!(XykStorage::balance(0, 2), balance_before_0 - reward + surplus); + assert_eq!(XykStorage::balance(1, 2), balance_before_1); + }); +} + +#[test_case(2_000_000, 1_000_000, 0 ; "compound all rewards")] +#[test_case(2_000_000, 500_000, 0 ; "compound half rewards")] +#[test_case(100_000_000_000_000_000_000, 1_000_000, 1 ; "benchmark precision test")] +#[serial] +fn test_compound_rewards(amount: u128, part_permille: u32, surplus: u128) { + new_test_ext().execute_with(|| { + let amount_permille = Permill::from_parts(part_permille); + System::set_block_number(1); + + let check_pool_exist_mock = MockValuationApi::check_pool_exist_context(); + check_pool_exist_mock.expect().return_const(Ok(())); + + // MockPromotedPoolApi::instance().lock().unwrap().clear(); + + XykStorage::create_new_token(&2, amount); + XykStorage::create_new_token(&2, amount); + XykStorage::create_pool(RuntimeOrigin::signed(2), 0, amount / 2, 1, amount / 2).unwrap(); + ProofOfStake::update_pool_promotion(RuntimeOrigin::root(), 2, 1u8).unwrap(); + ProofOfStake::activate_liquidity(RuntimeOrigin::signed(2), 2, amount / 2, None).unwrap(); + + // MockPromotedPoolApi::instance().lock().unwrap().insert(2, U256::from(u128::MAX)); + ProofOfStake::distribute_rewards(amount / 2); + System::set_block_number(10); + + let amount = ProofOfStake::calculate_rewards_amount(2, 2).unwrap(); + XykStorage::transfer( + 0, + 2, + ::LiquidityMiningIssuanceVault::get(), + amount, + ) + .unwrap(); + + let balance_before_0 = XykStorage::balance(0, 2); + let balance_before_1 = XykStorage::balance(1, 2); + let balance_not_compounded: u128 = (Permill::one() - amount_permille) * amount; + XykStorage::compound_rewards(RuntimeOrigin::signed(2), 2, amount_permille).unwrap(); + + assert_eq!(XykStorage::balance(0, 2), balance_before_0 + surplus + balance_not_compounded); + assert_eq!(XykStorage::balance(1, 2), balance_before_1); + }); +} + +#[test] +#[serial] +fn test_compound_rewards_pool_assets_order_swapped() { + new_test_ext().execute_with(|| { + let amount = 2_000_000_u128; + let amount_permille = Permill::from_parts(1_000_000); + System::set_block_number(1); + // MockPromotedPoolApi::instance().lock().unwrap().clear(); + + let check_pool_exist_mock = MockValuationApi::check_pool_exist_context(); + check_pool_exist_mock.expect().return_const(Ok(())); + + XykStorage::create_new_token(&2, amount); + XykStorage::create_new_token(&2, amount); + XykStorage::create_pool(RuntimeOrigin::signed(2), 1, amount / 2, 0, amount / 2).unwrap(); + ProofOfStake::update_pool_promotion(RuntimeOrigin::root(), 2, 1u8).unwrap(); + ProofOfStake::activate_liquidity(RuntimeOrigin::signed(2), 2, amount / 2, None).unwrap(); + + // MockPromotedPoolApi::instance().lock().unwrap().insert(2, U256::from(u128::MAX)); + ProofOfStake::distribute_rewards(amount / 2); + + System::set_block_number(10); + + let amount = ProofOfStake::calculate_rewards_amount(2, 2).unwrap(); + XykStorage::transfer( + 0, + 2, + ::LiquidityMiningIssuanceVault::get(), + amount, + ) + .unwrap(); + + let balance_before_0 = XykStorage::balance(0, 2); + let balance_before_1 = XykStorage::balance(1, 2); + let balance_not_compounded: u128 = (Permill::one() - amount_permille) * amount; + XykStorage::compound_rewards(RuntimeOrigin::signed(2), 2, amount_permille).unwrap(); + + assert_eq!(XykStorage::balance(0, 2), balance_before_0 + balance_not_compounded); + assert_eq!(XykStorage::balance(1, 2), balance_before_1); + }); +} + +#[test] +#[serial] +fn sell_N_maintenance_mode() { + new_test_ext().execute_with(|| { + initialize(); + + MockMaintenanceStatusProvider::set_maintenance(true); + + assert_err_ignore_postinfo!( + XykStorage::multiswap_sell_asset(RuntimeOrigin::signed(2), vec![1, 4], 20000000, 0), + Error::::TradingBlockedByMaintenanceMode, + ); + }); +} + +#[test] +#[serial] +fn test_compound_rewards_error_on_non_native_pool() { + new_test_ext().execute_with(|| { + XykStorage::create_new_token(&2, 2_000_000_u128); + XykStorage::create_new_token(&2, 2_000_000_u128); + XykStorage::create_new_token(&2, 2_000_000_u128); + XykStorage::create_pool(RuntimeOrigin::signed(2), 1, 1000, 2, 1000).unwrap(); + + assert_err!( + XykStorage::compound_rewards( + RuntimeOrigin::signed(2), + 3, + Permill::from_parts(1_000_000) + ), + Error::::FunctionNotAvailableForThisToken + ); + }); +} + +#[test] +#[serial] +fn buy_W_maintenance_mode() { + new_test_ext().execute_with(|| { + initialize(); + + MockMaintenanceStatusProvider::set_maintenance(true); + + assert_err_ignore_postinfo!( + // buying 30000000000000000000 assetId 1 of pool 0 1 + XykStorage::multiswap_buy_asset( + RuntimeOrigin::signed(2), + vec![1, 4], + 30000000000000000000, + 3000000000000000000000, + ), + Error::::TradingBlockedByMaintenanceMode, + ); + }); +} + +#[test] +#[serial] +fn valuate_token_paired_with_mgx() { + new_test_ext().execute_with(|| { + System::set_block_number(1); + let native_token_id = XykStorage::create_new_token(&DUMMY_USER_ID, 2_000_000_u128); + assert_eq!(native_token_id, XykStorage::native_token_id()); + + let first_token = XykStorage::create_new_token(&DUMMY_USER_ID, 1_000_000_u128); + let second_token = XykStorage::create_new_token(&DUMMY_USER_ID, 1_000_000_u128); + let first_token_pool = second_token + 1; + let second_token_pool = second_token + 2; + + XykStorage::create_pool( + RuntimeOrigin::signed(DUMMY_USER_ID), + native_token_id, + 1_000_000_u128, + first_token, + 500_000_u128, + ) + .unwrap(); + + XykStorage::create_pool( + RuntimeOrigin::signed(DUMMY_USER_ID), + second_token, + 1_000_000_u128, + native_token_id, + 500_000_u128, + ) + .unwrap(); + + assert_eq!( + as Valuate<_, _>>::valuate_non_liquidity_token(first_token, 100), + 200 + ); + + assert_eq!( + as Valuate<_, _>>::valuate_non_liquidity_token(second_token, 100), + 50 + ); + }); +} diff --git a/gasp-node/pallets/xyk/src/weights.rs b/gasp-node/pallets/xyk/src/weights.rs new file mode 100644 index 000000000..a91a37ec2 --- /dev/null +++ b/gasp-node/pallets/xyk/src/weights.rs @@ -0,0 +1,212 @@ +// This file is part of Mangata. + +// Copyright (C) 2020-2022 Mangata Foundation. +// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 + +// This program 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. + +// This program 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 this program. If not, see . + +//! Autogenerated weights for pallet_xyk +//! +//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev +//! DATE: 2023-03-06, STEPS: `2`, REPEAT: 2, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! EXECUTION: Some(Wasm), WASM-EXECUTION: Compiled, CHAIN: Some("kusama-local"), DB CACHE: 1024 + +// Executed Command: +// /hdd/work/mangata-ws/mangata-node/scripts/..//target/release/mangata-node +// benchmark +// pallet +// --chain +// kusama-local +// --execution +// wasm +// --wasm-execution +// compiled +// --pallet +// pallet_xyk +// --extrinsic +// * +// --steps +// 2 +// --repeat +// 2 +// --output +// ./benchmarks/pallet_xyk_weights.rs +// --template +// ./templates/module-weight-template.hbs + +#![cfg_attr(rustfmt, rustfmt_skip)] +#![allow(unused_parens)] +#![allow(unused_imports)] +#![allow(clippy::unnecessary_cast)] + +use frame_support::{traits::Get, weights::{Weight, constants::RocksDbWeight}}; +use sp_std::marker::PhantomData; + +/// Weight functions needed for pallet_xyk. +pub trait WeightInfo { + fn create_pool() -> Weight; + fn sell_asset() -> Weight; + fn multiswap_sell_asset(x: u32, ) -> Weight; + fn buy_asset() -> Weight; + fn multiswap_buy_asset(x: u32, ) -> Weight; + fn mint_liquidity() -> Weight; + fn mint_liquidity_using_vesting_native_tokens() -> Weight; + fn burn_liquidity() -> Weight; + fn provide_liquidity_with_conversion() -> Weight; + fn compound_rewards() -> Weight; +} + +// For backwards compatibility and tests +impl WeightInfo for () { + // Storage: AssetRegistry Metadata (r:3 w:1) + // Storage: Bootstrap ActivePair (r:1 w:0) + // Storage: Xyk Pools (r:2 w:1) + // Storage: Tokens Accounts (r:5 w:5) + // Storage: System Account (r:1 w:1) + // Storage: Tokens NextCurrencyId (r:1 w:1) + // Storage: Tokens TotalIssuance (r:1 w:1) + // Storage: Xyk LiquidityAssets (r:0 w:1) + // Storage: Xyk LiquidityPools (r:0 w:1) + fn create_pool() -> Weight { + (Weight::from_parts(180_230_000, 0)) + .saturating_add(RocksDbWeight::get().reads(14 as u64)) + .saturating_add(RocksDbWeight::get().writes(12 as u64)) + } + // Storage: Maintenance MaintenanceStatus (r:1 w:0) + // Storage: AssetRegistry Metadata (r:2 w:0) + // Storage: Xyk Pools (r:3 w:1) + // Storage: Tokens Accounts (r:6 w:6) + // Storage: System Account (r:2 w:2) + fn sell_asset() -> Weight { + (Weight::from_parts(197_110_000, 0)) + .saturating_add(RocksDbWeight::get().reads(14 as u64)) + .saturating_add(RocksDbWeight::get().writes(9 as u64)) + } + // Storage: Maintenance MaintenanceStatus (r:1 w:0) + // Storage: AssetRegistry Metadata (r:3 w:0) + // Storage: Xyk Pools (r:6 w:4) + // Storage: Tokens Accounts (r:12 w:12) + // Storage: System Account (r:2 w:2) + // Storage: Tokens TotalIssuance (r:1 w:1) + fn multiswap_sell_asset(x: u32, ) -> Weight { + (Weight::from_parts(517_520_000, 0)) + // Standard Error: 249_655 + .saturating_add((Weight::from_parts(197_021_587, 0)).saturating_mul(x as u64)) + .saturating_add(RocksDbWeight::get().reads(1 as u64)) + .saturating_add(RocksDbWeight::get().reads((8 as u64).saturating_mul(x as u64))) + .saturating_add(RocksDbWeight::get().writes(1 as u64)) + .saturating_add(RocksDbWeight::get().writes((6 as u64).saturating_mul(x as u64))) + } + // Storage: Maintenance MaintenanceStatus (r:1 w:0) + // Storage: AssetRegistry Metadata (r:2 w:0) + // Storage: Xyk Pools (r:4 w:1) + // Storage: Tokens Accounts (r:6 w:6) + // Storage: System Account (r:2 w:2) + fn buy_asset() -> Weight { + (Weight::from_parts(205_329_000, 0)) + .saturating_add(RocksDbWeight::get().reads(15 as u64)) + .saturating_add(RocksDbWeight::get().writes(9 as u64)) + } + // Storage: Maintenance MaintenanceStatus (r:1 w:0) + // Storage: AssetRegistry Metadata (r:3 w:0) + // Storage: Xyk Pools (r:6 w:4) + // Storage: Tokens Accounts (r:12 w:12) + // Storage: System Account (r:2 w:2) + // Storage: Tokens TotalIssuance (r:1 w:1) + fn multiswap_buy_asset(x: u32, ) -> Weight { + (Weight::from_parts(533_530_000, 0)) + // Standard Error: 254_339 + .saturating_add((Weight::from_parts(202_640_460, 0)).saturating_mul(x as u64)) + .saturating_add(RocksDbWeight::get().reads(1 as u64)) + .saturating_add(RocksDbWeight::get().reads((8 as u64).saturating_mul(x as u64))) + .saturating_add(RocksDbWeight::get().writes(1 as u64)) + .saturating_add(RocksDbWeight::get().writes((6 as u64).saturating_mul(x as u64))) + } + // Storage: AssetRegistry Metadata (r:2 w:0) + // Storage: Xyk LiquidityAssets (r:1 w:0) + // Storage: Xyk Pools (r:1 w:1) + // Storage: Tokens TotalIssuance (r:1 w:1) + // Storage: Tokens Accounts (r:5 w:5) + // Storage: Tokens NextCurrencyId (r:1 w:0) + // Storage: ProofOfStake PromotedPoolRewards (r:1 w:0) + // Storage: MultiPurposeLiquidity ReserveStatus (r:1 w:1) + // Storage: ProofOfStake RewardsInfo (r:1 w:1) + // Storage: ProofOfStake TotalActivatedLiquidity (r:1 w:1) + fn mint_liquidity() -> Weight { + (Weight::from_parts(206_570_000, 0)) + .saturating_add(RocksDbWeight::get().reads(15 as u64)) + .saturating_add(RocksDbWeight::get().writes(10 as u64)) + } + // Storage: Xyk LiquidityAssets (r:1 w:0) + // Storage: ProofOfStake PromotedPoolRewards (r:1 w:0) + // Storage: Vesting Vesting (r:2 w:2) + // Storage: Tokens Locks (r:2 w:2) + // Storage: Tokens Accounts (r:5 w:5) + // Storage: Xyk Pools (r:1 w:1) + // Storage: Tokens TotalIssuance (r:1 w:1) + // Storage: Tokens NextCurrencyId (r:1 w:0) + fn mint_liquidity_using_vesting_native_tokens() -> Weight { + (Weight::from_parts(228_030_000, 0)) + .saturating_add(RocksDbWeight::get().reads(14 as u64)) + .saturating_add(RocksDbWeight::get().writes(11 as u64)) + } + // Storage: AssetRegistry Metadata (r:2 w:0) + // Storage: Xyk LiquidityAssets (r:1 w:2) + // Storage: MultiPurposeLiquidity ReserveStatus (r:1 w:1) + // Storage: Xyk Pools (r:1 w:2) + // Storage: Tokens Accounts (r:5 w:5) + // Storage: ProofOfStake PromotedPoolRewards (r:1 w:0) + // Storage: ProofOfStake RewardsInfo (r:1 w:1) + // Storage: ProofOfStake TotalActivatedLiquidity (r:1 w:1) + // Storage: Tokens TotalIssuance (r:1 w:1) + // Storage: Xyk LiquidityPools (r:0 w:1) + fn burn_liquidity() -> Weight { + (Weight::from_parts(193_430_000, 0)) + .saturating_add(RocksDbWeight::get().reads(14 as u64)) + .saturating_add(RocksDbWeight::get().writes(14 as u64)) + } + // Storage: Xyk LiquidityPools (r:1 w:0) + // Storage: AssetRegistry Metadata (r:2 w:0) + // Storage: Xyk Pools (r:4 w:1) + // Storage: Tokens Accounts (r:7 w:7) + // Storage: Maintenance MaintenanceStatus (r:1 w:0) + // Storage: System Account (r:2 w:2) + // Storage: Xyk LiquidityAssets (r:2 w:0) + // Storage: Tokens TotalIssuance (r:1 w:1) + // Storage: Tokens NextCurrencyId (r:1 w:0) + // Storage: ProofOfStake PromotedPoolRewards (r:1 w:0) + fn provide_liquidity_with_conversion() -> Weight { + (Weight::from_parts(314_670_000, 0)) + .saturating_add(RocksDbWeight::get().reads(22 as u64)) + .saturating_add(RocksDbWeight::get().writes(11 as u64)) + } + // Storage: Xyk LiquidityPools (r:1 w:0) + // Storage: AssetRegistry Metadata (r:2 w:0) + // Storage: ProofOfStake PromotedPoolRewards (r:1 w:0) + // Storage: ProofOfStake RewardsInfo (r:1 w:1) + // Storage: Tokens Accounts (r:8 w:8) + // Storage: Xyk Pools (r:2 w:1) + // Storage: Maintenance MaintenanceStatus (r:1 w:0) + // Storage: System Account (r:2 w:2) + // Storage: Tokens TotalIssuance (r:2 w:2) + // Storage: Xyk LiquidityAssets (r:2 w:0) + // Storage: Tokens NextCurrencyId (r:1 w:0) + // Storage: MultiPurposeLiquidity ReserveStatus (r:1 w:1) + // Storage: ProofOfStake TotalActivatedLiquidity (r:1 w:1) + fn compound_rewards() -> Weight { + (Weight::from_parts(436_760_000, 0)) + .saturating_add(RocksDbWeight::get().reads(25 as u64)) + .saturating_add(RocksDbWeight::get().writes(16 as u64)) + } +} diff --git a/gasp-node/rollup/node/Cargo.toml b/gasp-node/rollup/node/Cargo.toml new file mode 100644 index 000000000..64e52d242 --- /dev/null +++ b/gasp-node/rollup/node/Cargo.toml @@ -0,0 +1,127 @@ +[package] +name = "rollup-node" +version = "1.0.0" +description = "Mangata rollup node" +authors = ["Mangata Team"] +edition.workspace = true +license = "MIT-0" +publish = false +repository.workspace = true +build = "build.rs" + +[package.metadata.docs.rs] +targets = ["x86_64-unknown-linux-gnu"] + +[[bin]] +name = "rollup-node" + +[dependencies] +clap = { version = "4.4.2", features = ["derive", "env"] } +codec = { workspace = true } +futures = { workspace = true } +hex = { workspace = true } +hex-literal = { workspace = true } +jsonrpsee = { workspace = true, features = ["server"] } +log = { workspace = true } +serde = { workspace = true, features = ["derive"] } +serde_json = { workspace = true } +array-bytes = { workspace = true } +rand = "0.8.5" +color-print = "0.3.4" + +# Local +rollup-runtime = { path = "../runtime/" } +xyk-rpc = { path = "../../pallets/xyk/rpc" } +xyk-runtime-api = { path = "../../pallets/xyk/runtime-api" } +rolldown-rpc = { path = "../../pallets/rolldown/rpc" } +rolldown-runtime-api = { path = "../../pallets/rolldown/runtime-api" } +proof-of-stake-runtime-api = { path = '../../pallets/proof-of-stake/runtime-api' } +proof-of-stake-rpc = { path = '../../pallets/proof-of-stake/rpc' } +metamask-signature-rpc = { path = '../../pallets/metamask-signature-rpc/' } +pallet-rolldown = { path = "../../pallets/rolldown" } +market-rpc = { path = "../../pallets/market/rpc" } +pallet-market = { path = "../../pallets/market" } + +# Substrate +frame-benchmarking = { workspace = true } +frame-benchmarking-cli = { workspace = true } +frame-system = { workspace = true } +pallet-transaction-payment = { workspace = true } +pallet-transaction-payment-rpc = { workspace = true } +sc-basic-authorship-ver = { workspace = true } +sc-chain-spec = { workspace = true } +sc-cli = { workspace = true } +sc-client-api = { workspace = true } +sc-executor = { workspace = true } +sc-network = { workspace = true } +sc-network-sync = { workspace = true } +sc-offchain = { workspace = true } +sc-rpc = { workspace = true } +sc-rpc-api = { workspace = true } +sc-service = { workspace = true } +sc-sysinfo = { workspace = true } +sc-telemetry = { workspace = true } +sc-tracing = { workspace = true } +sc-transaction-pool = { workspace = true } +sc-transaction-pool-api = { workspace = true } +sp-api = { workspace = true } +sp-block-builder = { workspace = true } +sp-blockchain = { workspace = true } +sc-consensus-aura = { workspace = true } +sp-consensus-aura = { workspace = true } +sc-consensus = { workspace = true } +sc-consensus-grandpa = { workspace = true } +sp-consensus-grandpa = { workspace = true } +sp-statement-store = { workspace = true } +sp-std = { workspace = true } +sp-core = { workspace = true } +sp-io = { workspace = true } +sp-inherents = { workspace = true } +sp-keyring = { workspace = true } +sp-keystore = { workspace = true } +sp-offchain = { workspace = true } +sp-runtime = { workspace = true } +sp-session = { workspace = true } +sp-timestamp = { workspace = true } +sp-transaction-pool = { workspace = true } +sp-ver = { workspace = true, features = ["helpers"] } +substrate-prometheus-endpoint = { workspace = true } +ver-api = { workspace = true } +try-runtime-cli = { workspace = true, optional = true } + +substrate-frame-rpc-system = { package = "mangata-rpc-nonce", path = "../../rpc/nonce" } + +[dev-dependencies] +tempfile = "3.8.0" + +[build-dependencies] +substrate-build-script-utils = { workspace = true } + +[features] +default = ["std"] +fast-runtime = [] +std = [ + "rollup-runtime/std", + "xyk-rpc/std", + "market-rpc/std", + "pallet-market/std", + "rolldown-rpc/std", +] +# Dependencies that are only required if runtime benchmarking should be build. +runtime-benchmarks = [ + "frame-benchmarking-cli/runtime-benchmarks", + "frame-benchmarking/runtime-benchmarks", + "frame-system/runtime-benchmarks", + "rollup-runtime/runtime-benchmarks", + "sc-service/runtime-benchmarks", + "sp-runtime/runtime-benchmarks", +] +# Enable features that allow the runtime to be tried and debugged. Name might be subject to change +# in the near future. +try-runtime = [ + "frame-system/try-runtime", + "rollup-runtime/try-runtime", + "pallet-transaction-payment/try-runtime", + "sp-runtime/try-runtime", + "try-runtime-cli/try-runtime", +] diff --git a/gasp-node/rollup/node/build.rs b/gasp-node/rollup/node/build.rs new file mode 100644 index 000000000..e3bfe3116 --- /dev/null +++ b/gasp-node/rollup/node/build.rs @@ -0,0 +1,7 @@ +use substrate_build_script_utils::{generate_cargo_keys, rerun_if_git_head_changed}; + +fn main() { + generate_cargo_keys(); + + rerun_if_git_head_changed(); +} diff --git a/gasp-node/rollup/node/src/benchmarking.rs b/gasp-node/rollup/node/src/benchmarking.rs new file mode 100644 index 000000000..e0d07c2f0 --- /dev/null +++ b/gasp-node/rollup/node/src/benchmarking.rs @@ -0,0 +1,217 @@ +//! Setup code for [`super::command`] which would otherwise bloat that module. +//! +//! Should only be used for benchmarking as it may break in other contexts. + +use crate::service::Block; +use rollup_runtime as runtime; +use rollup_runtime::config::frame_system::BlockHashCount; +use runtime::{AccountId, Balance, RuntimeApi, Signer, SystemCall, TokenId, TokensCall}; +use sc_cli::Result; +use sc_client_api::BlockBackend; +use sc_executor::WasmExecutor; +use sp_api::ProvideRuntimeApi; +use sp_core::{crypto::key_types::AURA, ecdsa, sr25519, Encode, Pair}; +use sp_inherents::{InherentData, InherentDataProvider}; +use sp_keyring::EthereumKeyring; +use sp_keystore::Keystore; +use sp_runtime::{ + account::EthereumSignature, + traits::{IdentifyAccount, Zero}, + OpaqueExtrinsic, SaturatedConversion, +}; +use sp_std::str::FromStr; +use std::{ + sync::{Arc, Mutex}, + time::Duration, +}; +use substrate_frame_rpc_system::AccountNonceApi; + +#[cfg(not(feature = "runtime-benchmarks"))] +type HostFunctions = sp_io::SubstrateHostFunctions; + +#[cfg(feature = "runtime-benchmarks")] +type HostFunctions = + (sp_io::SubstrateHostFunctions, frame_benchmarking::benchmarking::HostFunctions); + +type WasmFullClient = sc_service::TFullClient>; + +pub fn fetch_nonce(client: &WasmFullClient, account: sp_core::ecdsa::Pair) -> u32 { + let best_hash = client.chain_info().best_hash; + client + .runtime_api() + .account_nonce(best_hash, Signer::from(account.public()).into_account()) + .expect("Fetching account nonce works; qed") +} + +pub fn get_eth_pair_from_seed(seed: &str) -> ecdsa::Pair { + EthereumKeyring::from_str(seed).expect("The keypair should be defined").pair() +} + +/// Create a transaction using the given `call`. +/// +/// Note: Should only be used for benchmarking. +pub fn create_benchmark_extrinsic( + client: &WasmFullClient, + sender: sp_core::ecdsa::Pair, + call: runtime::RuntimeCall, + nonce: Option, +) -> runtime::UncheckedExtrinsic { + let genesis_hash = client.block_hash(0).ok().flatten().expect("Genesis block exists; qed"); + let best_hash = client.chain_info().best_hash; + let best_block = client.chain_info().best_number; + let nonce = nonce.unwrap_or_else(|| fetch_nonce(client, sender.clone())); + + let period = + BlockHashCount::get().checked_next_power_of_two().map(|c| c / 2).unwrap_or(2) as u64; + let extra: runtime::SignedExtra = ( + frame_system::CheckSpecVersion::::new(), + frame_system::CheckTxVersion::::new(), + frame_system::CheckGenesis::::new(), + frame_system::CheckEra::::from(sp_runtime::generic::Era::mortal( + period, + best_block.saturated_into(), + )), + frame_system::CheckNonce::::from(nonce), + frame_system::CheckWeight::::new(), + pallet_transaction_payment::ChargeTransactionPayment::::from(0), + frame_system::CheckNonZeroSender::::new(), + ); + + let raw_payload = runtime::SignedPayload::from_raw( + call.clone(), + extra.clone(), + ( + runtime::VERSION.spec_version, + runtime::VERSION.transaction_version, + genesis_hash, + best_hash, + (), + (), + (), + (), + ), + ); + let signature = raw_payload.using_encoded(|e| sender.sign(e)); + + runtime::UncheckedExtrinsic::new_signed( + call, + Signer::from(sender.public()).into_account().into(), + EthereumSignature::from(signature), + extra, + ) +} + +/// Generates inherent data for the `benchmark overhead` command. +/// +/// Note: Should only be used for benchmarking. +pub async fn inherent_benchmark_data( + prev_seed: [u8; 32], + duration: Duration, +) -> Result { + let keystore = sp_keystore::testing::MemoryKeystore::new(); + let secret_uri = "//Alith"; + let key_pair = + sp_core::sr25519::Pair::from_string(secret_uri, None).expect("Generates key pair"); + keystore + .insert(AURA, secret_uri, key_pair.public().as_ref()) + .expect("Inserts unknown key"); + + let seed = + sp_ver::calculate_next_seed_from_bytes(&keystore, &key_pair.public(), prev_seed.to_vec()) + .unwrap(); + + let mut inherent_data = InherentData::new(); + + sp_timestamp::InherentDataProvider::new(duration.into()) + .provide_inherent_data(&mut inherent_data) + .await + .map_err(|e| format!("creating inherent data: {:?}", e))?; + + sp_ver::RandomSeedInherentDataProvider(seed) + .provide_inherent_data(&mut inherent_data) + .await + .map_err(|e| format!("creating inherent data: {:?}", e))?; + + Ok(inherent_data) +} + +/// Generates extrinsics for the `benchmark overhead` command. +/// +/// Note: Should only be used for benchmarking. +pub struct RemarkBuilder { + client: Arc>, +} + +impl RemarkBuilder { + /// Creates a new [`Self`] from the given client. + pub fn new(client: Arc>) -> Self { + Self { client } + } +} + +impl frame_benchmarking_cli::ExtrinsicBuilder for RemarkBuilder { + fn pallet(&self) -> &str { + "system" + } + + fn extrinsic(&self) -> &str { + "remark" + } + + fn build(&self, nonce: u32) -> std::result::Result { + let acc = get_eth_pair_from_seed("Baltathar"); + let extrinsic: OpaqueExtrinsic = create_benchmark_extrinsic( + &*self.client.lock().unwrap(), + acc, + SystemCall::remark { remark: vec![] }.into(), + Some(nonce), + ) + .into(); + + Ok(extrinsic) + } +} + +/// Generates `Balances::TransferKeepAlive` extrinsics for the benchmarks. +/// +/// Note: Should only be used for benchmarking. +pub struct TransferKeepAliveBuilder { + client: Arc>, + dest: AccountId, + value: Balance, +} + +impl TransferKeepAliveBuilder { + /// Creates a new [`Self`] from the given client. + pub fn new(client: Arc>, dest: AccountId, value: Balance) -> Self { + Self { client, dest, value } + } +} + +impl frame_benchmarking_cli::ExtrinsicBuilder for TransferKeepAliveBuilder { + fn pallet(&self) -> &str { + "balances" + } + + fn extrinsic(&self) -> &str { + "transfer_keep_alive" + } + + fn build(&self, nonce: u32) -> std::result::Result { + let acc = get_eth_pair_from_seed("Baltathar"); + let extrinsic: OpaqueExtrinsic = create_benchmark_extrinsic( + &*self.client.lock().unwrap(), + acc, + TokensCall::transfer_keep_alive { + dest: self.dest.clone().into(), + currency_id: TokenId::zero(), + amount: self.value, + } + .into(), + Some(nonce), + ) + .into(); + + Ok(extrinsic) + } +} diff --git a/gasp-node/rollup/node/src/chain_spec.rs b/gasp-node/rollup/node/src/chain_spec.rs new file mode 100644 index 000000000..079da6d58 --- /dev/null +++ b/gasp-node/rollup/node/src/chain_spec.rs @@ -0,0 +1,713 @@ +use crate::command::{EvmChain, InitialSequencersSet}; +use frame_benchmarking::benchmarking::current_time; +use rand::{thread_rng, Rng}; +use rollup_runtime::{ + config::orml_asset_registry::AssetMetadataOf, currency, tokens::RX_TOKEN_ID, AccountId, + AuraConfig, CustomMetadata, GrandpaConfig, L1Asset, RuntimeGenesisConfig, Signature, + SudoConfig, SystemConfig, XcmMetadata, WASM_BINARY, +}; +use sc_service::ChainType; +use sp_consensus_aura::sr25519::AuthorityId as AuraId; +use sp_consensus_grandpa::AuthorityId as GrandpaId; +use sp_core::{ecdsa, ByteArray, Encode, Pair, Public, H256}; +use sp_keyring::EthereumKeyring; +use sp_runtime::{ + traits::{IdentifyAccount, Verify}, + BoundedVec, +}; +use sp_std::{convert::TryInto, str::FromStr}; + +// The URL for the telemetry server. +// const STAGING_TELEMETRY_URL: &str = "wss://telemetry.polkadot.io/submit/"; + +/// Specialized `ChainSpec`. This is a specialization of the general Substrate ChainSpec type. +pub type ChainSpec = sc_service::GenericChainSpec; + +/// Generate a crypto pair from seed. +pub fn get_from_seed(seed: &str) -> ::Public { + let pair = TPublic::Pair::from_string(&format!("//{}", seed), None) + .expect("static values are valid; qed"); + // log::info!("Dev Account Seed Info - {:?}, {:x?}", seed, array_bytes::bytes2hex("0x", pair.to_raw_vec())); + pair.public() +} + +type AccountPublic = ::Signer; + +/// Generate an account ID from seed. +pub fn get_account_id_from_seed(seed: &str) -> AccountId +where + AccountPublic: From<::Public>, +{ + let account = EthereumKeyring::from_str(seed) + .expect("The keypair should be defined") + .to_account_id(); + // log::info!("Dev Account PublicKey Info - {:?}, {:?}", seed, account); + account +} + +/// Generate an Aura authority key. +pub fn authority_keys_from_seed(s: &str) -> (AuraId, GrandpaId) { + (get_from_seed::(s), get_from_seed::(s)) +} + +/// Generate the session keys from individual elements. +/// +/// The input must be a tuple of individual keys (a single arg for now since we have just one key). +pub fn rollup_session_keys(aura: AuraId, grandpa: GrandpaId) -> rollup_runtime::SessionKeys { + rollup_runtime::SessionKeys { aura, grandpa } +} + +pub fn rollup_local_config( + randomize_chain_genesis_salt: bool, + chain_genesis_salt: Option, + eth_sequencers: Vec, + arb_sequencers: Vec, + base_sequencers: Vec, + evm_chain: EvmChain, + decode_url: Option, +) -> ChainSpec { + let (gasp_token_address, eth_chain_id) = match evm_chain { + EvmChain::Holesky => ( + array_bytes::hex2array("0x5620cDb94BaAaD10c20483bd8705DA711b2Bc0a3") + .expect("is correct address"), + 17000u64, + ), + EvmChain::Anvil => ( + array_bytes::hex2array("0xc351628EB244ec633d5f21fBD6621e1a683B1181") + .expect("is correct address"), + 31337u64, + ), + EvmChain::Reth => ( + array_bytes::hex2array("0xc351628EB244ec633d5f21fBD6621e1a683B1181") + .expect("is correct address"), + 1337u64, + ), + }; + + let mut chain_genesis_salt_arr: [u8; 32] = [0u8; 32]; + if randomize_chain_genesis_salt { + thread_rng().fill(&mut chain_genesis_salt_arr[..]); + } else if let Some(salt) = chain_genesis_salt { + chain_genesis_salt_arr = array_bytes::hex2bytes(salt) + .expect("chain_genesis_salt should be hex") + .iter() + .chain(sp_std::iter::repeat(&0u8)) + .take(32) + .cloned() + .collect::>() + .try_into() + .expect("32 bytes"); + } + + // Give your base currency a unit name and decimal places + let mut properties = sc_chain_spec::Properties::new(); + properties.insert("tokenSymbol".into(), "GASP".into()); + properties.insert("tokenDecimals".into(), 18u32.into()); + properties.insert("ss58Format".into(), 42u32.into()); + properties.insert("isEthereum".into(), true.into()); + // This is quite useless here :/ + properties.insert( + "chainGenesisSalt".into(), + array_bytes::bytes2hex("0x", chain_genesis_salt_arr).into(), + ); + + let decode_url = decode_url.unwrap_or(String::from( + "https://polkadot.js.org/apps/?rpc=ws%253A%252F%252F127.0.0.1%253A9944#/extrinsics/decode/", + )); + // todo builder + ChainSpec::from_genesis( + // Name + "Rollup Local", + // ID + "rollup_local", + ChainType::Local, + move || { + let eth = eth_sequencers.clone(); + let arb = arb_sequencers.clone(); + let base = base_sequencers.clone(); + + let tokens_endowment = [ + eth_sequencers.clone(), + arb_sequencers.clone(), + base_sequencers.clone(), + vec![ + get_account_id_from_seed::("Alith"), + get_account_id_from_seed::("Baltathar"), + get_account_id_from_seed::("Charleth"), + ], + ] + .iter() + .flatten() + .cloned() + .map(|account_id| (0u32, 300_000_000__000_000_000_000_000_000u128, account_id)) + .collect::>(); + + rollup_genesis( + // chain genesis salt + H256::from(chain_genesis_salt_arr), + // initial collators. + vec![ + ( + get_account_id_from_seed::("Alith"), + authority_keys_from_seed("Alith"), + ), + ( + get_account_id_from_seed::("Baltathar"), + authority_keys_from_seed("Baltathar"), + ), + ], + // Sudo account + get_account_id_from_seed::("Alith"), + // Tokens endowment + tokens_endowment, + // Config for Staking + // Make sure it works with initial-authorities as staking uses both + ( + vec![ + ( + // Who gets to stake initially + get_account_id_from_seed::("Alith"), + // Id of MGA token, + 0u32, + // How much mangata they stake + 100_000_000__000_000_000_000_000_000_u128, + ), + ( + // Who gets to stake initially + get_account_id_from_seed::("Baltathar"), + // Id of MGA token, + 0u32, + // How much mangata they stake + 80_000_000__000_000_000_000_000_000_u128, + ), + ], + vec![ + // Who gets to stake initially + // Id of MGA token, + // How much mangata they pool + // Id of the dummy token, + // How many dummy tokens they pool, + // Id of the liquidity token that is generated + // How many liquidity tokens they stake, + ], + ), + vec![ + ( + RX_TOKEN_ID, + AssetMetadataOf { + decimals: 18, + name: BoundedVec::truncate_from(b"Gasp".to_vec()), + symbol: BoundedVec::truncate_from(b"GASP".to_vec()), + additional: Default::default(), + existential_deposit: Default::default(), + }, + Some(L1Asset::Ethereum(gasp_token_address)), + ), + ( + 1, + AssetMetadataOf { + decimals: 18, + name: BoundedVec::truncate_from(b"Gasp Ethereum".to_vec()), + symbol: BoundedVec::truncate_from(b"GETH".to_vec()), + additional: Default::default(), + existential_deposit: Default::default(), + }, + Some(L1Asset::Ethereum( + array_bytes::hex2array("0x0000000000000000000000000000000000000001") + .unwrap(), + )), + ), + ], + eth, + arb, + base, + eth_chain_id, + decode_url.clone(), + vec![], + ) + }, + // Bootnodes + Vec::new(), + // Telemetry + None, + // Protocol ID + None, + // ForkId + None, + // Properties + Some(properties), + // Extensions + None, + // code + rollup_runtime::WASM_BINARY.expect("WASM binary was not build, please build it!"), + ) +} + +pub fn ethereum_mainnet(decode_url: Option) -> ChainSpec { + let (_gasp_token_address, eth_chain_id) = ([0u8; 20], 1u64); + let chain_genesis_salt_arr: [u8; 32] = + hex_literal::hex!("0011001100110011001100110011001100110011001100110011001100110011"); + + let collator01 = hex_literal::hex!("b9fcA08B9cA327a1dE90FDB4d51aa5ae6Ffe512a"); + let collator01_sr25519 = + hex_literal::hex!("b6bcce45d0431d7cf3b23cf270e60fab48290cc2e129a62bcac04f6eab20e61f"); + let collator01_ed25519 = + hex_literal::hex!("efc45e2afccbe0f53cab042438aebb6bcfc78585625c2a1b5517f3b258dd1cf8"); + + let collator02 = hex_literal::hex!("1f4E3f24d1ad7fE108c6eB3BA6F83ebe8cF0eD20"); + let collator02_sr25519 = + hex_literal::hex!("860a476d36782b7e2854ab4e93287e67618a835741b84bd7cad0740a83275f3c"); + let collator02_ed25519 = + hex_literal::hex!("2227445cd1b97943e1ba5c3cf3a94cadabd4494ff4394667be13ff755bae1abe"); + + let collator03 = hex_literal::hex!("7F7c7b782fBdAd01Fe33ca8FC647c867ee29deD2"); + let collator03_sr25519 = + hex_literal::hex!("4899a218a591b9345b92de354b4d251eabd205bc64c787386fdccfe1f2147625"); + let collator03_ed25519 = + hex_literal::hex!("d59387884193c920e0cef94770d74cc2cef0d534b1ebf5a5d1eb5033fb58746a"); + + let collator04 = hex_literal::hex!("4691A9BB90e20a7708182fD881fb723f9845460E"); + let collator04_sr25519 = + hex_literal::hex!("b0c2a16a1acecd3e05a243d9bf8881f5d64a70b40864701dba01cfd1ee53c85a"); + let collator04_ed25519 = + hex_literal::hex!("172165647a152929e2bb0af97f55ea0c0deaa087479be8eab7448c2dc8cd0dfe"); + + let sudo = hex_literal::hex!("E73e1Bb7B07f6bf447ED71252A5ad08C7ebE5bE5"); + + // Give your base currency a unit name and decimal places + let mut properties = sc_chain_spec::Properties::new(); + properties.insert("tokenSymbol".into(), "GASP".into()); + properties.insert("tokenDecimals".into(), 18u32.into()); + properties.insert("ss58Format".into(), 42u32.into()); + properties.insert("isEthereum".into(), true.into()); + // This is quite useless here :/ + properties.insert( + "chainGenesisSalt".into(), + array_bytes::bytes2hex("0x", chain_genesis_salt_arr).into(), + ); + + let decode_url = decode_url.expect("polkadot url is provided"); + // todo builder + ChainSpec::from_genesis( + // Name + "Mainnet", + // ID + "mainnet", + ChainType::Live, + move || { + let eth_sequencers: Vec = vec![ + hex_literal::hex!("dFD7f828689FbF00995BAA40d2DE93Eb400Cf60b").into(), + hex_literal::hex!("88bbb08aF77987D86E9559491fE7cC5910D68f2D").into(), + hex_literal::hex!("8d3CD208aa5592CF510eB24D8a2376bbF840bb63").into(), + ]; + let arb_sequencers: Vec = vec![ + hex_literal::hex!("b67CB37E9d114731B5624B6E919c007f4ddEa582").into(), + hex_literal::hex!("71403bFc37f031b60BD7a5B9597115708E391410").into(), + hex_literal::hex!("25CeF43c3F52db02ae52D951936b390C4B6A998F").into(), + ]; + let base_sequencers: Vec = vec![ + hex_literal::hex!("A395bBE2de17B488a578b972D96EE38933eE3c85").into(), + hex_literal::hex!("6f52f2D60AdFC152ac561287b754A56A7933F1ae").into(), + hex_literal::hex!("a7196AF761942A10126165B2c727eFCD46c254e0").into(), + ]; + + let council_members = vec![ + hex_literal::hex!("35dbD8Bd2c5617541bd9D9D8e065adf92275b83E").into(), + hex_literal::hex!("7368bff2fBB4C7B05f854c370eeDD6809186917B").into(), + hex_literal::hex!("9cA8aFB1326c99EC23B8D4e16C0162Bb206D83b8").into(), + hex_literal::hex!("8b5368B4BBa80475c9DFb70543F6090A7e986F39").into(), + hex_literal::hex!("aF3cA574A4903c5ddC7378Ac60d786a2664CbD91").into(), + hex_literal::hex!("584728a637303e753906a4F05CD8Ced10D80eB5e").into(), + hex_literal::hex!("8960911c51EaD00db4cCA88FAF395672458da676").into(), + ]; + + let sequencers_endownment = + [eth_sequencers.clone(), arb_sequencers.clone(), base_sequencers.clone()] + .iter() + .flatten() + .cloned() + .map(|account_id| (RX_TOKEN_ID, 100u128 * currency::DOLLARS, account_id)) + .collect::>(); + + let mut tokens_endowment = sequencers_endownment; + tokens_endowment.push((RX_TOKEN_ID, 988_100_u128 * currency::DOLLARS, sudo.into())); + + tokens_endowment.append( + &mut council_members + .clone() + .into_iter() + .map(|addr| (RX_TOKEN_ID, 1000_u128 * currency::DOLLARS, addr)) + .collect::>(), + ); + + rollup_genesis( + // chain genesis salt + H256::from(chain_genesis_salt_arr), + // initial collators. + vec![ + ( + collator01.into(), + ( + AuraId::from_slice(collator01_sr25519.as_slice()).unwrap(), + GrandpaId::from_slice(collator01_ed25519.as_slice()).unwrap(), + ), + ), + ( + collator02.into(), + ( + AuraId::from_slice(collator02_sr25519.as_slice()).unwrap(), + GrandpaId::from_slice(collator02_ed25519.as_slice()).unwrap(), + ), + ), + ( + collator03.into(), + ( + AuraId::from_slice(collator03_sr25519.as_slice()).unwrap(), + GrandpaId::from_slice(collator03_ed25519.as_slice()).unwrap(), + ), + ), + ( + collator04.into(), + ( + AuraId::from_slice(collator04_sr25519.as_slice()).unwrap(), + GrandpaId::from_slice(collator04_ed25519.as_slice()).unwrap(), + ), + ), + ], + // Sudo account + sudo.into(), + // Tokens endowment + tokens_endowment, + // Config for Staking + // Make sure it works with initial-authorities as staking uses both + ( + vec![ + ( + // Who gets to stake initially + collator01.into(), + // Id of MGA token, + 0u32, + // How much mangata they stake + 1__001_000u128 * currency::DOLLARS, + ), + ( + // Who gets to stake initially + collator02.into(), + // Id of MGA token, + 0u32, + // How much mangata they stake + 1__001_000u128 * currency::DOLLARS, + ), + ( + // Who gets to stake initially + collator03.into(), + // Id of MGA token, + 0u32, + // How much mangata they stake + 1__001_000u128 * currency::DOLLARS, + ), + ( + // Who gets to stake initially + collator04.into(), + // Id of MGA token, + 0u32, + // How much mangata they stake + 1__001_000u128 * currency::DOLLARS, + ), + ], + vec![ + // Who gets to stake initially + // Id of MGA token, + // How much mangata they pool + // Id of the dummy token, + // How many dummy tokens they pool, + // Id of the liquidity token that is generated + // How many liquidity tokens they stake, + ], + ), + vec![ + ( + RX_TOKEN_ID, + AssetMetadataOf { + decimals: 18, + name: BoundedVec::truncate_from(b"Gasp".to_vec()), + symbol: BoundedVec::truncate_from(b"GASP".to_vec()), + additional: Default::default(), + existential_deposit: Default::default(), + }, + Some(L1Asset::Ethereum(hex_literal::hex!( + "0000000000000000000000000000000000000000" + ))), + ), + ( + 1, + AssetMetadataOf { + decimals: 18, + name: BoundedVec::truncate_from(b"Gasp Ethereum".to_vec()), + symbol: BoundedVec::truncate_from(b"GETH".to_vec()), + additional: Default::default(), + existential_deposit: Default::default(), + }, + Some(L1Asset::Ethereum( + array_bytes::hex2array("0x0000000000000000000000000000000000000001") + .unwrap(), + )), + ), + ], + eth_sequencers, + arb_sequencers, + base_sequencers, + eth_chain_id, + decode_url.clone(), + council_members, + ) + }, + // Bootnodes + Vec::new(), + // Telemetry + None, + // Protocol ID + None, + // ForkId + None, + // Properties + Some(properties), + // Extensions + None, + // code + rollup_runtime::WASM_BINARY.expect("WASM binary was not build, please build it!"), + ) +} + +/// Configure initial storage state for FRAME modules. +fn rollup_genesis( + chain_genesis_salt: H256, + initial_authorities: Vec<(AccountId, (AuraId, GrandpaId))>, + root_key: AccountId, + tokens_endowment: Vec<(u32, u128, AccountId)>, + staking_accounts: ( + Vec<(AccountId, u32, u128)>, + Vec<(AccountId, u32, u128, u32, u128, u32, u128)>, + ), + register_assets: Vec<(u32, AssetMetadataOf, Option)>, + eth_initial_sequencers: Vec, + arb_initial_sequencers: Vec, + base_initial_sequencers: Vec, + chain_id: u64, + decode_url: String, + council_members: Vec, +) -> rollup_runtime::RuntimeGenesisConfig { + let initial_sequencers_stake = 10_000_000_u128; + + rollup_runtime::RuntimeGenesisConfig { + system: rollup_runtime::SystemConfig { chain_genesis_salt, ..Default::default() }, + tokens: rollup_runtime::TokensConfig { + tokens_endowment: tokens_endowment + .iter() + .cloned() + .map(|(token_id, amount, account)| (account, token_id, amount)) + .collect(), + created_tokens_for_staking: { + let mut created_tokens_for_staking_token_1: Vec<(AccountId, u32, u128)> = + staking_accounts + .1 + .iter() + .cloned() + .map(|x| { + let (who, _, _, token_id, initial_amount, _, _) = x; + (who.clone(), token_id, initial_amount) + }) + .collect(); + let mut created_tokens_for_staking_token_2: Vec<(AccountId, u32, u128)> = + staking_accounts + .1 + .iter() + .cloned() + .map(|x| { + let (who, token_id, initial_amount, _, _, _, _) = x; + (who.clone(), token_id, initial_amount) + }) + .collect(); + let mut created_tokens_for_staking_token_3: Vec<(AccountId, u32, u128)> = + staking_accounts.0.clone(); + created_tokens_for_staking_token_1.append(&mut created_tokens_for_staking_token_2); + created_tokens_for_staking_token_1.append(&mut created_tokens_for_staking_token_3); + created_tokens_for_staking_token_1 + }, + }, + treasury: Default::default(), + parachain_staking: rollup_runtime::ParachainStakingConfig { + candidates: { + let mut parachain_staking_accounts_1: Vec<_> = staking_accounts + .0 + .iter() + .map(|x| { + let (account_id, liquidity_token_id, liquidity_token_amount) = x; + (account_id.clone(), *liquidity_token_amount, *liquidity_token_id) + }) + .collect(); + let mut parachain_staking_accounts_2: Vec<_> = staking_accounts + .1 + .iter() + .map(|x| { + let (account_id, _, _, _, _, liquidity_token_id, liquidity_token_amount) = + x; + (account_id.clone(), *liquidity_token_amount, *liquidity_token_id) + }) + .collect(); + parachain_staking_accounts_1.append(&mut parachain_staking_accounts_2); + parachain_staking_accounts_1 + }, + delegations: vec![], + }, + session: rollup_runtime::SessionConfig { + keys: initial_authorities + .clone() + .into_iter() + .map(|(acc, (aura, grandpa))| { + ( + acc.clone(), // account id + acc, // validator id + rollup_session_keys(aura, grandpa), // session keys + ) + }) + .collect(), + }, + // no need to pass anything to aura, in fact it will panic if we do. Session will take care + // of this. + aura: Default::default(), + grandpa: Default::default(), + xyk: rollup_runtime::XykConfig { + created_pools_for_staking: staking_accounts + .1 + .iter() + .map(|x| { + let ( + account_id, + native_token_id, + native_token_amount, + pooled_token_id, + pooled_token_amount, + liquidity_token_id, + _, + ) = x; + ( + account_id.clone(), + *native_token_id, + *native_token_amount, + *pooled_token_id, + *pooled_token_amount, + *liquidity_token_id, + ) + }) + .collect(), + }, + fee_lock: rollup_runtime::FeeLockConfig { + period_length: Some(10), + fee_lock_amount: Some(50u128 * currency::DOLLARS), + swap_value_threshold: Some(50u128 * currency::DOLLARS), + whitelisted_tokens: Default::default(), + }, + council: rollup_runtime::CouncilConfig { + phantom: Default::default(), + members: council_members, + }, + transaction_payment: Default::default(), + sudo: rollup_runtime::SudoConfig { + // Assign network admin rights. + key: Some(root_key), + }, + asset_registry: rollup_runtime::AssetRegistryConfig { + assets: register_assets + .iter() + .cloned() + .map(|(id, meta, maybe_l1_asset)| { + let encoded = AssetMetadataOf::encode(&meta); + (id, encoded, maybe_l1_asset) + }) + .collect(), + }, + vesting: Default::default(), + sequencer_staking: rollup_runtime::SequencerStakingConfig { + minimal_stake_amount: 100u128 * currency::DOLLARS, + slash_fine_amount: 1u128 * currency::DOLLARS, + sequencers_stake: [ + eth_initial_sequencers + .into_iter() + .map(|seq| { + ( + seq, + pallet_rolldown::messages::Chain::Ethereum, + 100u128 * currency::DOLLARS, + ) + }) + .collect::>(), + arb_initial_sequencers + .into_iter() + .map(|seq| { + ( + seq, + pallet_rolldown::messages::Chain::Arbitrum, + 100u128 * currency::DOLLARS, + ) + }) + .collect::>(), + base_initial_sequencers + .into_iter() + .map(|seq| { + (seq, pallet_rolldown::messages::Chain::Base, 100u128 * currency::DOLLARS) + }) + .collect::>(), + ] + .iter() + .flatten() + .cloned() + .collect(), + }, + #[cfg(not(feature = "fast-runtime"))] + rolldown: rollup_runtime::RolldownConfig { + _phantom: Default::default(), + dispute_periods: [ + (pallet_rolldown::messages::Chain::Ethereum, 200u128), + (pallet_rolldown::messages::Chain::Arbitrum, 200u128), + (pallet_rolldown::messages::Chain::Base, 200u128), + ] + .iter() + .cloned() + .collect(), + }, + #[cfg(feature = "fast-runtime")] + rolldown: rollup_runtime::RolldownConfig { + _phantom: Default::default(), + dispute_periods: [ + (pallet_rolldown::messages::Chain::Ethereum, 10u128), + (pallet_rolldown::messages::Chain::Arbitrum, 15u128), + (pallet_rolldown::messages::Chain::Base, 15u128), + ] + .iter() + .cloned() + .collect(), + }, + metamask: rollup_runtime::MetamaskConfig { + name: "Gasp".to_string(), + version: "0.0.1".to_string(), + chain_id, + decode_url, + _phantom: Default::default(), + }, + foundation_members: rollup_runtime::FoundationMembersConfig { + members: BoundedVec::truncate_from( + [ + hex_literal::hex!["9cA8aFB1326c99EC23B8D4e16C0162Bb206D83b8"], + hex_literal::hex!["8960911c51EaD00db4cCA88FAF395672458da676"], + hex_literal::hex!["35dbD8Bd2c5617541bd9D9D8e065adf92275b83E"], + ] + .iter() + .map(|acc| sp_runtime::AccountId20::from(*acc)) + .collect::>(), + ), + phantom: Default::default(), + }, + transfer_members: Default::default(), + } +} diff --git a/gasp-node/rollup/node/src/cli.rs b/gasp-node/rollup/node/src/cli.rs new file mode 100644 index 000000000..4b9694d78 --- /dev/null +++ b/gasp-node/rollup/node/src/cli.rs @@ -0,0 +1,66 @@ +use sc_cli::RunCmd; + +#[derive(Debug, clap::Parser)] +pub struct Cli { + #[arg(long, env, default_value_t = false, conflicts_with_all = &["chain_genesis_salt"], global = true)] + pub randomize_chain_genesis_salt: bool, + + #[arg(long, env, conflicts_with_all = &["randomize_chain_genesis_salt"], global = true)] + pub chain_genesis_salt: Option, + + #[command(subcommand)] + pub subcommand: Option, + + #[arg(long, value_parser, value_delimiter = ',', global = true)] + pub override_eth_sequencers: Vec, + + #[arg(long, value_parser, value_delimiter = ',', global = true)] + pub override_arb_sequencers: Vec, + + #[arg(long, value_parser, value_delimiter = ',', global = true)] + pub override_base_sequencers: Vec, + + #[clap(flatten)] + pub run: RunCmd, +} + +#[derive(Debug, clap::Subcommand)] +#[allow(clippy::large_enum_variant)] +pub enum Subcommand { + /// Key management cli utilities + #[command(subcommand)] + Key(sc_cli::KeySubcommand), + + /// Build a chain specification. + BuildSpec(sc_cli::BuildSpecCmd), + + /// Validate blocks. + CheckBlock(sc_cli::CheckBlockCmd), + + /// Export blocks. + ExportBlocks(sc_cli::ExportBlocksCmd), + + /// Export the state of a given block into a chain spec. + ExportState(sc_cli::ExportStateCmd), + + /// Import blocks. + ImportBlocks(sc_cli::ImportBlocksCmd), + + /// Remove the whole chain. + PurgeChain(sc_cli::PurgeChainCmd), + + /// Revert the chain to a previous state. + Revert(sc_cli::RevertCmd), + + /// Sub-commands concerned with benchmarking. + #[command(subcommand)] + Benchmark(frame_benchmarking_cli::BenchmarkCmd), + + /// Try-runtime has migrated to a standalone CLI + /// (). The subcommand exists as a stub and + /// deprecation notice. It will be removed entirely some time after Janurary 2024. + TryRuntime, + + /// Db meta columns information. + ChainInfo(sc_cli::ChainInfoCmd), +} diff --git a/gasp-node/rollup/node/src/command.rs b/gasp-node/rollup/node/src/command.rs new file mode 100644 index 000000000..d78f46e41 --- /dev/null +++ b/gasp-node/rollup/node/src/command.rs @@ -0,0 +1,324 @@ +use crate::{ + benchmarking::{inherent_benchmark_data, RemarkBuilder, TransferKeepAliveBuilder}, + chain_spec, + cli::{Cli, Subcommand}, + service, + service::Block, +}; +use frame_benchmarking_cli::{BenchmarkCmd, ExtrinsicFactory, SUBSTRATE_REFERENCE_HARDWARE}; +use futures::executor::block_on; +use rollup_runtime::{AccountId, Signer}; +use sc_cli::SubstrateCli; +use sc_executor::{WasmExecutor, DEFAULT_HEAP_ALLOC_STRATEGY}; +use sc_service::{Deref, PartialComponents}; +use sp_core::Pair; +use sp_keyring::Sr25519Keyring; +use sp_runtime::traits::{HashingFor, IdentifyAccount}; +use std::{ + convert::TryInto, + sync::{Arc, Mutex}, + time::Duration, +}; + +pub enum EvmChain { + Holesky, + Anvil, + Reth, +} + +pub enum InitialSequencersSet { + Collators, + Set(Vec), + Empty, +} + +impl SubstrateCli for Cli { + fn impl_name() -> String { + "Rollup Node".into() + } + + fn impl_version() -> String { + env!("SUBSTRATE_CLI_IMPL_VERSION").into() + } + + fn description() -> String { + env!("CARGO_PKG_DESCRIPTION").into() + } + + fn author() -> String { + env!("CARGO_PKG_AUTHORS").into() + } + + fn support_url() -> String { + "support.anonymous.an".into() + } + + fn copyright_start_year() -> i32 { + 2017 + } + + fn load_spec(&self, id: &str) -> Result, String> { + let parse_accounts = |account: &String| -> AccountId { + if account.starts_with("0x") { + let mut expected_hex_account = [0u8; 20]; + hex::decode_to_slice(&account[2..], &mut expected_hex_account) + .expect("Eth sequencer account must be 20 bytes"); + expected_hex_account.into() + } else { + crate::chain_spec::get_account_id_from_seed::(account) + } + }; + + let eth_sequencers = if self.override_eth_sequencers.is_empty() { + [crate::chain_spec::get_account_id_from_seed::("Baltathar")] + .to_vec() + } else { + self.override_eth_sequencers.iter().map(parse_accounts).collect() + }; + + let arb_sequencers = if self.override_arb_sequencers.is_empty() { + [crate::chain_spec::get_account_id_from_seed::("Charleth")] + .to_vec() + } else { + self.override_arb_sequencers.iter().map(parse_accounts).collect() + }; + + let base_sequencers = if self.override_base_sequencers.is_empty() { + [crate::chain_spec::get_account_id_from_seed::("Dorothy")] + .to_vec() + } else { + self.override_base_sequencers.iter().map(parse_accounts).collect() + }; + Ok(match id { + "" | "rollup-local" => + Box::new(chain_spec::rollup_local_config(self.randomize_chain_genesis_salt, self.chain_genesis_salt.clone(), eth_sequencers, arb_sequencers, base_sequencers, EvmChain::Anvil, + None + )), + "rollup-local-seq" => Box::new(chain_spec::rollup_local_config(self.randomize_chain_genesis_salt, self.chain_genesis_salt.clone(), eth_sequencers, arb_sequencers, base_sequencers, EvmChain::Anvil, + None + )), + "anvil" => Box::new(chain_spec::rollup_local_config(self.randomize_chain_genesis_salt, self.chain_genesis_salt.clone(), eth_sequencers, arb_sequencers, base_sequencers, EvmChain::Anvil, + None + )), + "reth" => Box::new(chain_spec::rollup_local_config(self.randomize_chain_genesis_salt, self.chain_genesis_salt.clone(), eth_sequencers, arb_sequencers, base_sequencers, EvmChain::Reth, + None + )), + "holesky" => Box::new(chain_spec::rollup_local_config(self.randomize_chain_genesis_salt, self.chain_genesis_salt.clone(), eth_sequencers, arb_sequencers, base_sequencers, EvmChain::Holesky, + Some(String::from("https://polkadot.js.org/apps/?rpc=wss%3A%2F%2Frollup-holesky-rpc.gasp.xyz#/extrinsics/decode/")) + )), + "ethereum-mainnet" => Box::new(chain_spec::ethereum_mainnet( Some(String::from("https://polkadot.js.org/apps/?rpc=wss%3A%2F%2Frollup-prod-rpc.gasp.xyz#/extrinsics/decode/")) + )), + path => + Box::new(chain_spec::ChainSpec::from_json_file(std::path::PathBuf::from(path))?), + }) + } +} + +/// Parse and run command line arguments +pub fn run() -> sc_cli::Result<()> { + let cli = Cli::from_args(); + + match &cli.subcommand { + Some(Subcommand::Key(cmd)) => cmd.run(&cli), + Some(Subcommand::BuildSpec(cmd)) => { + let runner = cli.create_runner(cmd)?; + runner.sync_run(|config| cmd.run(config.chain_spec, config.network)) + }, + Some(Subcommand::CheckBlock(cmd)) => { + let runner = cli.create_runner(cmd)?; + runner.async_run(|config| { + let PartialComponents { client, task_manager, import_queue, .. } = + service::new_partial(&config)?; + Ok((cmd.run(client, import_queue), task_manager)) + }) + }, + Some(Subcommand::ExportBlocks(cmd)) => { + let runner = cli.create_runner(cmd)?; + runner.async_run(|config| { + let PartialComponents { client, task_manager, .. } = service::new_partial(&config)?; + Ok((cmd.run(client, config.database), task_manager)) + }) + }, + Some(Subcommand::ExportState(cmd)) => { + let runner = cli.create_runner(cmd)?; + runner.async_run(|config| { + let PartialComponents { client, task_manager, .. } = service::new_partial(&config)?; + Ok((cmd.run(client, config.chain_spec), task_manager)) + }) + }, + Some(Subcommand::ImportBlocks(cmd)) => { + let runner = cli.create_runner(cmd)?; + runner.async_run(|config| { + let PartialComponents { client, task_manager, import_queue, .. } = + service::new_partial(&config)?; + Ok((cmd.run(client, import_queue), task_manager)) + }) + }, + Some(Subcommand::PurgeChain(cmd)) => { + let runner = cli.create_runner(cmd)?; + runner.sync_run(|config| cmd.run(config.database)) + }, + Some(Subcommand::Revert(cmd)) => { + let runner = cli.create_runner(cmd)?; + runner.async_run(|config| { + let PartialComponents { client, task_manager, backend, .. } = + service::new_partial(&config)?; + let aux_revert = Box::new(|client, _, blocks| { + sc_consensus_grandpa::revert(client, blocks)?; + Ok(()) + }); + Ok((cmd.run(client, backend, Some(aux_revert)), task_manager)) + }) + }, + Some(Subcommand::Benchmark(cmd)) => { + let runner = cli.create_runner(cmd)?; + + runner.sync_run(|config| { + // This switch needs to be in the client, since the client decides + // which sub-commands it wants to support. + match cmd { + BenchmarkCmd::Pallet(cmd) => { + if !cfg!(feature = "runtime-benchmarks") { + return Err( + "Runtime benchmarking wasn't enabled when building the node. \ + You can enable it with `--features runtime-benchmarks`." + .into(), + ) + } + + cmd.run::, sp_statement_store::runtime_api::HostFunctions>(config) + }, + BenchmarkCmd::Block(cmd) => { + let PartialComponents { client, .. } = service::new_partial(&config)?; + cmd.run(client) + }, + #[cfg(not(feature = "runtime-benchmarks"))] + BenchmarkCmd::Storage(_) => Err( + "Storage benchmarking can be enabled with `--features runtime-benchmarks`." + .into(), + ), + #[cfg(feature = "runtime-benchmarks")] + BenchmarkCmd::Storage(cmd) => { + let PartialComponents { client, backend, .. } = + service::new_partial(&config)?; + let db = backend.expose_db(); + let storage = backend.expose_storage(); + + cmd.run(config, client, db, storage) + }, + BenchmarkCmd::Overhead(cmd) => { + let executor = WasmExecutor::builder() + .with_execution_method(config.wasm_method) + .with_onchain_heap_alloc_strategy(DEFAULT_HEAP_ALLOC_STRATEGY) + .with_offchain_heap_alloc_strategy(DEFAULT_HEAP_ALLOC_STRATEGY) + .with_max_runtime_instances(config.max_runtime_instances) + .with_runtime_cache_size(config.runtime_cache_size) + .build(); + + let (c, _, _, _) = + sc_service::new_full_parts::( + &config, None, executor, + )?; + + let client = Arc::new(Mutex::new(c)); + let ext_builder = RemarkBuilder::new(client.clone()); + + let first_block_inherent = + block_on(inherent_benchmark_data([0u8; 32], Duration::from_millis(0))) + .unwrap(); + + let first_block_seed = sp_ver::extract_inherent_data(&first_block_inherent) + .map_err(|_| { + sp_blockchain::Error::Backend(String::from( + "cannot read random seed from inherents data", + )) + })?; + + let second_block_inherent = block_on(inherent_benchmark_data( + first_block_seed.seed.as_bytes().try_into().unwrap(), + Duration::from_millis(6000), + )) + .unwrap(); + + cmd.run_ver( + config, + client, + (first_block_inherent, second_block_inherent), + &ext_builder, + ) + }, + BenchmarkCmd::Extrinsic(cmd) => { + let executor = WasmExecutor::builder() + .with_execution_method(config.wasm_method) + .with_onchain_heap_alloc_strategy(DEFAULT_HEAP_ALLOC_STRATEGY) + .with_offchain_heap_alloc_strategy(DEFAULT_HEAP_ALLOC_STRATEGY) + .with_max_runtime_instances(config.max_runtime_instances) + .with_runtime_cache_size(config.runtime_cache_size) + .build(); + + let (c, _, _, _) = + sc_service::new_full_parts::( + &config, None, executor, + )?; + + let client = Arc::new(Mutex::new(c)); + // Register the *Remark* and *TKA* builders. + let ext_factory = ExtrinsicFactory(vec![ + Box::new(RemarkBuilder::new(client.clone())), + Box::new(TransferKeepAliveBuilder::new( + client.clone(), + Signer::from( + crate::benchmarking::get_eth_pair_from_seed("Alith").public(), + ) + .into_account(), + Default::default(), + )), + ]); + + let first_block_inherent = + block_on(inherent_benchmark_data([0u8; 32], Duration::from_millis(0))) + .unwrap(); + + let first_block_seed = sp_ver::extract_inherent_data(&first_block_inherent) + .map_err(|_| { + sp_blockchain::Error::Backend(String::from( + "cannot read random seed from inherents data", + )) + })?; + + let second_block_inherent = block_on(inherent_benchmark_data( + first_block_seed.seed.as_bytes().try_into().unwrap(), + Duration::from_millis(6000), + )) + .unwrap(); + + cmd.run_ver( + client, + (first_block_inherent, second_block_inherent), + Vec::new(), + &ext_factory, + ) + }, + BenchmarkCmd::Machine(cmd) => + cmd.run(&config, SUBSTRATE_REFERENCE_HARDWARE.clone()), + } + }) + }, + #[cfg(feature = "try-runtime")] + Some(Subcommand::TryRuntime) => Err(try_runtime_cli::DEPRECATION_NOTICE.into()), + #[cfg(not(feature = "try-runtime"))] + Some(Subcommand::TryRuntime) => Err("TryRuntime wasn't enabled when building the node. \ + You can enable it with `--features try-runtime`." + .into()), + Some(Subcommand::ChainInfo(cmd)) => { + let runner = cli.create_runner(cmd)?; + runner.sync_run(|config| cmd.run::(&config)) + }, + None => { + let runner = cli.create_runner(&cli.run)?; + runner.run_node_until_exit(|config| async move { + service::new_full(config).map_err(sc_cli::Error::Service) + }) + }, + } +} diff --git a/gasp-node/rollup/node/src/main.rs b/gasp-node/rollup/node/src/main.rs new file mode 100644 index 000000000..852691041 --- /dev/null +++ b/gasp-node/rollup/node/src/main.rs @@ -0,0 +1,15 @@ +//! Substrate Node Template CLI library. +#![warn(missing_docs)] + +mod chain_spec; +#[macro_use] +mod service; +mod benchmarking; +mod cli; +mod command; +mod metrics; +mod rpc; + +fn main() -> sc_cli::Result<()> { + command::run() +} diff --git a/gasp-node/rollup/node/src/metrics.rs b/gasp-node/rollup/node/src/metrics.rs new file mode 100644 index 000000000..d168a0c59 --- /dev/null +++ b/gasp-node/rollup/node/src/metrics.rs @@ -0,0 +1,245 @@ +//! A collection of node-specific RPC methods. +//! Substrate provides the `sc-rpc` crate, which defines the core RPC layer +//! used by Substrate nodes. This file extends those RPC definitions with +//! capabilities that are specific to this project's runtime configuration. + +#![warn(missing_docs)] + +use std::sync::Arc; + +use sp_api::ProvideRuntimeApi; +use sp_blockchain::HeaderBackend; + +use rollup_runtime::runtime_config::{ + opaque::Block, + types::{AccountId, Balance, TokenId}, +}; + +use rolldown_runtime_api::RolldownRuntimeApi; +use sp_runtime::SaturatedConversion; +use xyk_runtime_api::XykRuntimeApi; + +use substrate_prometheus_endpoint::{ + MetricSource, Opts, PrometheusError, Registry, SourcedCounter, SourcedGauge, +}; + +pub fn register( + registry: Option<&Registry>, + client: Arc, +) -> Result<(), PrometheusError> { + if let Some(registry) = registry { + GaspMetricsCounter::register(registry, client.clone())?; + RolldownLastProccessedRequestOnL2Counter::register(registry, client.clone())?; + RolldownNumberOfPendingRequestsGauge::register(registry, client.clone())?; + } + Ok(()) +} + +pub struct RolldownLastProccessedRequestOnL2Counter(Arc); + +impl Clone for RolldownLastProccessedRequestOnL2Counter { + fn clone(&self) -> Self { + RolldownLastProccessedRequestOnL2Counter(self.0.clone()) + } +} + +impl RolldownLastProccessedRequestOnL2Counter +where + C: ProvideRuntimeApi, + C: Send + Sync + 'static, + C: HeaderBackend, + C::Api: RolldownRuntimeApi< + Block, + pallet_rolldown::messages::L1Update, + pallet_rolldown::messages::Chain, + >, +{ + /// Registers the `RolldownLastProccessedRequestOnL2Counter` metric whose value is + /// obtained from the given `C` as in Client that would implement the api we need. + pub fn register(registry: &Registry, client: Arc) -> Result<(), PrometheusError> { + substrate_prometheus_endpoint::register( + SourcedCounter::new( + &Opts::new( + "substrate_rolldown_last_processed_request_on_l2", + "Last Processed Request On L2", + ) + .variable_label("for_L1"), + RolldownLastProccessedRequestOnL2Counter(client.clone()), + )?, + registry, + )?; + Ok(()) + } +} + +impl MetricSource for RolldownLastProccessedRequestOnL2Counter +where + C: ProvideRuntimeApi, + C: Send + Sync + 'static, + C: HeaderBackend, + C::Api: RolldownRuntimeApi< + Block, + pallet_rolldown::messages::L1Update, + pallet_rolldown::messages::Chain, + >, +{ + type N = u64; + + fn collect(&self, mut set: impl FnMut(&[&str], Self::N)) { + let at = self.0.info().best_hash; + set( + &["Ethereum"], + self.0 + .runtime_api() + .get_last_processed_request_on_l2(at, pallet_rolldown::messages::Chain::Ethereum) + .unwrap_or(None) + .unwrap_or_default() + .saturated_into::(), + ); + } +} + +pub struct RolldownNumberOfPendingRequestsGauge(Arc); + +impl Clone for RolldownNumberOfPendingRequestsGauge { + fn clone(&self) -> Self { + RolldownNumberOfPendingRequestsGauge(self.0.clone()) + } +} + +impl RolldownNumberOfPendingRequestsGauge +where + C: ProvideRuntimeApi, + C: Send + Sync + 'static, + C: HeaderBackend, + C::Api: RolldownRuntimeApi< + Block, + pallet_rolldown::messages::L1Update, + pallet_rolldown::messages::Chain, + >, +{ + /// Registers the `RolldownNumberOfPendingRequestsGauge` metric whose value is + /// obtained from the given `C` as in Client that would implement the api we need. + pub fn register(registry: &Registry, client: Arc) -> Result<(), PrometheusError> { + substrate_prometheus_endpoint::register( + SourcedCounter::new( + &Opts::new( + "substrate_rolldown_number_of_pending_requests", + "Number Of Pending Requests", + ) + .variable_label("for_L1"), + RolldownNumberOfPendingRequestsGauge(client.clone()), + )?, + registry, + )?; + Ok(()) + } +} + +impl MetricSource for RolldownNumberOfPendingRequestsGauge +where + C: ProvideRuntimeApi, + C: Send + Sync + 'static, + C: HeaderBackend, + C::Api: RolldownRuntimeApi< + Block, + pallet_rolldown::messages::L1Update, + pallet_rolldown::messages::Chain, + >, +{ + type N = u64; + + fn collect(&self, mut set: impl FnMut(&[&str], Self::N)) { + let at = self.0.info().best_hash; + set( + &["Ethereum"], + self.0 + .runtime_api() + .get_number_of_pending_requests(at, pallet_rolldown::messages::Chain::Ethereum) + .unwrap_or(None) + .unwrap_or_default() + .saturated_into::(), + ); + } +} + +pub struct GaspMetricsCounter(Arc); + +impl Clone for GaspMetricsCounter { + fn clone(&self) -> Self { + GaspMetricsCounter(self.0.clone()) + } +} + +impl GaspMetricsCounter +where + C: ProvideRuntimeApi, + C: Send + Sync + 'static, + C: HeaderBackend, + // use the concrete type sp_runtime::AccountId20 here because whatever implements MetricSource needs to be + // Send + Sync + Clone and the AccountId abstraction we get from Signer and IdentifyAccount do not provide these bounds + C::Api: XykRuntimeApi, + C::Api: RolldownRuntimeApi< + Block, + pallet_rolldown::messages::L1Update, + pallet_rolldown::messages::Chain, + >, +{ + /// Registers the `GaspMetricsCounter` metric whose value is + /// obtained from the given `C` as in Client that would implement the api we need. + pub fn register(registry: &Registry, client: Arc) -> Result<(), PrometheusError> { + substrate_prometheus_endpoint::register( + SourcedCounter::new( + &Opts::new("substrate_gasp_metrics", "Gasp Metrics").variable_label("Counter_for"), + GaspMetricsCounter(client.clone()), + )?, + registry, + )?; + Ok(()) + } +} + +impl MetricSource for GaspMetricsCounter +where + C: ProvideRuntimeApi, + C: Send + Sync + 'static, + C: HeaderBackend, + // use the concrete type sp_runtime::AccountId20 here because whatever implements MetricSource needs to be + // Send + Sync + Clone and the AccountId abstraction we get from Signer and IdentifyAccount do not provide these bounds + C::Api: XykRuntimeApi, + C::Api: RolldownRuntimeApi< + Block, + pallet_rolldown::messages::L1Update, + pallet_rolldown::messages::Chain, + >, +{ + type N = u64; + + fn collect(&self, mut set: impl FnMut(&[&str], Self::N)) { + let at = self.0.info().best_hash; + set( + &["Total Deposits"], + self.0 + .runtime_api() + .get_total_number_of_deposits(at) + .unwrap_or_default() + .saturated_into::(), + ); + set( + &["Total Withdrawals"], + self.0 + .runtime_api() + .get_total_number_of_withdrawals(at) + .unwrap_or_default() + .saturated_into::(), + ); + set( + &["Total Swaps"], + self.0 + .runtime_api() + .get_total_number_of_swaps(at) + .unwrap_or_default() + .saturated_into::(), + ); + } +} diff --git a/gasp-node/rollup/node/src/rpc.rs b/gasp-node/rollup/node/src/rpc.rs new file mode 100644 index 000000000..f09c9c852 --- /dev/null +++ b/gasp-node/rollup/node/src/rpc.rs @@ -0,0 +1,88 @@ +//! A collection of node-specific RPC methods. +//! Substrate provides the `sc-rpc` crate, which defines the core RPC layer +//! used by Substrate nodes. This file extends those RPC definitions with +//! capabilities that are specific to this project's runtime configuration. + +#![warn(missing_docs)] + +use std::sync::Arc; + +use jsonrpsee::RpcModule; +use sc_client_api::BlockBackend; +use sc_transaction_pool_api::TransactionPool; +use sp_api::ProvideRuntimeApi; +use sp_block_builder::BlockBuilder; +use sp_blockchain::{Error as BlockChainError, HeaderBackend, HeaderMetadata}; + +use rollup_runtime::runtime_config::{ + opaque::Block, + types::{AccountId, Balance, Nonce, TokenId}, +}; + +use metamask_signature_rpc::MetamaskSignatureApiServer; +use ver_api::VerNonceApi; + +pub use sc_rpc_api::DenyUnsafe; + +/// Full client dependencies. +pub struct FullDeps { + /// The client instance to use. + pub client: Arc, + /// Transaction pool instance. + pub pool: Arc

, + /// Whether to deny unsafe calls + pub deny_unsafe: DenyUnsafe, +} + +/// Instantiate all full RPC extensions. +pub fn create_full( + deps: FullDeps, +) -> Result, Box> +where + C: ProvideRuntimeApi, + C: HeaderBackend + + HeaderMetadata + + BlockBackend + + 'static, + C: Send + Sync + 'static, + C::Api: pallet_transaction_payment_rpc::TransactionPaymentRuntimeApi, + C::Api: substrate_frame_rpc_system::AccountNonceApi, + C::Api: xyk_runtime_api::XykRuntimeApi, + C::Api: proof_of_stake_rpc::ProofOfStakeRuntimeApi, + C::Api: metamask_signature_rpc::MetamaskSignatureRuntimeApi, + C::Api: rolldown_runtime_api::RolldownRuntimeApi< + Block, + pallet_rolldown::messages::L1Update, + pallet_rolldown::messages::Chain, + >, + C::Api: pallet_market::MarketRuntimeApi, + C::Api: BlockBuilder, + C::Api: VerNonceApi, + P: TransactionPool + 'static, +{ + use market_rpc::{Market, MarketApiServer}; + use metamask_signature_rpc::MetamaskSignature; + use pallet_transaction_payment_rpc::{TransactionPayment, TransactionPaymentApiServer}; + use proof_of_stake_rpc::{ProofOfStake, ProofOfStakeApiServer}; + use rolldown_rpc::{Rolldown, RolldownApiServer}; + use substrate_frame_rpc_system::{System, SystemApiServer}; + use xyk_rpc::{Xyk, XykApiServer}; + + let mut module = RpcModule::new(()); + let FullDeps { client, pool, deny_unsafe } = deps; + + module.merge(System::new(client.clone(), pool, deny_unsafe).into_rpc())?; + module.merge(TransactionPayment::new(client.clone()).into_rpc())?; + module.merge(Xyk::new(client.clone()).into_rpc())?; + module.merge(Rolldown::new(client.clone()).into_rpc())?; + module.merge(ProofOfStake::new(client.clone()).into_rpc())?; + module.merge(MetamaskSignature::new(client.clone()).into_rpc())?; + module.merge(Market::new(client).into_rpc())?; + + // Extend this RPC with a custom API by using the following syntax. + // `YourRpcStruct` should have a reference to a client, which is needed + // to call into the runtime. + // `module.merge(YourRpcTrait::into_rpc(YourRpcStruct::new(ReferenceToClient, ...)))?;` + + Ok(module) +} diff --git a/gasp-node/rollup/node/src/service.rs b/gasp-node/rollup/node/src/service.rs new file mode 100644 index 000000000..31045555f --- /dev/null +++ b/gasp-node/rollup/node/src/service.rs @@ -0,0 +1,340 @@ +//! Service and ServiceFactory implementation. Specialized wrapper over substrate service. + +use futures::FutureExt; +pub use rollup_runtime::{self, opaque::Block, RuntimeApi}; +use sc_client_api::{Backend, BlockBackend}; +use sc_consensus_aura::{ImportQueueParams, SlotProportion, StartAuraParams}; +use sc_consensus_grandpa::SharedVoterState; +pub use sc_executor::NativeElseWasmExecutor; +use sc_service::{error::Error as ServiceError, Configuration, TaskManager, WarpSyncParams}; +use sc_telemetry::{Telemetry, TelemetryWorker}; +use sc_transaction_pool_api::OffchainTransactionPoolFactory; +use sp_consensus_aura::sr25519::AuthorityPair as AuraPair; +use std::{sync::Arc, time::Duration}; + +// Our native executor instance. +pub struct ExecutorDispatch; + +impl sc_executor::NativeExecutionDispatch for ExecutorDispatch { + /// Only enable the benchmarking host functions when we actually want to benchmark. + #[cfg(feature = "runtime-benchmarks")] + type ExtendHostFunctions = frame_benchmarking::benchmarking::HostFunctions; + /// Otherwise we only use the default Substrate host functions. + #[cfg(not(feature = "runtime-benchmarks"))] + type ExtendHostFunctions = (); + + fn dispatch(method: &str, data: &[u8]) -> Option> { + rollup_runtime::api::dispatch(method, data) + } + + fn native_version() -> sc_executor::NativeVersion { + rollup_runtime::native_version() + } +} + +pub(crate) type FullClient = + sc_service::TFullClient>; +type FullBackend = sc_service::TFullBackend; +type FullSelectChain = sc_consensus::LongestChain; + +/// The minimum period of blocks on which justifications will be +/// imported and generated. +const GRANDPA_JUSTIFICATION_PERIOD: u32 = 512; + +#[allow(clippy::type_complexity)] +pub fn new_partial( + config: &Configuration, +) -> Result< + sc_service::PartialComponents< + FullClient, + FullBackend, + FullSelectChain, + sc_consensus::DefaultImportQueue, + sc_transaction_pool::FullPool, + ( + sc_consensus_grandpa::GrandpaBlockImport< + FullBackend, + Block, + FullClient, + FullSelectChain, + >, + sc_consensus_grandpa::LinkHalf, + Option, + ), + >, + ServiceError, +> { + let telemetry = config + .telemetry_endpoints + .clone() + .filter(|x| !x.is_empty()) + .map(|endpoints| -> Result<_, sc_telemetry::Error> { + let worker = TelemetryWorker::new(16)?; + let telemetry = worker.handle().new_telemetry(endpoints); + Ok((worker, telemetry)) + }) + .transpose()?; + + let executor = sc_service::new_native_or_wasm_executor(config); + let (client, backend, keystore_container, task_manager) = + sc_service::new_full_parts::( + config, + telemetry.as_ref().map(|(_, telemetry)| telemetry.handle()), + executor, + )?; + let client = Arc::new(client); + + let telemetry = telemetry.map(|(worker, telemetry)| { + task_manager.spawn_handle().spawn("telemetry", None, worker.run()); + telemetry + }); + + let select_chain = sc_consensus::LongestChain::new(backend.clone()); + + let transaction_pool = sc_transaction_pool::BasicPool::new_full( + config.transaction_pool.clone(), + config.role.is_authority().into(), + config.prometheus_registry(), + task_manager.spawn_essential_handle(), + client.clone(), + ); + + let (grandpa_block_import, grandpa_link) = sc_consensus_grandpa::block_import( + client.clone(), + GRANDPA_JUSTIFICATION_PERIOD, + &client, + select_chain.clone(), + telemetry.as_ref().map(|x| x.handle()), + )?; + + let slot_duration = sc_consensus_aura::slot_duration(&*client)?; + + let import_queue = + sc_consensus_aura::import_queue::(ImportQueueParams { + block_import: grandpa_block_import.clone(), + justification_import: Some(Box::new(grandpa_block_import.clone())), + client: client.clone(), + create_inherent_data_providers: move |_, ()| async move { + let timestamp = sp_timestamp::InherentDataProvider::from_system_time(); + + let slot = + sp_consensus_aura::inherents::InherentDataProvider::from_timestamp_and_slot_duration( + *timestamp, + slot_duration, + ); + + Ok((slot, timestamp)) + }, + spawner: &task_manager.spawn_essential_handle(), + registry: config.prometheus_registry(), + check_for_equivocation: Default::default(), + telemetry: telemetry.as_ref().map(|x| x.handle()), + compatibility_mode: Default::default(), + })?; + + Ok(sc_service::PartialComponents { + client, + backend, + task_manager, + import_queue, + keystore_container, + select_chain, + transaction_pool, + other: (grandpa_block_import, grandpa_link, telemetry), + }) +} + +/// Builds a new service for a full client. +pub fn new_full(config: Configuration) -> Result { + let sc_service::PartialComponents { + client, + backend, + mut task_manager, + import_queue, + keystore_container, + select_chain, + transaction_pool, + other: (block_import, grandpa_link, mut telemetry), + } = new_partial(&config)?; + + let mut net_config = sc_network::config::FullNetworkConfiguration::new(&config.network); + + let grandpa_protocol_name = sc_consensus_grandpa::protocol_standard_name( + &client.block_hash(0).ok().flatten().expect("Genesis block exists; qed"), + &config.chain_spec, + ); + let (grandpa_protocol_config, grandpa_notification_service) = + sc_consensus_grandpa::grandpa_peers_set_config(grandpa_protocol_name.clone()); + net_config.add_notification_protocol(grandpa_protocol_config); + + let warp_sync = Arc::new(sc_consensus_grandpa::warp_proof::NetworkProvider::new( + backend.clone(), + grandpa_link.shared_authority_set().clone(), + Vec::default(), + )); + + let (network, system_rpc_tx, tx_handler_controller, network_starter, sync_service) = + sc_service::build_network(sc_service::BuildNetworkParams { + config: &config, + net_config, + client: client.clone(), + transaction_pool: transaction_pool.clone(), + spawn_handle: task_manager.spawn_handle(), + import_queue, + block_announce_validator_builder: None, + warp_sync_params: Some(WarpSyncParams::WithProvider(warp_sync)), + block_relay: None, + })?; + + if config.offchain_worker.enabled { + task_manager.spawn_handle().spawn( + "offchain-workers-runner", + "offchain-worker", + sc_offchain::OffchainWorkers::new(sc_offchain::OffchainWorkerOptions { + runtime_api_provider: client.clone(), + is_validator: config.role.is_authority(), + keystore: Some(keystore_container.keystore()), + offchain_db: backend.offchain_storage(), + transaction_pool: Some(OffchainTransactionPoolFactory::new( + transaction_pool.clone(), + )), + network_provider: network.clone(), + enable_http_requests: true, + custom_extensions: |_| vec![], + }) + .run(client.clone(), task_manager.spawn_handle()) + .boxed(), + ); + } + + let role = config.role.clone(); + let force_authoring = config.force_authoring; + let backoff_authoring_blocks: Option<()> = None; + let name = config.network.node_name.clone(); + let enable_grandpa = !config.disable_grandpa; + let prometheus_registry = config.prometheus_registry().cloned(); + + let rpc_extensions_builder = { + let client = client.clone(); + let pool = transaction_pool.clone(); + + Box::new(move |deny_unsafe, _| { + let deps = + crate::rpc::FullDeps { client: client.clone(), pool: pool.clone(), deny_unsafe }; + crate::rpc::create_full(deps).map_err(Into::into) + }) + }; + + let _ = crate::metrics::register(prometheus_registry.as_ref(), client.clone()); + + let _rpc_handlers = sc_service::spawn_tasks(sc_service::SpawnTasksParams { + network: network.clone(), + client: client.clone(), + keystore: keystore_container.keystore(), + task_manager: &mut task_manager, + transaction_pool: transaction_pool.clone(), + rpc_builder: rpc_extensions_builder, + backend, + system_rpc_tx, + tx_handler_controller, + sync_service: sync_service.clone(), + config, + telemetry: telemetry.as_mut(), + })?; + + if role.is_authority() { + let proposer_factory = sc_basic_authorship_ver::ProposerFactory::new( + task_manager.spawn_handle(), + client.clone(), + transaction_pool.clone(), + prometheus_registry.as_ref(), + telemetry.as_ref().map(|x| x.handle()), + ); + + let slot_duration = sc_consensus_aura::slot_duration(&*client)?; + + let aura = sc_consensus_aura::start_aura::( + StartAuraParams { + slot_duration, + client, + select_chain, + block_import, + proposer_factory, + create_inherent_data_providers: move |_, ()| async move { + let timestamp = sp_timestamp::InherentDataProvider::from_system_time(); + + let slot = + sp_consensus_aura::inherents::InherentDataProvider::from_timestamp_and_slot_duration( + *timestamp, + slot_duration, + ); + + Ok((slot, timestamp)) + }, + force_authoring, + backoff_authoring_blocks, + keystore: keystore_container.keystore(), + sync_oracle: sync_service.clone(), + justification_sync_link: sync_service.clone(), + block_proposal_slot_portion: SlotProportion::new(2f32 / 3f32), + max_block_proposal_slot_portion: None, + telemetry: telemetry.as_ref().map(|x| x.handle()), + compatibility_mode: Default::default(), + }, + )?; + + // the AURA authoring task is considered essential, i.e. if it + // fails we take down the service with it. + task_manager + .spawn_essential_handle() + .spawn_blocking("aura", Some("block-authoring"), aura); + } + + if enable_grandpa { + // if the node isn't actively participating in consensus then it doesn't + // need a keystore, regardless of which protocol we use below. + let keystore = if role.is_authority() { Some(keystore_container.keystore()) } else { None }; + + let grandpa_config = sc_consensus_grandpa::Config { + // FIXME #1578 make this available through chainspec + gossip_duration: Duration::from_millis(333), + justification_generation_period: GRANDPA_JUSTIFICATION_PERIOD, + name: Some(name), + observer_enabled: false, + keystore, + local_role: role, + telemetry: telemetry.as_ref().map(|x| x.handle()), + protocol_name: grandpa_protocol_name, + }; + + // start the full GRANDPA voter + // NOTE: non-authorities could run the GRANDPA observer protocol, but at + // this point the full voter should provide better guarantees of block + // and vote data availability than the observer. The observer has not + // been tested extensively yet and having most nodes in a network run it + // could lead to finality stalls. + let grandpa_config = sc_consensus_grandpa::GrandpaParams { + config: grandpa_config, + link: grandpa_link, + network, + sync: Arc::new(sync_service), + notification_service: grandpa_notification_service, + voting_rule: sc_consensus_grandpa::VotingRulesBuilder::default().build(), + prometheus_registry, + shared_voter_state: SharedVoterState::empty(), + telemetry: telemetry.as_ref().map(|x| x.handle()), + offchain_tx_pool_factory: OffchainTransactionPoolFactory::new(transaction_pool), + }; + + // the GRANDPA voter task is considered infallible, i.e. + // if it fails we take down the service with it. + task_manager.spawn_essential_handle().spawn_blocking( + "grandpa-voter", + None, + sc_consensus_grandpa::run_grandpa_voter(grandpa_config)?, + ); + } + + network_starter.start_network(); + Ok(task_manager) +} diff --git a/gasp-node/rollup/runtime/Cargo.toml b/gasp-node/rollup/runtime/Cargo.toml new file mode 100644 index 000000000..5260acd67 --- /dev/null +++ b/gasp-node/rollup/runtime/Cargo.toml @@ -0,0 +1,249 @@ +[package] +name = "rollup-runtime" +version = "1.0.0" +description = "Mangata rollup runtime" +authors = ["Mangata Team"] +edition.workspace = true +license = "MIT-0" +publish = false +repository.workspace = true + +[package.metadata.docs.rs] +targets = ["x86_64-unknown-linux-gnu"] + +[build-dependencies] +substrate-wasm-builder = { workspace = true } + +[dependencies] +array-bytes = { workspace = true } +codec = { workspace = true, default-features = false, features = ["derive"] } +hex-literal = { workspace = true } +log = { workspace = true, default-features = false } +scale-info = { workspace = true, default-features = false, features = ["derive"] } +smallvec = "1.6.1" +static_assertions = "1.1.0" + +# Local Dependencies +pallet-bootstrap = { path = "../../pallets/bootstrap", default-features = false, version = "0.1.0" } +pallet-fee-lock = { path = "../../pallets/fee-lock", default-features = false } +pallet-issuance = { path = "../../pallets/issuance", default-features = false } +pallet-maintenance = { path = "../../pallets/maintenance", default-features = false } +pallet-market = { path = "../../pallets/market", default-features = false } +pallet-multipurpose-liquidity = { path = "../../pallets/multipurpose-liquidity", default-features = false } +pallet-metamask-signature = { path = "../../pallets/metamask-signature", default-features = false } +pallet-proof-of-stake = { path = "../../pallets/proof-of-stake", default-features = false, version = "0.1.0" } +pallet-stable-swap = { path = "../../pallets/stable-swap", default-features = false } +pallet-sudo-origin = { path = "../../pallets/sudo-origin", default-features = false } +pallet-xyk = { path = "../../pallets/xyk", default-features = false, version = "0.1.0" } +parachain-staking = { path = "../../pallets/parachain-staking", default-features = false } +xyk-runtime-api = { path = "../../pallets/xyk/runtime-api", default-features = false, version = "2.0.0" } +rolldown-runtime-api = { path = "../../pallets/rolldown/runtime-api", default-features = false, version = "2.0.0" } +proof-of-stake-runtime-api = { path = '../../pallets/proof-of-stake/runtime-api', default-features = false } +pallet-rolldown = { path = "../../pallets/rolldown", default-features = false } +pallet-sequencer-staking = { path = "../../pallets/sequencer-staking", default-features = false } +metamask-signature-runtime-api = { path = '../../pallets/metamask-signature-runtime-api', default-features = false } +pallet-crowdloan-rewards = { path = '../../pallets/crowdloan-rewards', default-features = false } + +# Substrate Dependencies +## Substrate Primitive Dependencies +mangata-support = { workspace = true, default-features = false } +mangata-types = { workspace = true, default-features = false } +pallet-utility-mangata = { workspace = true, default-features = false } +sp-api = { workspace = true, default-features = false } +sp-block-builder = { workspace = true, default-features = false } +sp-consensus-aura = { workspace = true, default-features = false } +sp-consensus-grandpa = { workspace = true, default-features = false } +sp-core = { workspace = true, default-features = false } +sp-inherents = { workspace = true, default-features = false } +sp-io = { workspace = true, default-features = false } +sp-offchain = { workspace = true, default-features = false } +sp-runtime = { workspace = true, default-features = false } +sp-session = { workspace = true, default-features = false } +sp-std = { workspace = true, default-features = false } +sp-storage = { workspace = true, default-features = false } +sp-transaction-pool = { workspace = true, default-features = false } +sp-ver = { workspace = true, default-features = false } +sp-version = { workspace = true, default-features = false } +sp-weights = { workspace = true, default-features = false } +sp-application-crypto = { workspace = true, default-features = false } +primitive-types = { version = "0.12.1", default-features = false, features = ["codec", "num-traits", "scale-info"] } + +## Substrate FRAME Dependencies +frame-benchmarking = { workspace = true, default-features = false, optional = true } +frame-executive = { workspace = true, default-features = false } +frame-support = { workspace = true, default-features = false } +frame-system = { workspace = true, default-features = false } +frame-system-benchmarking = { workspace = true, default-features = false, optional = true } +frame-system-rpc-runtime-api = { workspace = true, default-features = false } +frame-try-runtime = { workspace = true, default-features = false, optional = true } +ver-api = { workspace = true, default-features = false } + +## Substrate Pallet Dependencies +pallet-aura = { workspace = true, default-features = false } +pallet-grandpa = { workspace = true, default-features = false } +pallet-authorship = { workspace = true, default-features = false } +pallet-collective-mangata = { workspace = true, default-features = false } +pallet-identity = { workspace = true, default-features = false } +pallet-membership = { workspace = true, default-features = false } +pallet-proxy = { workspace = true, default-features = false } +pallet-session = { workspace = true, default-features = false } +pallet-sudo-mangata = { workspace = true, default-features = false } +pallet-timestamp = { workspace = true, default-features = false } +pallet-transaction-payment = { workspace = true, default-features = false } +pallet-transaction-payment-rpc-runtime-api = { workspace = true, default-features = false } +pallet-treasury = { workspace = true, default-features = false } +pallet-vesting-mangata = { workspace = true, default-features = false } + +# Open-Runtime-Module-Library Dependencies +orml-asset-registry = { workspace = true, default-features = false } +orml-tokens = { workspace = true, default-features = false } +orml-traits = { workspace = true, default-features = false } + +[features] +default = ["std"] +std = [ + "codec/std", + "frame-benchmarking/std", + "frame-executive/std", + "frame-support/std", + "frame-system-benchmarking/std", + "frame-system-rpc-runtime-api/std", + "frame-system/std", + "frame-try-runtime/std", + "log/std", + "mangata-support/std", + "mangata-types/std", + "metamask-signature-runtime-api/std", + "orml-asset-registry/std", + "orml-tokens/std", + "orml-traits/std", + "pallet-aura/std", + "pallet-authorship/std", + "pallet-bootstrap/std", + "pallet-collective-mangata/std", + "pallet-crowdloan-rewards/std", + "pallet-fee-lock/std", + "pallet-grandpa/std", + "pallet-identity/std", + "pallet-issuance/std", + "pallet-market/std", + "pallet-maintenance/std", + "pallet-membership/std", + "pallet-metamask-signature/std", + "pallet-multipurpose-liquidity/std", + "pallet-proof-of-stake/std", + "pallet-proxy/std", + "pallet-rolldown/std", + "pallet-sequencer-staking/std", + "pallet-session/std", + "pallet-stable-swap/std", + "pallet-sudo-mangata/std", + "pallet-sudo-origin/std", + "pallet-timestamp/std", + "pallet-transaction-payment-rpc-runtime-api/std", + "pallet-transaction-payment/std", + "pallet-treasury/std", + "pallet-utility-mangata/std", + "pallet-vesting-mangata/std", + "pallet-xyk/std", + "parachain-staking/std", + "proof-of-stake-runtime-api/std", + "rolldown-runtime-api/std", + "scale-info/std", + "sp-api/std", + "sp-application-crypto/std", + "sp-block-builder/std", + "sp-consensus-aura/std", + "sp-consensus-grandpa/std", + "sp-core/std", + "sp-inherents/std", + "sp-io/std", + "sp-offchain/std", + "sp-runtime/std", + "sp-session/std", + "sp-std/std", + "sp-storage/std", + "sp-transaction-pool/std", + "sp-ver/std", + "sp-version/std", + "sp-weights/std", + "ver-api/std", + "xyk-runtime-api/std", +] + +try-runtime = [ + "frame-executive/try-runtime", + "frame-support/try-runtime", + "frame-system/try-runtime", + "frame-try-runtime", + "orml-asset-registry/try-runtime", + "orml-tokens/try-runtime", + "pallet-aura/try-runtime", + "pallet-authorship/try-runtime", + "pallet-bootstrap/try-runtime", + "pallet-collective-mangata/try-runtime", + "pallet-crowdloan-rewards/try-runtime", + "pallet-fee-lock/try-runtime", + "pallet-grandpa/try-runtime", + "pallet-identity/try-runtime", + "pallet-issuance/try-runtime", + "pallet-market/try-runtime", + "pallet-maintenance/try-runtime", + "pallet-membership/try-runtime", + "pallet-multipurpose-liquidity/try-runtime", + "pallet-proof-of-stake/try-runtime", + "pallet-proxy/try-runtime", + "pallet-rolldown/try-runtime", + "pallet-sequencer-staking/try-runtime", + "pallet-session/try-runtime", + "pallet-stable-swap/try-runtime", + "pallet-sudo-mangata/try-runtime", + "pallet-sudo-origin/try-runtime", + "pallet-timestamp/try-runtime", + "pallet-transaction-payment/try-runtime", + "pallet-treasury/try-runtime", + "pallet-utility-mangata/try-runtime", + "pallet-vesting-mangata/try-runtime", + "pallet-xyk/try-runtime", + "parachain-staking/try-runtime", + "sp-runtime/try-runtime", +] + +fast-runtime = ["unlocked"] +unlocked = [] + +runtime-benchmarks = [ + "frame-benchmarking/runtime-benchmarks", + "frame-support/runtime-benchmarks", + "frame-system-benchmarking", + "frame-system-benchmarking/runtime-benchmarks", + "frame-system/runtime-benchmarks", + "mangata-support/runtime-benchmarks", + "orml-asset-registry/runtime-benchmarks", + "orml-tokens/runtime-benchmarks", + "pallet-bootstrap/runtime-benchmarks", + "pallet-collective-mangata/runtime-benchmarks", + "pallet-crowdloan-rewards/runtime-benchmarks", + "pallet-fee-lock/runtime-benchmarks", + "pallet-grandpa/runtime-benchmarks", + "pallet-identity/runtime-benchmarks", + "pallet-issuance/runtime-benchmarks", + "pallet-market/runtime-benchmarks", + "pallet-maintenance/runtime-benchmarks", + "pallet-membership/runtime-benchmarks", + "pallet-multipurpose-liquidity/runtime-benchmarks", + "pallet-proof-of-stake/runtime-benchmarks", + "pallet-proxy/runtime-benchmarks", + "pallet-rolldown/runtime-benchmarks", + "pallet-sequencer-staking/runtime-benchmarks", + "pallet-session/runtime-benchmarks", + "pallet-stable-swap/runtime-benchmarks", + "pallet-sudo-mangata/runtime-benchmarks", + "pallet-timestamp/runtime-benchmarks", + "pallet-treasury/runtime-benchmarks", + "pallet-utility-mangata/runtime-benchmarks", + "pallet-vesting-mangata/runtime-benchmarks", + "pallet-xyk/runtime-benchmarks", + "parachain-staking/runtime-benchmarks", + "sp-runtime/runtime-benchmarks", +] diff --git a/gasp-node/rollup/runtime/build.rs b/gasp-node/rollup/runtime/build.rs new file mode 100644 index 000000000..c03d61853 --- /dev/null +++ b/gasp-node/rollup/runtime/build.rs @@ -0,0 +1,10 @@ +fn main() { + #[cfg(feature = "std")] + { + substrate_wasm_builder::WasmBuilder::new() + .with_current_project() + .export_heap_base() + .import_memory() + .build(); + } +} diff --git a/gasp-node/rollup/runtime/integration-test/Cargo.toml b/gasp-node/rollup/runtime/integration-test/Cargo.toml new file mode 100644 index 000000000..aad3c456d --- /dev/null +++ b/gasp-node/rollup/runtime/integration-test/Cargo.toml @@ -0,0 +1,50 @@ +[package] +name = "rollup-runtime-integration-test" +version = "0.1.0" +edition = "2021" + +[dev-dependencies] +env_logger.workspace = true +log.workspace = true + +# Substrate +frame-support = { workspace = true } +frame-system = { workspace = true } +mangata-support = { workspace = true, default-features = false } +mangata-types = { workspace = true } +pallet-balances = { workspace = true } +pallet-identity = { workspace = true } +pallet-membership = { workspace = true } +pallet-proxy = { workspace = true } +pallet-session = { workspace = true } +pallet-transaction-payment = { workspace = true } +sp-io = { workspace = true } +sp-runtime = { workspace = true } +sp-storage = { workspace = true } +hex-literal = { workspace = true } +codec = { workspace = true } + +# Open-Runtime-Module-Library Dependencies +orml-asset-registry = { workspace = true } +orml-tokens = { workspace = true } +orml-traits = { workspace = true } + +# Local +pallet-bootstrap = { path = "../../../pallets/bootstrap" } +pallet-issuance = { path = "../../../pallets/issuance" } +pallet-fee-lock = { path = "../../../pallets/fee-lock" } +pallet-maintenance = { path = "../../../pallets/maintenance" } +pallet-market = { path = "../../../pallets/market" } +pallet-multipurpose-liquidity = { path = "../../../pallets/multipurpose-liquidity" } +pallet-proof-of-stake = { path = "../../../pallets/proof-of-stake" } +pallet-rolldown = { path = "../../../pallets/rolldown" } +pallet-sudo-origin = { path = "../../../pallets/sudo-origin" } +pallet-xyk = { path = "../../../pallets/xyk" } +pallet-stable-swap = { path = "../../../pallets/stable-swap" } +parachain-staking = { path = "../../../pallets/parachain-staking" } +rolldown-runtime-api = { path = "../../../pallets/rolldown/runtime-api" } +rollup-runtime = { path = "../" } +xyk-runtime-api = { path = "../../../pallets/xyk/runtime-api" } + +[features] +default = [] diff --git a/gasp-node/rollup/runtime/integration-test/src/bootstrap.rs b/gasp-node/rollup/runtime/integration-test/src/bootstrap.rs new file mode 100644 index 000000000..61d702074 --- /dev/null +++ b/gasp-node/rollup/runtime/integration-test/src/bootstrap.rs @@ -0,0 +1,84 @@ +use pallet_bootstrap::{BootstrapPhase, Phase}; + +use crate::setup::*; + +const ASSET_ID_1: u32 = 1; + +#[test] +fn bootstrap_updates_metadata_and_creates_pool_correctly() { + ExtBuilder { + balances: vec![ + (AccountId::from(ALICE), NATIVE_ASSET_ID, 100 * UNIT), + (AccountId::from(ALICE), ASSET_ID_1, 100 * UNIT), + ], + assets: vec![( + ASSET_ID_1, + AssetMetadataOf { + decimals: 18, + name: BoundedVec::truncate_from(b"Asset".to_vec()), + symbol: BoundedVec::truncate_from(b"Asset".to_vec()), + existential_deposit: Default::default(), + additional: CustomMetadata { + xyk: Some(XykMetadata { operations_disabled: true }), + ..CustomMetadata::default() + }, + }, + )], + ..ExtBuilder::default() + } + .build() + .execute_with(|| { + assert_err!( + pallet_xyk::Pallet::::create_pool( + RuntimeOrigin::signed(AccountId::from(ALICE)), + NATIVE_ASSET_ID, + 10_ * UNIT, + ASSET_ID_1, + 10 * UNIT, + ), + pallet_xyk::Error::::FunctionNotAvailableForThisToken + ); + + assert_ok!(pallet_bootstrap::Pallet::::schedule_bootstrap( + RuntimeOrigin::root(), + NATIVE_ASSET_ID, + ASSET_ID_1, + 10_u32.into(), + Some(10), + 10, + None, + false, + )); + + pallet_bootstrap::Pallet::::on_initialize(25_u32); + assert_eq!(BootstrapPhase::Public, Phase::::get()); + + assert_ok!(pallet_bootstrap::Pallet::::provision( + RuntimeOrigin::signed(AccountId::from(ALICE)), + ASSET_ID_1, + 10 * UNIT, + )); + assert_ok!(pallet_bootstrap::Pallet::::provision( + RuntimeOrigin::signed(AccountId::from(ALICE)), + NATIVE_ASSET_ID, + 10 * UNIT, + )); + + assert_eq!( + pallet_xyk::LiquidityAssets::::get((NATIVE_ASSET_ID, ASSET_ID_1)), + None + ); + + pallet_bootstrap::Pallet::::on_initialize(40_u32); + assert_eq!(BootstrapPhase::Finished, Phase::::get()); + + assert_eq!( + pallet_xyk::LiquidityAssets::::get((NATIVE_ASSET_ID, ASSET_ID_1)), + Some(ASSET_ID_1 + 1) + ); + + let meta: Option = + orml_asset_registry::Metadata::::get(ASSET_ID_1); + assert_eq!(meta.unwrap().additional.xyk.unwrap().operations_disabled, false); + }); +} diff --git a/gasp-node/rollup/runtime/integration-test/src/fee_lock.rs b/gasp-node/rollup/runtime/integration-test/src/fee_lock.rs new file mode 100644 index 000000000..f6f5e8887 --- /dev/null +++ b/gasp-node/rollup/runtime/integration-test/src/fee_lock.rs @@ -0,0 +1,319 @@ +use crate::setup::*; + +use frame_support::dispatch::{DispatchInfo, PostDispatchInfo}; +use pallet_market::PoolKind; +use rollup_runtime::{ + config::{ + pallet_transaction_payment::{OnChargeHandler, ToAuthor}, + BnbAccountIdOf, TreasuryAccountIdOf, + }, + fees::FEE_PRECISION, + OnChargeTransactionHandler, +}; + +const ASSET_ID_1: u32 = 1; +const ASSET_ID_2: u32 = 2; +const POOL_ID: u32 = 3; +// runtime_config::fees::MarketTotalFee +const FEE: u128 = UNIT * 30_000_000 / FEE_PRECISION; +const TRSY_FEE_P: u128 = FEE * 3_333_333_334 / FEE_PRECISION; +const BNB_FEE: u128 = TRSY_FEE_P * 5_000_000_000 / FEE_PRECISION; +const TRSY_FEE: u128 = TRSY_FEE_P - BNB_FEE; +const POOL_FEE: u128 = FEE - TRSY_FEE - BNB_FEE; + +type Market = pallet_market::Pallet; + +fn test_env() -> TestExternalities { + ExtBuilder { + balances: vec![ + (AccountId::from(ALICE), NATIVE_ASSET_ID, 100 * UNIT), + (AccountId::from(ALICE), ASSET_ID_1, 100 * UNIT), + (AccountId::from(ALICE), ASSET_ID_2, 100 * UNIT), + (AccountId::from(BOB), ASSET_ID_2, 100 * UNIT), + ], + assets: vec![], + ..ExtBuilder::default() + } + .build() +} + +// NATIVE_ASSET_ID & ASSET_ID_1 are used in TwoCurrencyOnChargeHandler for native fees +type Handler = OnChargeHandler< + orml_tokens::MultiTokenCurrencyAdapter, + ToAuthor, + OnChargeTransactionHandler, + FeeLock, +>; + +fn origin() -> RuntimeOrigin { + RuntimeOrigin::signed(AccountId::from(ALICE)) +} + +fn root() -> RuntimeOrigin { + frame_system::RawOrigin::Root.into() +} + +fn init_fee_lock(amount: Balance) { + FeeLock::update_fee_lock_metadata(root(), Some(10), Some(amount), Some(1), Some(vec![])) + .unwrap(); +} + +pub(crate) fn events() -> Vec { + System::events() + .into_iter() + .map(|r| r.event) + .filter_map(|e| match e { + RuntimeEvent::FeeLock(_) | + RuntimeEvent::Tokens(_) | + RuntimeEvent::TransactionPayment(_) => Some(e), + _ => None, + }) + .collect::>() +} + +fn settle_fee( + who: &AccountId, + fee: Balance, + assets: (TokenId, TokenId), + has_failed: bool, + multi: bool, +) { + let swap_pool_list = if multi { vec![POOL_ID, 0] } else { vec![POOL_ID] }; + let withdrawn = >::withdraw_fee( + &who, + &RuntimeCall::Market(pallet_market::Call::multiswap_asset { + swap_pool_list, + asset_id_in: assets.0, + asset_amount_in: UNIT, + asset_id_out: assets.1, + min_amount_out: UNIT, + }), + &DispatchInfo::default(), + fee, + 0, + ) + .unwrap(); + + let pays_fee = if has_failed { Pays::Yes } else { Pays::No }; + + let _ = >::correct_and_deposit_fee( + &who, + &DispatchInfo::default(), + &PostDispatchInfo { actual_weight: None, pays_fee }, + fee, + 0, + withdrawn, + ); +} + +#[test] +fn high_value_swap_should_be_free() { + test_env().execute_with(|| { + init_fee_lock(UNIT); + Market::create_pool(origin(), PoolKind::Xyk, ASSET_ID_1, UNIT, NATIVE_ASSET_ID, UNIT) + .unwrap(); + let who = AccountId::from(ALICE); + + let before = Tokens::accounts(who, NATIVE_ASSET_ID); + settle_fee(&who, 1000, (ASSET_ID_1, NATIVE_ASSET_ID), false, false); + let after = Tokens::accounts(who, NATIVE_ASSET_ID); + + assert_eq!(before.free, after.free); + }) +} + +#[test] +fn high_value_swap_should_take_fees_from_input_xyk() { + test_env().execute_with(|| { + init_fee_lock(UNIT); + Market::create_pool(origin(), PoolKind::Xyk, ASSET_ID_2, UNIT, NATIVE_ASSET_ID, UNIT) + .unwrap(); + let who = AccountId::from(ALICE); + + let before_0 = Tokens::accounts(AccountId::from(ALICE), NATIVE_ASSET_ID); + let before_2 = Tokens::accounts(AccountId::from(ALICE), ASSET_ID_2); + settle_fee(&who, 1000, (ASSET_ID_2, NATIVE_ASSET_ID), true, false); + let after_0 = Tokens::accounts(AccountId::from(ALICE), NATIVE_ASSET_ID); + let after_2 = Tokens::accounts(AccountId::from(ALICE), ASSET_ID_2); + + assert_eq!(before_0.free, after_0.free); + assert_eq!(before_2.free, after_2.free + FEE); + System::assert_has_event(RuntimeEvent::Tokens(orml_tokens::Event::Transfer { + currency_id: ASSET_ID_2, + from: who, + to: TreasuryAccountIdOf::::get(), + amount: TRSY_FEE, + })); + System::assert_has_event(RuntimeEvent::Tokens(orml_tokens::Event::Transfer { + currency_id: ASSET_ID_2, + from: who, + to: BnbAccountIdOf::::get(), + amount: BNB_FEE, + })); + System::assert_has_event(RuntimeEvent::Tokens(orml_tokens::Event::Transfer { + currency_id: ASSET_ID_2, + from: who, + to: Xyk::account_id(), + amount: POOL_FEE, + })); + }) +} + +#[test] +fn high_value_swap_should_take_fees_from_input_ss() { + test_env().execute_with(|| { + init_fee_lock(UNIT); + Market::create_pool( + origin(), + PoolKind::StableSwap, + ASSET_ID_2, + UNIT, + NATIVE_ASSET_ID, + UNIT, + ) + .unwrap(); + let who = AccountId::from(ALICE); + + let before_0 = Tokens::accounts(AccountId::from(ALICE), NATIVE_ASSET_ID); + let before_2 = Tokens::accounts(AccountId::from(ALICE), ASSET_ID_2); + settle_fee(&who, 1000, (ASSET_ID_2, NATIVE_ASSET_ID), true, false); + let after_0 = Tokens::accounts(AccountId::from(ALICE), NATIVE_ASSET_ID); + let after_2 = Tokens::accounts(AccountId::from(ALICE), ASSET_ID_2); + + assert_eq!(before_0.free, after_0.free); + assert_eq!(before_2.free, after_2.free + FEE); + System::assert_has_event(RuntimeEvent::Tokens(orml_tokens::Event::Transfer { + currency_id: ASSET_ID_2, + from: who, + to: TreasuryAccountIdOf::::get(), + amount: TRSY_FEE, + })); + System::assert_has_event(RuntimeEvent::Tokens(orml_tokens::Event::Transfer { + currency_id: ASSET_ID_2, + from: who, + to: BnbAccountIdOf::::get(), + amount: BNB_FEE, + })); + System::assert_has_event(RuntimeEvent::Tokens(orml_tokens::Event::Transfer { + currency_id: ASSET_ID_2, + from: who, + to: StableSwap::get_pool_account(&POOL_ID), + amount: POOL_FEE, + })); + }) +} + +#[test] +fn low_value_swap_should_lock_fees_in_native() { + test_env().execute_with(|| { + let fee_lock = 5 * UNIT; + init_fee_lock(fee_lock); + let who = AccountId::from(ALICE); + let fee = 1000; + Market::create_pool(origin(), PoolKind::Xyk, ASSET_ID_1, UNIT, ASSET_ID_2, UNIT).unwrap(); + + let before_0 = Tokens::accounts(AccountId::from(ALICE), NATIVE_ASSET_ID); + settle_fee(&who, fee, (ASSET_ID_1, ASSET_ID_2), false, true); + let after_0 = Tokens::accounts(AccountId::from(ALICE), NATIVE_ASSET_ID); + + assert_eq!(before_0.free, after_0.free + fee_lock); + assert_eq!(after_0.reserved, fee_lock); + // fees locked + System::assert_last_event(RuntimeEvent::FeeLock(pallet_fee_lock::Event::FeeLocked { + who, + lock_amount: fee_lock, + total_locked: fee_lock, + })); + }) +} + +#[test] +fn low_value_swap_should_lock_fees_in_native_and_take_fees_from_input_xyk() { + test_env().execute_with(|| { + let fee_lock = 5 * UNIT; + init_fee_lock(fee_lock); + let who = AccountId::from(ALICE); + let fee = 1000; + Market::create_pool(origin(), PoolKind::Xyk, ASSET_ID_1, UNIT, ASSET_ID_2, UNIT).unwrap(); + + let before_0 = Tokens::accounts(AccountId::from(ALICE), NATIVE_ASSET_ID); + let before_1 = Tokens::accounts(AccountId::from(ALICE), ASSET_ID_1); + settle_fee(&who, fee, (ASSET_ID_1, ASSET_ID_2), true, true); + let after_0 = Tokens::accounts(AccountId::from(ALICE), NATIVE_ASSET_ID); + let after_1 = Tokens::accounts(AccountId::from(ALICE), ASSET_ID_1); + + assert_eq!(before_0.free, after_0.free + fee_lock); + assert_eq!(after_0.reserved, fee_lock); + assert_eq!(before_1.free, after_1.free + FEE); + // fees locked + System::assert_has_event(RuntimeEvent::FeeLock(pallet_fee_lock::Event::FeeLocked { + who, + lock_amount: fee_lock, + total_locked: fee_lock, + })); + System::assert_has_event(RuntimeEvent::Tokens(orml_tokens::Event::Transfer { + currency_id: ASSET_ID_1, + from: who, + to: TreasuryAccountIdOf::::get(), + amount: TRSY_FEE, + })); + System::assert_has_event(RuntimeEvent::Tokens(orml_tokens::Event::Transfer { + currency_id: ASSET_ID_1, + from: who, + to: BnbAccountIdOf::::get(), + amount: BNB_FEE, + })); + System::assert_has_event(RuntimeEvent::Tokens(orml_tokens::Event::Transfer { + currency_id: ASSET_ID_1, + from: who, + to: Xyk::account_id(), + amount: POOL_FEE, + })); + }) +} + +#[test] +fn low_value_swap_should_lock_fees_in_native_and_take_fees_from_input_ss() { + test_env().execute_with(|| { + let fee_lock = 5 * UNIT; + init_fee_lock(fee_lock); + let who = AccountId::from(ALICE); + let fee = 1000; + Market::create_pool(origin(), PoolKind::StableSwap, ASSET_ID_1, UNIT, ASSET_ID_2, UNIT) + .unwrap(); + + let before_0 = Tokens::accounts(AccountId::from(ALICE), NATIVE_ASSET_ID); + let before_1 = Tokens::accounts(AccountId::from(ALICE), ASSET_ID_1); + settle_fee(&who, fee, (ASSET_ID_1, ASSET_ID_2), true, true); + let after_0 = Tokens::accounts(AccountId::from(ALICE), NATIVE_ASSET_ID); + let after_1 = Tokens::accounts(AccountId::from(ALICE), ASSET_ID_1); + + assert_eq!(before_0.free, after_0.free + fee_lock); + assert_eq!(after_0.reserved, fee_lock); + assert_eq!(before_1.free, after_1.free + FEE); + // fees locked + System::assert_has_event(RuntimeEvent::FeeLock(pallet_fee_lock::Event::FeeLocked { + who, + lock_amount: fee_lock, + total_locked: fee_lock, + })); + System::assert_has_event(RuntimeEvent::Tokens(orml_tokens::Event::Transfer { + currency_id: ASSET_ID_1, + from: who, + to: TreasuryAccountIdOf::::get(), + amount: TRSY_FEE, + })); + System::assert_has_event(RuntimeEvent::Tokens(orml_tokens::Event::Transfer { + currency_id: ASSET_ID_1, + from: who, + to: BnbAccountIdOf::::get(), + amount: BNB_FEE, + })); + System::assert_has_event(RuntimeEvent::Tokens(orml_tokens::Event::Transfer { + currency_id: ASSET_ID_1, + from: who, + to: StableSwap::get_pool_account(&POOL_ID), + amount: POOL_FEE, + })); + }) +} diff --git a/gasp-node/rollup/runtime/integration-test/src/identity.rs b/gasp-node/rollup/runtime/integration-test/src/identity.rs new file mode 100644 index 000000000..4923f6cdd --- /dev/null +++ b/gasp-node/rollup/runtime/integration-test/src/identity.rs @@ -0,0 +1,33 @@ +use crate::setup::*; + +#[test] +fn identity_permissions_correct() { + ExtBuilder { + balances: vec![(AccountId::from(ALICE), NATIVE_ASSET_ID, 10000 * UNIT)], + ..ExtBuilder::default() + } + .build() + .execute_with(|| { + assert_noop!( + Identity::add_registrar( + RuntimeOrigin::signed(AccountId::from(ALICE)), + AccountId::from(BOB).into() + ), + BadOrigin + ); + assert_ok!(Identity::add_registrar(RuntimeOrigin::root(), AccountId::from(BOB).into())); + + assert_noop!( + Identity::kill_identity( + RuntimeOrigin::signed(AccountId::from(ALICE)), + AccountId::from(BOB).into() + ), + BadOrigin + ); + // origin passes, but fails on no identity set + assert_noop!( + Identity::kill_identity(RuntimeOrigin::root(), AccountId::from(BOB).into()), + pallet_identity::Error::::NoIdentity + ); + }); +} diff --git a/gasp-node/rollup/runtime/integration-test/src/lib.rs b/gasp-node/rollup/runtime/integration-test/src/lib.rs new file mode 100644 index 000000000..52419ae4c --- /dev/null +++ b/gasp-node/rollup/runtime/integration-test/src/lib.rs @@ -0,0 +1,13 @@ +#![cfg(test)] + +mod bootstrap; +mod fee_lock; +mod identity; +mod maintenance; +mod market; +mod membership; +mod nontransfer; +mod proof_of_stake; +mod proxy; +mod setup; +mod xyk; diff --git a/gasp-node/rollup/runtime/integration-test/src/maintenance.rs b/gasp-node/rollup/runtime/integration-test/src/maintenance.rs new file mode 100644 index 000000000..62bf9eb2a --- /dev/null +++ b/gasp-node/rollup/runtime/integration-test/src/maintenance.rs @@ -0,0 +1,62 @@ +use crate::setup::*; +use sp_runtime::testing::H256; + +fn test_env() -> TestExternalities { + ExtBuilder { ..ExtBuilder::default() }.build() +} + +#[test] +fn system_set_code_works_with_maintenance_mode() { + let mut ext = test_env(); + ext.execute_with(|| { + System::set_block_number(1); + assert_ok!(System::set_code_without_checks( + frame_system::RawOrigin::Root.into(), + vec![1, 2, 3, 4], + )); + + assert_ok!(Maintenance::switch_maintenance_mode_on( + RuntimeOrigin::signed( + FoundationAccountsProvider::get().pop().expect("There atleast 1 F acc") + ) + .into() + )); + assert_err!( + System::set_code_without_checks(frame_system::RawOrigin::Root.into(), vec![1, 2, 3, 4],), + pallet_maintenance::Error::::UpgradeBlockedByMaintenance + ); + + assert_ok!(Maintenance::switch_upgradability_in_maintenance_mode_on( + RuntimeOrigin::signed( + FoundationAccountsProvider::get().pop().expect("There atleast 1 F acc") + ) + .into() + )); + assert_ok!(System::set_code_without_checks( + frame_system::RawOrigin::Root.into(), + vec![1, 2, 3, 4], + )); + + assert_ok!(Maintenance::switch_upgradability_in_maintenance_mode_off( + RuntimeOrigin::signed( + FoundationAccountsProvider::get().pop().expect("There atleast 1 F acc") + ) + .into() + )); + assert_err!( + System::set_code_without_checks(frame_system::RawOrigin::Root.into(), vec![1, 2, 3, 4],), + pallet_maintenance::Error::::UpgradeBlockedByMaintenance + ); + + assert_ok!(Maintenance::switch_maintenance_mode_off( + RuntimeOrigin::signed( + FoundationAccountsProvider::get().pop().expect("There atleast 1 F acc") + ) + .into() + )); + assert_ok!(System::set_code_without_checks( + frame_system::RawOrigin::Root.into(), + vec![1, 2, 3, 4], + )); + }); +} diff --git a/gasp-node/rollup/runtime/integration-test/src/market.rs b/gasp-node/rollup/runtime/integration-test/src/market.rs new file mode 100644 index 000000000..cae0b294e --- /dev/null +++ b/gasp-node/rollup/runtime/integration-test/src/market.rs @@ -0,0 +1,424 @@ +use crate::setup::*; + +use pallet_market::{AtomicSwap, Event, PoolKind}; +use sp_runtime::{traits::Zero, DispatchResult}; + +const ASSET_ID_1: u32 = NATIVE_ASSET_ID + 1; +const ASSET_ID_2: u32 = ASSET_ID_1 + 1; +const ASSET_ID_3: u32 = ASSET_ID_2 + 1; +const POOL_ID_1: u32 = ASSET_ID_3 + 1; +const POOL_ID_2: u32 = POOL_ID_1 + 1; +const POOL_ID_3: u32 = POOL_ID_2 + 1; + +fn test_env() -> TestExternalities { + ExtBuilder { + balances: vec![ + (AccountId::from(ALICE), NATIVE_ASSET_ID, 100 * UNIT), + (AccountId::from(ALICE), ASSET_ID_1, 100 * UNIT), + (AccountId::from(ALICE), ASSET_ID_2, Balance::MAX), + (AccountId::from(ALICE), ASSET_ID_3, Balance::MAX), + ], + ..ExtBuilder::default() + } + .build() +} + +type Market = pallet_market::Pallet; + +fn origin() -> RuntimeOrigin { + RuntimeOrigin::signed(AccountId::from(ALICE)) +} + +fn create_pool(kind: PoolKind, assets: (u32, u32)) -> DispatchResult { + Market::create_pool(origin(), kind, assets.0, 10 * UNIT, assets.1, 10 * UNIT) +} + +fn create_pool_unb(kind: PoolKind, assets: (u32, u32)) -> DispatchResult { + Market::create_pool(origin(), kind, assets.0, 10 * UNIT, assets.1, 5 * UNIT) +} + +pub(crate) fn events() -> Vec { + System::events() + .into_iter() + .map(|r| r.event) + .filter_map(|e| match e { + RuntimeEvent::Xyk(_) | RuntimeEvent::Market(_) | RuntimeEvent::StableSwap(_) => Some(e), + _ => None, + }) + .collect::>() +} + +#[test] +fn create_pool_works() { + test_env().execute_with(|| { + assert_ok!(create_pool(PoolKind::Xyk, (NATIVE_ASSET_ID, ASSET_ID_1))); + System::assert_has_event(RuntimeEvent::Market(Event::PoolCreated { + creator: AccountId::from(ALICE), + pool_id: POOL_ID_1, + lp_token: POOL_ID_1, + assets: (0, 1), + })); + System::assert_has_event(RuntimeEvent::Market(Event::LiquidityMinted { + who: AccountId::from(ALICE), + pool_id: POOL_ID_1, + amounts_provided: (10000000000000000000, 10000000000000000000), + lp_token: POOL_ID_1, + lp_token_minted: 10000000000000000000, + total_supply: 10000000000000000000, + })); + + assert_ok!(create_pool(PoolKind::StableSwap, (NATIVE_ASSET_ID, ASSET_ID_1))); + + let p = pallet_stable_swap::Pools::::get(POOL_ID_2).unwrap(); + assert_eq!(p.rate_multipliers[0], UNIT); + assert_eq!(p.rate_multipliers[1], UNIT); + System::assert_has_event(RuntimeEvent::Market(Event::PoolCreated { + creator: AccountId::from(ALICE), + pool_id: POOL_ID_2, + lp_token: POOL_ID_2, + assets: (0, 1), + })); + System::assert_has_event(RuntimeEvent::Market(Event::LiquidityMinted { + who: AccountId::from(ALICE), + pool_id: POOL_ID_2, + amounts_provided: (10000000000000000000, 10000000000000000000), + lp_token: POOL_ID_2, + lp_token_minted: 20000000000000000000, + total_supply: 20000000000000000000, + })); + + assert_ok!(create_pool_unb(PoolKind::StableSwap, (NATIVE_ASSET_ID, ASSET_ID_1))); + + let p = pallet_stable_swap::Pools::::get(POOL_ID_3).unwrap(); + println!("{:?}", p); + assert_eq!(p.rate_multipliers[0], 5 * UNIT); + assert_eq!(p.rate_multipliers[1], 10 * UNIT); + + System::assert_has_event(RuntimeEvent::Market(Event::PoolCreated { + creator: AccountId::from(ALICE), + pool_id: POOL_ID_3, + lp_token: POOL_ID_3, + assets: (0, 1), + })); + // lp_token_minted are correct since we applied the rates, and the pool is balanced + System::assert_has_event(RuntimeEvent::Market(Event::LiquidityMinted { + who: AccountId::from(ALICE), + pool_id: POOL_ID_3, + amounts_provided: (10000000000000000000, 5000000000000000000), + lp_token: POOL_ID_3, + lp_token_minted: 100000000000000000000, + total_supply: 100000000000000000000, + })); + }) +} + +#[test] +fn create_pool_works_ss_min_max() { + test_env().execute_with(|| { + let min = 1; + let max_round_to_decimal: u128 = 300000000000000000000000000000000000000 / 10; + + assert_ok!(Market::create_pool( + origin(), + PoolKind::StableSwap, + ASSET_ID_2, + min, + ASSET_ID_3, + min, + )); + + let max_rate = UNIT; // 1e18 + let max_rate_fail = UNIT * 10; // 1e19 + assert_ok!(Market::create_pool( + origin(), + PoolKind::StableSwap, + ASSET_ID_2, + min, + ASSET_ID_3, + max_rate, + )); + assert_err!( + Market::create_pool( + origin(), + PoolKind::StableSwap, + ASSET_ID_2, + min, + ASSET_ID_3, + max_rate_fail, + ), + pallet_stable_swap::Error::::InitialPoolRateOutOfRange + ); + + assert_ok!(Market::create_pool( + origin(), + PoolKind::StableSwap, + ASSET_ID_2, + max_round_to_decimal, + ASSET_ID_3, + max_round_to_decimal, + )); + }); +} + +#[test] +fn add_liquidity_works() { + test_env().execute_with(|| { + assert_ok!(create_pool_unb(PoolKind::Xyk, (ASSET_ID_2, ASSET_ID_1))); + // note that the created pool will be sorted as (ASSET_ID_1, ASSET_ID_2) + assert_ok!(create_pool_unb(PoolKind::StableSwap, (ASSET_ID_2, ASSET_ID_1))); + + let expected = + Market::calculate_expected_amount_for_minting(POOL_ID_1, ASSET_ID_2, UNIT).unwrap(); + let lp_expected = + Market::calculate_expected_lp_minted(POOL_ID_1, (UNIT, expected)).unwrap(); + assert_ok!(Market::mint_liquidity(origin(), POOL_ID_1, ASSET_ID_2, UNIT, 10 * UNIT)); + System::assert_last_event(RuntimeEvent::Market(Event::LiquidityMinted { + who: AccountId::from(ALICE), + pool_id: POOL_ID_1, + amounts_provided: (1000000000000000000, expected), + lp_token: POOL_ID_1, + lp_token_minted: lp_expected, + total_supply: 8250000000000000000, + })); + + let expected = + Market::calculate_expected_amount_for_minting(POOL_ID_2, ASSET_ID_2, UNIT).unwrap(); + let lp_expected = + Market::calculate_expected_lp_minted(POOL_ID_2, (expected, UNIT)).unwrap(); + assert_ok!(Market::mint_liquidity(origin(), POOL_ID_2, ASSET_ID_2, UNIT, 10 * UNIT)); + System::assert_last_event(RuntimeEvent::Market(Event::LiquidityMinted { + who: AccountId::from(ALICE), + pool_id: POOL_ID_2, + amounts_provided: (1000000000000000000, expected), + lp_token: POOL_ID_2, + lp_token_minted: lp_expected, + total_supply: 110000000000000000000, + })); + }) +} + +#[test] +fn add_liquidity_fixed_works() { + test_env().execute_with(|| { + assert_ok!(create_pool_unb(PoolKind::Xyk, (ASSET_ID_2, ASSET_ID_1))); + assert_ok!(create_pool_unb(PoolKind::StableSwap, (ASSET_ID_2, ASSET_ID_1))); + + assert_ok!(Market::mint_liquidity_fixed_amounts(origin(), POOL_ID_1, (UNIT, 0), 0)); + System::assert_last_event(RuntimeEvent::Market(Event::LiquidityMinted { + who: AccountId::from(ALICE), + pool_id: POOL_ID_1, + amounts_provided: (1000000000000000000, 0), + lp_token: POOL_ID_1, + lp_token_minted: 365524961509654622, + total_supply: 7865524961509654622, + })); + + let expected = Market::calculate_expected_lp_minted(POOL_ID_2, (5 * UNIT, UNIT)).unwrap(); + assert_ok!(Market::mint_liquidity_fixed_amounts(origin(), POOL_ID_2, (5 * UNIT, UNIT), 0)); + System::assert_last_event(RuntimeEvent::Market(Event::LiquidityMinted { + who: AccountId::from(ALICE), + pool_id: POOL_ID_2, + amounts_provided: (5000000000000000000, 1000000000000000000), + lp_token: POOL_ID_2, + lp_token_minted: expected, + total_supply: 154925100814226884776, + })); + }) +} + +#[test] +fn remove_liquidity_works() { + test_env().execute_with(|| { + assert_ok!(create_pool_unb(PoolKind::Xyk, (NATIVE_ASSET_ID, ASSET_ID_1))); + assert_ok!(create_pool_unb(PoolKind::StableSwap, (NATIVE_ASSET_ID, ASSET_ID_1))); + + assert_ok!(Market::burn_liquidity(origin(), POOL_ID_1, UNIT, 0, 0)); + System::assert_last_event(RuntimeEvent::Market(Event::LiquidityBurned { + who: AccountId::from(ALICE), + pool_id: POOL_ID_1, + amounts: (1333333333333333333, 666666666666666666), + burned_amount: 1000000000000000000, + total_supply: 6500000000000000000, + })); + + assert_ok!(Market::burn_liquidity(origin(), POOL_ID_2, UNIT, 0, 0)); + System::assert_last_event(RuntimeEvent::Market(Event::LiquidityBurned { + who: AccountId::from(ALICE), + pool_id: POOL_ID_2, + amounts: (100000000000000000, 50000000000000000), + burned_amount: 1000000000000000000, + total_supply: 99000000000000000000, + })); + }) +} + +#[test] +fn multiswap_should_work_xyk() { + test_env().execute_with(|| { + assert_ok!(create_pool_unb(PoolKind::Xyk, (ASSET_ID_3, ASSET_ID_1))); + assert_ok!(create_pool_unb(PoolKind::Xyk, (ASSET_ID_1, ASSET_ID_2))); + assert_ok!(create_pool_unb(PoolKind::Xyk, (ASSET_ID_2, ASSET_ID_3))); + + assert_ok!(Market::multiswap_asset( + origin(), + vec![POOL_ID_1, POOL_ID_2, POOL_ID_3], + ASSET_ID_3, + UNIT, + ASSET_ID_3, + Zero::zero(), + )); + + println!("{:#?}", events()); + + System::assert_last_event(RuntimeEvent::Market(Event::AssetsSwapped { + who: AccountId::from(ALICE), + swaps: vec![ + AtomicSwap { + pool_id: POOL_ID_1, + kind: PoolKind::Xyk, + asset_in: 3, + asset_out: 1, + amount_in: 1000000000000000000, + amount_out: 453305446940074565, + }, + AtomicSwap { + pool_id: POOL_ID_2, + kind: PoolKind::Xyk, + asset_in: 1, + asset_out: 2, + amount_in: 453305446940074565, + amount_out: 216201629292906575, + }, + AtomicSwap { + pool_id: POOL_ID_3, + kind: PoolKind::Xyk, + asset_in: 2, + asset_out: 3, + amount_in: 216201629292906575, + amount_out: 105502376567411557, + }, + ], + })); + }) +} + +#[test] +fn multiswap_should_work_stable_swap_with_bnb() { + test_env().execute_with(|| { + // 2:1 rate + assert_ok!(create_pool_unb(PoolKind::StableSwap, (ASSET_ID_3, ASSET_ID_1))); + assert_ok!(create_pool_unb(PoolKind::StableSwap, (ASSET_ID_1, ASSET_ID_2))); + // 1:1 rate + assert_ok!(create_pool(PoolKind::StableSwap, (ASSET_ID_2, ASSET_ID_3))); + // for bnb + assert_ok!(create_pool_unb(PoolKind::Xyk, (NATIVE_ASSET_ID, ASSET_ID_1))); + assert_ok!(create_pool_unb(PoolKind::Xyk, (NATIVE_ASSET_ID, ASSET_ID_2))); + assert_ok!(create_pool_unb(PoolKind::Xyk, (NATIVE_ASSET_ID, ASSET_ID_3))); + + let p = pallet_stable_swap::Pools::::get(POOL_ID_1).unwrap(); + // println!("{:#?}", p); + // assets are ordered by id, same sa as the corresponding rates + assert_eq!(p.assets, vec![ASSET_ID_1, ASSET_ID_3]); + assert_eq!(p.rate_multipliers[0], 10 * UNIT); + assert_eq!(p.rate_multipliers[1], 5 * UNIT); + + let before = Tokens::total_issuance(NATIVE_ASSET_ID); + + assert_ok!(Market::multiswap_asset( + origin(), + vec![POOL_ID_1, POOL_ID_2, POOL_ID_3], + ASSET_ID_3, + UNIT, + ASSET_ID_3, + Zero::zero(), + )); + + let after = Tokens::total_issuance(NATIVE_ASSET_ID); + // issuance decreased because of bnb + assert!(before > after); + assert_eq!(before, 100000000000000000000); + assert_eq!(after, 99999001734203514767); + + // println!("{:#?}", events()); + + System::assert_last_event(RuntimeEvent::Market(Event::AssetsSwapped { + who: AccountId::from(ALICE), + swaps: vec![ + AtomicSwap { + pool_id: POOL_ID_1, + kind: PoolKind::StableSwap, + asset_in: ASSET_ID_3, + asset_out: 1, + amount_in: 1000000000000000000, + amount_out: 498447826003559573, + }, + AtomicSwap { + pool_id: POOL_ID_2, + kind: PoolKind::StableSwap, + asset_in: 1, + asset_out: 2, + amount_in: 498447826003559573, + amount_out: 248463606016707341, + }, + AtomicSwap { + pool_id: POOL_ID_3, + kind: PoolKind::StableSwap, + asset_in: 2, + asset_out: 3, + amount_in: 248463606016707341, + amount_out: 247712005295675461, + }, + ], + })); + }) +} + +#[test] +fn multiswap_should_work_mixed() { + test_env().execute_with(|| { + assert_ok!(create_pool_unb(PoolKind::Xyk, (ASSET_ID_3, ASSET_ID_1))); + assert_ok!(create_pool_unb(PoolKind::StableSwap, (ASSET_ID_1, ASSET_ID_2))); + assert_ok!(create_pool_unb(PoolKind::Xyk, (ASSET_ID_2, ASSET_ID_3))); + + assert_ok!(Market::multiswap_asset( + origin(), + vec![POOL_ID_1, POOL_ID_2, POOL_ID_3], + ASSET_ID_3, + UNIT, + ASSET_ID_3, + Zero::zero(), + )); + + println!("{:#?}", events()); + + System::assert_last_event(RuntimeEvent::Market(Event::AssetsSwapped { + who: AccountId::from(ALICE), + swaps: vec![ + AtomicSwap { + pool_id: POOL_ID_1, + kind: PoolKind::Xyk, + asset_in: ASSET_ID_3, + asset_out: 1, + amount_in: 1000000000000000000, + amount_out: 453305446940074565, + }, + AtomicSwap { + pool_id: POOL_ID_2, + kind: PoolKind::StableSwap, + asset_in: 1, + asset_out: 2, + amount_in: 453305446940074565, + amount_out: 225962336828316482, + }, + AtomicSwap { + pool_id: POOL_ID_3, + kind: PoolKind::Xyk, + asset_in: 2, + asset_out: 3, + amount_in: 225962336828316482, + amount_out: 110160480582936294, + }, + ], + })); + }) +} diff --git a/gasp-node/rollup/runtime/integration-test/src/membership.rs b/gasp-node/rollup/runtime/integration-test/src/membership.rs new file mode 100644 index 000000000..49faa5fe4 --- /dev/null +++ b/gasp-node/rollup/runtime/integration-test/src/membership.rs @@ -0,0 +1,108 @@ +use crate::setup::*; +use frame_support::traits::Contains; +use sp_runtime::testing::H256; + +fn test_env() -> TestExternalities { + ExtBuilder { ..ExtBuilder::default() }.build() +} + +#[test] +fn change_key_works() { + let mut ext = test_env(); + ext.execute_with(|| { + System::set_block_number(1); + + let alice = AccountId::from(ALICE); + let bob = AccountId::from(BOB); + + assert_err!( + FoundationMembers::change_key(RuntimeOrigin::signed(bob.clone()), alice), + pallet_membership::Error::::NotMember, + ); + + assert!(>::contains(&alice)); + + assert_ok!(FoundationMembers::change_key( + RuntimeOrigin::signed( + FoundationAccountsProvider::get().pop().expect("There atleast 1 F acc") + ) + .into(), + bob, + )); + + assert!(!>::contains(&alice)); + assert!(>::contains(&bob)); + }); +} + +#[test] +fn other_fn_doesnt_work_for_root() { + let mut ext = test_env(); + ext.execute_with(|| { + System::set_block_number(1); + + let alice = AccountId::from(ALICE); + let bob = AccountId::from(BOB); + let origin: RuntimeOrigin = frame_system::RawOrigin::Root.into(); + + assert_err!(FoundationMembers::add_member(origin.clone(), bob), BadOrigin,); + assert_err!(FoundationMembers::remove_member(origin.clone(), bob), BadOrigin,); + assert_err!(FoundationMembers::swap_member(origin.clone(), alice, bob), BadOrigin,); + assert_err!(FoundationMembers::reset_members(origin.clone(), vec![]), BadOrigin,); + assert_err!(FoundationMembers::set_prime(origin.clone(), bob), BadOrigin,); + assert_err!(FoundationMembers::clear_prime(origin.clone()), BadOrigin,); + }); +} + +#[test] +fn other_fn_doesnt_work_for_foundation() { + let mut ext = test_env(); + ext.execute_with(|| { + System::set_block_number(1); + + let alice = AccountId::from(ALICE); + let bob = AccountId::from(BOB); + let origin: RuntimeOrigin = RuntimeOrigin::signed( + FoundationAccountsProvider::get().pop().expect("There atleast 1 F acc"), + ); + + assert_err!(FoundationMembers::add_member(origin.clone(), bob), BadOrigin,); + assert_err!(FoundationMembers::remove_member(origin.clone(), bob), BadOrigin,); + assert_err!(FoundationMembers::swap_member(origin.clone(), alice, bob), BadOrigin,); + assert_err!(FoundationMembers::reset_members(origin.clone(), vec![]), BadOrigin,); + assert_err!(FoundationMembers::set_prime(origin.clone(), bob), BadOrigin,); + assert_err!(FoundationMembers::clear_prime(origin.clone()), BadOrigin,); + }); +} + +#[test] +fn transfer_memebership_fns_work_for_root() { + let mut ext = test_env(); + ext.execute_with(|| { + System::set_block_number(1); + + let alice = AccountId::from(ALICE); + let bob = AccountId::from(BOB); + let origin: RuntimeOrigin = frame_system::RawOrigin::Root.into(); + + assert_ok!(TransferMembers::add_member(origin.clone(), bob)); + assert_ok!(TransferMembers::swap_member(origin.clone(), bob, alice)); + assert_ok!(TransferMembers::remove_member(origin.clone(), alice)); + assert_ok!(TransferMembers::reset_members(origin.clone(), vec![])); + }); +} + +#[test] +fn transfer_memebership_fns_blocked_for_root() { + let mut ext = test_env(); + ext.execute_with(|| { + System::set_block_number(1); + + let alice = AccountId::from(ALICE); + let bob = AccountId::from(BOB); + let origin: RuntimeOrigin = frame_system::RawOrigin::Root.into(); + + assert_err!(TransferMembers::set_prime(origin.clone(), bob), BadOrigin,); + assert_err!(TransferMembers::clear_prime(origin.clone()), BadOrigin,); + }); +} diff --git a/gasp-node/rollup/runtime/integration-test/src/nontransfer.rs b/gasp-node/rollup/runtime/integration-test/src/nontransfer.rs new file mode 100644 index 000000000..a1b260a19 --- /dev/null +++ b/gasp-node/rollup/runtime/integration-test/src/nontransfer.rs @@ -0,0 +1,288 @@ +use rollup_runtime::tokens::NontransferableTokens; + +use crate::setup::*; + +const ASSET_ID_1: TokenId = NATIVE_ASSET_ID + 1; +const POOL_ID: TokenId = ASSET_ID_1 + 1; +const ARB: [u8; 20] = hex_literal::hex!["fc741134c82b81b7ab7efbf334b0c90ff8dbf22c"]; + +fn test_env() -> TestExternalities { + ExtBuilder { + balances: vec![ + (AccountId::from(ALICE), NATIVE_ASSET_ID, 100 * UNIT), + (AccountId::from(ARB), NATIVE_ASSET_ID, 100 * UNIT), + (AccountId::from(ALICE), ASSET_ID_1, 100 * UNIT), + ], + ..ExtBuilder::default() + } + .build() +} + +fn root() -> RuntimeOrigin { + RuntimeOrigin::root().into() +} + +fn origin() -> RuntimeOrigin { + RuntimeOrigin::signed(AccountId::from(ALICE)) +} + +#[test] +fn test_tokens_calls_should_block() { + let mut ext = test_env(); + ext.execute_with(|| { + let who = AccountId::from(ALICE); + let bob = AccountId::from(BOB); + let amount = UNIT; + assert_err!( + orml_tokens::Pallet::::mint(root(), NATIVE_ASSET_ID, who, amount), + orml_tokens::Error::::NontransferableToken + ); + assert_err!( + orml_tokens::Pallet::::set_balance( + root(), + who, + NATIVE_ASSET_ID, + amount, + amount + ), + orml_tokens::Error::::NontransferableToken + ); + assert_err!( + orml_tokens::Pallet::::transfer(origin(), bob, NATIVE_ASSET_ID, amount), + orml_tokens::Error::::NontransferableToken + ); + assert_err!( + orml_tokens::Pallet::::force_transfer( + root(), + who, + bob, + NATIVE_ASSET_ID, + amount + ), + orml_tokens::Error::::NontransferableToken + ); + assert_err!( + orml_tokens::Pallet::::transfer_all(origin(), bob, NATIVE_ASSET_ID, false), + orml_tokens::Error::::NontransferableToken + ); + assert_err!( + orml_tokens::Pallet::::transfer_keep_alive( + origin(), + bob, + NATIVE_ASSET_ID, + amount + ), + orml_tokens::Error::::NontransferableToken + ); + }); +} + +#[test] +fn test_tokens_calls_should_work_for_allow_listed() { + let mut ext = test_env(); + ext.execute_with(|| { + let who = AccountId::from(ALICE); + let bob = AccountId::from(BOB); + let amount = UNIT; + + assert_ok!(TransferMembers::add_member(root(), who)); + + assert_err!( + orml_tokens::Pallet::::mint(root(), NATIVE_ASSET_ID, who, amount), + orml_tokens::Error::::NontransferableToken + ); + assert_err!( + orml_tokens::Pallet::::set_balance( + root(), + who, + NATIVE_ASSET_ID, + amount, + amount + ), + orml_tokens::Error::::NontransferableToken + ); + assert_ok!(orml_tokens::Pallet::::transfer( + origin(), + bob, + NATIVE_ASSET_ID, + amount + )); + assert_ok!(orml_tokens::Pallet::::force_transfer( + root(), + who, + bob, + NATIVE_ASSET_ID, + amount + )); + assert_ok!(orml_tokens::Pallet::::transfer_keep_alive( + origin(), + bob, + NATIVE_ASSET_ID, + amount + )); + assert_ok!(orml_tokens::Pallet::::transfer_all( + origin(), + bob, + NATIVE_ASSET_ID, + false + )); + }); +} + +#[test] +fn test_market_should_block() { + let mut ext = test_env(); + ext.execute_with(|| { + let foundation = AccountId::from(ALICE); + let bob = AccountId::from(BOB); + let amount = 100 * UNIT; + + // user cannot create pool, foundation can + assert_err!( + pallet_market::Pallet::::create_pool( + RuntimeOrigin::signed(bob), + pallet_market::PoolKind::Xyk, + NATIVE_ASSET_ID, + amount, + ASSET_ID_1, + amount, + ), + pallet_market::Error::::NontransferableToken + ); + assert_ok!(pallet_market::Pallet::::create_pool( + RuntimeOrigin::signed(foundation), + pallet_market::PoolKind::Xyk, + NATIVE_ASSET_ID, + amount, + ASSET_ID_1, + amount, + )); + + // none can mint + assert_err!( + pallet_market::Pallet::::mint_liquidity( + RuntimeOrigin::signed(foundation), + POOL_ID, + NATIVE_ASSET_ID, + amount, + amount, + ), + pallet_market::Error::::NontransferableToken + ); + assert_err!( + pallet_market::Pallet::::mint_liquidity_fixed_amounts( + RuntimeOrigin::signed(foundation), + POOL_ID, + (amount, amount), + amount, + ), + pallet_market::Error::::NontransferableToken + ); + assert_err!( + pallet_market::Pallet::::mint_liquidity_using_vesting_native_tokens( + RuntimeOrigin::signed(foundation), + POOL_ID, + amount, + amount, + ), + pallet_market::Error::::NontransferableToken + ); + assert_err!( + pallet_market::Pallet::::mint_liquidity_using_vesting_native_tokens_by_vesting_index( + RuntimeOrigin::signed(foundation), + POOL_ID, + 0, + None, + amount, + ), + pallet_market::Error::::NontransferableToken + ); + + // user cannot burn, foundation can + assert_err!( + pallet_market::Pallet::::burn_liquidity( + RuntimeOrigin::signed(bob), + POOL_ID, + amount, + amount, + amount, + ), + pallet_market::Error::::NontransferableToken + ); + assert_ok!(pallet_market::Pallet::::burn_liquidity( + RuntimeOrigin::signed(foundation), + POOL_ID, + UNIT, + 0, + 0, + )); + + // user & foundation cannot sell, ARB bot can + assert_err!( + pallet_market::Pallet::::multiswap_asset( + RuntimeOrigin::signed(bob), + vec![POOL_ID], + NATIVE_ASSET_ID, + UNIT, + ASSET_ID_1, + 0, + ), + pallet_market::Error::::NontransferableToken + ); + assert_err!( + pallet_market::Pallet::::multiswap_asset( + RuntimeOrigin::signed(foundation), + vec![POOL_ID], + NATIVE_ASSET_ID, + UNIT, + ASSET_ID_1, + 0, + ), + pallet_market::Error::::NontransferableToken + ); + assert_err!( + pallet_market::Pallet::::multiswap_asset( + RuntimeOrigin::signed(AccountId::from(ARB)), + vec![POOL_ID], + NATIVE_ASSET_ID, + UNIT, + ASSET_ID_1, + 0, + ), + pallet_market::Error::::NontransferableToken + ); + assert_err!( + pallet_market::Pallet::::multiswap_asset_buy( + RuntimeOrigin::signed(bob), + vec![POOL_ID], + ASSET_ID_1, + UNIT, + NATIVE_ASSET_ID, + amount, + ), + pallet_market::Error::::NontransferableToken + ); + assert_err!( + pallet_market::Pallet::::multiswap_asset_buy( + RuntimeOrigin::signed(foundation), + vec![POOL_ID], + ASSET_ID_1, + UNIT, + NATIVE_ASSET_ID, + amount, + ), + pallet_market::Error::::NontransferableToken + ); + assert_err!( + pallet_market::Pallet::::multiswap_asset_buy( + RuntimeOrigin::signed(AccountId::from(ARB)), + vec![POOL_ID], + ASSET_ID_1, + UNIT, + NATIVE_ASSET_ID, + amount, + ), + pallet_market::Error::::NontransferableToken + ); + }); +} diff --git a/gasp-node/rollup/runtime/integration-test/src/proof_of_stake.rs b/gasp-node/rollup/runtime/integration-test/src/proof_of_stake.rs new file mode 100644 index 000000000..01118aef0 --- /dev/null +++ b/gasp-node/rollup/runtime/integration-test/src/proof_of_stake.rs @@ -0,0 +1,97 @@ +use crate::setup::*; +use frame_support::traits::{OnFinalize, OnInitialize}; +use orml_tokens::MultiTokenCurrencyExtended; +use pallet_market::PoolKind; + +type TokensOf = ::Currency; +type XykOf = ::ValuationApi; + +fn forward_to_block(n: u32) { + while frame_system::Pallet::::block_number() < n { + let i = frame_system::Pallet::::block_number() + 1; + frame_system::Pallet::::set_block_number(i); + + frame_system::Pallet::::on_initialize(i); + parachain_staking::Pallet::::on_initialize(i); + pallet_session::Pallet::::on_initialize(i); + + pallet_session::Pallet::::on_finalize(i); + parachain_staking::Pallet::::on_finalize(i); + frame_system::Pallet::::on_finalize(i); + } +} + +#[test] +fn rewards_are_aligned_with_sessions() { + ExtBuilder::default().build().execute_with(|| { + let alice: sp_runtime::AccountId20 = [0u8; 20].into(); + let bob: sp_runtime::AccountId20 = [1u8; 20].into(); + let charlie: sp_runtime::AccountId20 = [2u8; 20].into(); + let amount: u128 = 100_000u128; + let blocks_per_round = ::BlocksPerRound::get(); + + TokensOf::::create(&alice, amount).unwrap(); + TokensOf::::create(&alice, amount).unwrap(); + TokensOf::::create(&alice, amount).unwrap(); + TokensOf::::create(&alice, amount).unwrap(); + let first_token_id = TokensOf::::create(&alice, amount).unwrap(); + let second_token_id = TokensOf::::create(&alice, amount).unwrap(); + + let liqudity_token_id = TokensOf::::get_next_currency_id(); + + TokensOf::::mint(first_token_id, &bob, amount).unwrap(); + TokensOf::::mint(second_token_id, &bob, amount).unwrap(); + TokensOf::::mint(first_token_id, &charlie, amount).unwrap(); + TokensOf::::mint(second_token_id, &charlie, amount).unwrap(); + + XykOf::::create_pool( + RuntimeOrigin::signed(alice.clone()), + PoolKind::Xyk, + first_token_id, + 100000_u128, + second_token_id, + 100000_u128, + ) + .unwrap(); + + assert_eq!(0, pallet_session::Pallet::::current_index()); + ProofOfStake::update_pool_promotion(RuntimeOrigin::root(), liqudity_token_id, 1u8).unwrap(); + ProofOfStake::activate_liquidity( + RuntimeOrigin::signed(alice.clone()), + liqudity_token_id, + amount, + None, + ) + .unwrap(); + + forward_to_block(blocks_per_round - 10); + assert_eq!(0, pallet_session::Pallet::::current_index()); + XykOf::::mint_liquidity( + RuntimeOrigin::signed(charlie.clone()), + liqudity_token_id, + first_token_id, + 1000, + 10000, + ) + .unwrap(); + + forward_to_block(blocks_per_round - 2); + assert_eq!(0, pallet_session::Pallet::::current_index()); + XykOf::::mint_liquidity( + RuntimeOrigin::signed(bob.clone()), + liqudity_token_id, + first_token_id, + 1000, + 10000, + ) + .unwrap(); + + forward_to_block(blocks_per_round - 1); + assert_eq!(1, pallet_session::Pallet::::current_index()); + + assert_eq!( + ProofOfStake::get_rewards_info(charlie.clone(), liqudity_token_id), + ProofOfStake::get_rewards_info(bob.clone(), liqudity_token_id) + ); + }); +} diff --git a/gasp-node/rollup/runtime/integration-test/src/proxy.rs b/gasp-node/rollup/runtime/integration-test/src/proxy.rs new file mode 100644 index 000000000..85ce548e2 --- /dev/null +++ b/gasp-node/rollup/runtime/integration-test/src/proxy.rs @@ -0,0 +1,72 @@ +use crate::setup::*; + +type SystemError = frame_system::Error; + +const ASSET_ID_1: u32 = 1; +const LP_ASSET_ID: u32 = 2; + +#[test] +fn proxy_permissions_correct() { + ExtBuilder { + balances: vec![ + (AccountId::from(ALICE), NATIVE_ASSET_ID, 10000 * UNIT), + (AccountId::from(BOB), NATIVE_ASSET_ID, 1000 * UNIT), + (AccountId::from(BOB), ASSET_ID_1, 1000 * UNIT), + ], + ..ExtBuilder::default() + } + .build() + .execute_with(|| { + assert_ok!(Xyk::create_pool( + RuntimeOrigin::signed(AccountId::from(BOB)), + NATIVE_ASSET_ID, + 10_ * UNIT, + ASSET_ID_1, + 10 * UNIT, + )); + let transfer_call = Box::new(RuntimeCall::Tokens(orml_tokens::Call::transfer { + dest: AccountId::from(BOB).into(), + currency_id: NATIVE_ASSET_ID, + amount: 10 * UNIT, + })); + + // Alice's gives compound permissions to Bob + assert_ok!(Proxy::add_proxy( + RuntimeOrigin::signed(AccountId::from(ALICE)), + AccountId::from(BOB).into(), + ProxyType::AutoCompound, + 0 + )); + // Bob can't proxy for alice in a non compound call, proxy call works but nested call fails + assert_ok!(Proxy::proxy( + RuntimeOrigin::signed(AccountId::from(BOB)), + AccountId::from(ALICE).into(), + Some(ProxyType::AutoCompound), + transfer_call.clone() + )); + // hence the failure + System::assert_last_event( + pallet_proxy::Event::ProxyExecuted { result: Err(SystemError::CallFiltered.into()) } + .into(), + ); + // the transfer call fails as Bob only had compound permission for alice + assert!(Tokens::free_balance(NATIVE_ASSET_ID, &AccountId::from(BOB)) < 1000 * UNIT); + + // remove proxy works + assert_ok!(Proxy::remove_proxy( + RuntimeOrigin::signed(AccountId::from(ALICE)), + AccountId::from(BOB).into(), + ProxyType::AutoCompound, + 0 + )); + assert_noop!( + Proxy::proxy( + RuntimeOrigin::signed(AccountId::from(BOB)), + AccountId::from(ALICE).into(), + Some(ProxyType::AutoCompound), + transfer_call.clone() + ), + pallet_proxy::Error::::NotProxy + ); + }); +} diff --git a/gasp-node/rollup/runtime/integration-test/src/setup.rs b/gasp-node/rollup/runtime/integration-test/src/setup.rs new file mode 100644 index 000000000..d877f2cc6 --- /dev/null +++ b/gasp-node/rollup/runtime/integration-test/src/setup.rs @@ -0,0 +1,112 @@ +pub use std::default::Default; + +pub use frame_support::{ + assert_err, assert_noop, assert_ok, dispatch::DispatchResultWithPostInfo, error::BadOrigin, + traits::OnInitialize, +}; +pub use orml_traits::currency::{MultiCurrency, MultiCurrencyExtended}; +pub use rollup_runtime::Get; +pub use sp_io::TestExternalities; +pub use sp_runtime::{codec::Encode, BoundedVec, BuildStorage, MultiAddress, Permill}; + +pub use rollup_imports::*; + +mod rollup_imports { + pub use rollup_runtime::{ + consts::UNIT, + runtime_config::config::{ + orml_asset_registry::AssetMetadataOf, pallet_membership::FoundationAccountsProvider, + pallet_proxy::ProxyType, + }, + AccountId, AssetRegistry, Balance, Bootstrap, CustomMetadata, DispatchClass, FeeLock, + FoundationMembers, Identity, Maintenance, OnChargeTransaction, Pays, ProofOfStake, Proxy, + Rolldown, Runtime, RuntimeCall, RuntimeEvent, RuntimeOrigin, StableSwap, System, TokenId, + Tokens, TransferMembers, UncheckedExtrinsic, Weight, Xyk, XykMetadata, + }; + + pub const NATIVE_ASSET_ID: TokenId = rollup_runtime::runtime_config::tokens::RX_TOKEN_ID; +} + +/// Accounts +pub const ALICE: [u8; 32] = [4u8; 32]; +pub const BOB: [u8; 32] = [5u8; 32]; + +pub fn unit(decimals: u32) -> Balance { + 10u128.saturating_pow(decimals) +} + +pub fn cent(decimals: u32) -> Balance { + unit(decimals) / 100 +} + +pub fn millicent(decimals: u32) -> Balance { + cent(decimals) / 1000 +} + +pub fn microcent(decimals: u32) -> Balance { + millicent(decimals) / 1000 +} + +pub struct ExtBuilder { + pub assets: Vec<(TokenId, AssetMetadataOf)>, + pub balances: Vec<(AccountId, TokenId, Balance)>, +} + +impl Default for ExtBuilder { + fn default() -> Self { + Self { assets: vec![], balances: vec![] } + } +} + +impl ExtBuilder { + pub fn assets(mut self, assets: Vec<(TokenId, AssetMetadataOf)>) -> Self { + self.assets = assets; + self + } + + pub fn balances(mut self, balances: Vec<(AccountId, TokenId, Balance)>) -> Self { + self.balances = balances; + self + } + + pub fn build(self) -> sp_io::TestExternalities { + let _ = env_logger::builder().is_test(true).try_init(); + + let mut t = frame_system::GenesisConfig::::default().build_storage().unwrap(); + + orml_tokens::GenesisConfig:: { + tokens_endowment: self.balances, + created_tokens_for_staking: vec![], + } + .assimilate_storage(&mut t) + .unwrap(); + + parachain_staking::GenesisConfig::::default() + .assimilate_storage(&mut t) + .unwrap(); + + let encoded: Vec<(TokenId, Vec, Option<_>)> = self + .assets + .iter() + .map(|(id, meta)| { + let encoded = AssetMetadataOf::encode(&meta); + (*id, encoded, None) + }) + .collect(); + + orml_asset_registry::GenesisConfig:: { assets: encoded } + .assimilate_storage(&mut t) + .unwrap(); + + pallet_membership::GenesisConfig:: { + members: BoundedVec::truncate_from([AccountId::from(ALICE)].to_vec()), + ..Default::default() + } + .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/gasp-node/rollup/runtime/integration-test/src/xyk.rs b/gasp-node/rollup/runtime/integration-test/src/xyk.rs new file mode 100644 index 000000000..fba87570b --- /dev/null +++ b/gasp-node/rollup/runtime/integration-test/src/xyk.rs @@ -0,0 +1,55 @@ +use crate::setup::*; + +const ASSET_ID_1: u32 = 1; + +fn test_env(xyk_metadata: Option) -> TestExternalities { + ExtBuilder { + balances: vec![ + (AccountId::from(ALICE), NATIVE_ASSET_ID, 100 * UNIT), + (AccountId::from(ALICE), ASSET_ID_1, 100 * UNIT), + ], + assets: vec![( + ASSET_ID_1, + AssetMetadataOf { + decimals: 18, + name: BoundedVec::truncate_from(b"Asset".to_vec()), + symbol: BoundedVec::truncate_from(b"Asset".to_vec()), + existential_deposit: Default::default(), + additional: CustomMetadata { xyk: xyk_metadata, ..CustomMetadata::default() }, + }, + )], + ..ExtBuilder::default() + } + .build() +} + +fn create_pool() -> DispatchResultWithPostInfo { + pallet_xyk::Pallet::::create_pool( + RuntimeOrigin::signed(AccountId::from(ALICE)), + NATIVE_ASSET_ID, + 10_ * UNIT, + ASSET_ID_1, + 10 * UNIT, + ) +} + +#[test] +fn create_pool_works_meta_allowed() { + test_env(Some(XykMetadata { operations_disabled: false })).execute_with(|| { + assert_ok!(create_pool()); + }); +} + +#[test] +fn create_pool_works_no_meta() { + test_env(None).execute_with(|| { + assert_ok!(create_pool()); + }); +} + +#[test] +fn create_pool_disabled_meta_disabled() { + test_env(Some(XykMetadata { operations_disabled: true })).execute_with(|| { + assert_err!(create_pool(), pallet_xyk::Error::::FunctionNotAvailableForThisToken); + }); +} diff --git a/gasp-node/rollup/runtime/src/constants.rs b/gasp-node/rollup/runtime/src/constants.rs new file mode 100644 index 000000000..f0ece01e3 --- /dev/null +++ b/gasp-node/rollup/runtime/src/constants.rs @@ -0,0 +1,50 @@ +#![cfg_attr(not(feature = "std"), no_std)] +pub mod fee { + use crate::{ + runtime_config::{config::frame_system::VerExtrinsicBaseWeight, consts::UNIT}, + Balance, + }; + use frame_support::weights::{ + constants::WEIGHT_REF_TIME_PER_SECOND, WeightToFeeCoefficient, WeightToFeeCoefficients, + WeightToFeePolynomial, + }; + use smallvec::smallvec; + use sp_runtime::Perbill; + + /// Handles converting a weight scalar to a fee value, based on the scale and granularity of the + /// node's balance type. + /// + /// This should typically create a mapping between the following ranges: + /// - `[0, MAXIMUM_BLOCK_WEIGHT]` + /// - `[Balance::min, Balance::max]` + /// + /// Yet, it can be used for any other sort of change to weight-fee. Some examples being: + /// - Setting it to `0` will essentially disable the weight fee. + /// - Setting it to `1` will cause the literal `#[weight = x]` values to be charged. + pub struct WeightToFee; + impl WeightToFeePolynomial for WeightToFee { + type Balance = Balance; + fn polynomial() -> WeightToFeeCoefficients { + // in Rococo, extrinsic base weight (smallest non-zero weight) is mapped to 1 MILLIUNIT: + // in mangata, we map to 1/10 of that, or 1/10 MILLIUNIT + let p = base_tx_in_mgx(); + let q = Balance::from(VerExtrinsicBaseWeight::get().ref_time()); + smallvec![WeightToFeeCoefficient { + degree: 1, + negative: false, + coeff_frac: Perbill::from_rational(p % q, q), + coeff_integer: p / q, + }] + } + } + + pub fn base_tx_in_mgx() -> Balance { + UNIT + } + + pub fn mgx_per_second() -> u128 { + let base_weight = Balance::from(VerExtrinsicBaseWeight::get().ref_time()); + let base_per_second = (WEIGHT_REF_TIME_PER_SECOND / base_weight as u64) as u128; + base_per_second * base_tx_in_mgx() + } +} diff --git a/gasp-node/rollup/runtime/src/lib.rs b/gasp-node/rollup/runtime/src/lib.rs new file mode 100644 index 000000000..c983957ba --- /dev/null +++ b/gasp-node/rollup/runtime/src/lib.rs @@ -0,0 +1,1694 @@ +#![cfg_attr(not(feature = "std"), no_std)] +// `construct_runtime!` does a lot of recursion and requires us to increase the limit to 256. +#![recursion_limit = "256"] + +// Make the WASM binary available. +#[cfg(feature = "std")] +include!(concat!(env!("OUT_DIR"), "/wasm_binary.rs")); + +use codec::{alloc::string::String, Decode, Encode, MaxEncodedLen}; +use sp_api::impl_runtime_apis; +use sp_application_crypto::ByteArray; +use sp_consensus_aura::sr25519::AuthorityId as AuraId; +use sp_core::{crypto::KeyTypeId, OpaqueMetadata}; +#[cfg(any(feature = "std", test))] +pub use sp_runtime::BuildStorage; +use sp_runtime::{ + account::EthereumSignature, + create_runtime_str, generic, impl_opaque_keys, + traits::{ + AccountIdConversion, BlakeTwo256, Block as BlockT, Convert, ConvertInto, DispatchInfoOf, + Dispatchable, Header as HeaderT, IdentifyAccount, IdentityLookup, Keccak256, NumberFor, + PostDispatchInfoOf, SignedExtension, StaticLookup, Verify, Zero, + }, + transaction_validity::{InvalidTransaction, TransactionSource, TransactionValidity}, + ApplyExtrinsicResult, BoundedVec, DispatchError, ExtrinsicInclusionMode, FixedPointNumber, + Perbill, Percent, Permill, RuntimeDebug, +}; +use sp_std::{ + cmp::Ordering, + convert::{TryFrom, TryInto}, + marker::PhantomData, + prelude::*, +}; +#[cfg(feature = "std")] +use sp_version::NativeVersion; +use sp_version::RuntimeVersion; + +pub use mangata_support::{ + pools::ValuateFor, + traits::{ + AssetRegistryApi, AssetRegistryProviderTrait, FeeLockTriggerTrait, + GetMaintenanceStatusTrait, PreValidateSwaps, ProofOfStakeRewardsApi, + }, +}; +pub use mangata_types::assets::{CustomMetadata, L1Asset, XcmMetadata, XykMetadata}; + +// A few exports that help ease life for downstream crates. +#[cfg(feature = "runtime-benchmarks")] +pub use frame_support::traits::OriginTrait; +pub use frame_support::{ + construct_runtime, + dispatch::{DispatchClass, DispatchResult, Pays}, + ensure, parameter_types, + traits::{ + tokens::{ + currency::{MultiTokenCurrency, MultiTokenImbalanceWithZeroTrait}, + pay::PayFromAccount, + UnityAssetBalanceConversion, + }, + ConstBool, ConstU128, ConstU32, ConstU64, ConstU8, Contains, EitherOfDiverse, EnsureOrigin, + EnsureOriginWithArg, Equals, Everything, ExistenceRequirement, FindAuthor, Get, Imbalance, + InstanceFilter, KeyOwnerProofSystem, NeverEnsureOrigin, Nothing, Randomness, StorageInfo, + WithdrawReasons, + }, + unsigned::TransactionValidityError, + weights::{ + constants::{ + BlockExecutionWeight, ExtrinsicBaseWeight, RocksDbWeight, WEIGHT_REF_TIME_PER_SECOND, + }, + ConstantMultiplier, IdentityFee, Weight, + }, + PalletId, StorageValue, +}; +use frame_support::{dispatch::PostDispatchInfo, traits::SameOrOther}; +pub use frame_system::{ + limits::{BlockLength, BlockWeights}, + Call as SystemCall, ConsumedWeight, EnsureRoot, EnsureRootWithSuccess, SetCode, +}; + +pub use orml_tokens::Call as TokensCall; +pub use pallet_timestamp::Call as TimestampCall; +pub use runtime_config::*; +use static_assertions::const_assert; +use xyk_runtime_api::RpcAssetMetadata; + +pub use orml_tokens::{self, MultiTokenCurrencyExtended}; +pub use orml_traits::{ + asset_registry::{AssetMetadata, AssetProcessor}, + parameter_type_with_key, +}; +use pallet_grandpa::AuthorityId as GrandpaId; +use pallet_identity::legacy::IdentityInfo; +pub use pallet_issuance::IssuanceInfo; +pub use pallet_sudo_mangata; +pub use pallet_sudo_origin; +pub use pallet_transaction_payment::{ConstFeeMultiplier, Multiplier, OnChargeTransaction}; +pub use pallet_xyk::{self, AssetMetadataMutationTrait}; +pub use scale_info::TypeInfo; + +pub mod runtime_config; +pub mod weights; +use runtime_config::config as cfg; +pub use runtime_config::{currency::*, runtime_types, tokens, types::*, CallType}; + +pub mod constants; + +/// Block header type as expected by this runtime. +pub type Header = runtime_types::Header; +/// Block type as expected by this runtime. +pub type Block = runtime_types::Block; + +/// A Block signed with a Justification +pub type SignedBlock = runtime_types::SignedBlock; +/// BlockId type as expected by this runtime. +pub type BlockId = runtime_types::BlockId; +/// The SignedExtension to the basic transaction logic. +pub type SignedExtra = runtime_types::SignedExtra; +/// The payload being signed in transactions. +pub type SignedPayload = runtime_types::SignedPayload; +/// Unchecked extrinsic type as expected by this runtime. +pub type UncheckedExtrinsic = runtime_types::UncheckedExtrinsic; +/// Extrinsic type that has already been checked. +pub type CheckedExtrinsic = runtime_types::CheckedExtrinsic; + +/// Executive: handles dispatch to the various modules. +pub type Executive = frame_executive::Executive< + Runtime, + Block, + frame_system::ChainContext, + Runtime, + AllPalletsWithSystem, + Migrations, +>; + +type Migrations = (); + +/// Opaque types. These are used by the CLI to instantiate machinery that don't need to know +/// the specifics of the runtime. They can then be made to be agnostic over specific formats +/// of data like extrinsics, allowing for them to continue syncing the network through upgrades +/// to even the core data structures. +pub mod opaque { + use super::*; + /// Opaque block header type. + pub type Header = runtime_types::Header; + /// Opaque block type. + pub type Block = runtime_types::OpaqueBlock; + /// Opaque block identifier type. + pub type BlockId = runtime_types::OpaqueBlockId; +} + +impl_opaque_keys! { + pub struct SessionKeys { + pub aura: Aura, + pub grandpa: Grandpa, + } +} + +// To learn more about runtime versioning, see: +// https://docs.substrate.io/main-docs/build/upgrade#runtime-versioning +#[sp_version::runtime_version] +pub const VERSION: RuntimeVersion = RuntimeVersion { + spec_name: create_runtime_str!("rollup-chain"), + impl_name: create_runtime_str!("rollup-chain"), + authoring_version: 1, + // The version of the runtime specification. A full node will not attempt to use its native + // runtime in substitute for the on-chain Wasm runtime unless all of `spec_name`, + // `spec_version`, and `authoring_version` are the same between Wasm and native. + // This value is set to 100 to notify Polkadot-JS App (https://polkadot.js.org/apps) to use + // the compatible custom types. + spec_version: 102, + impl_version: 2, + apis: RUNTIME_API_VERSIONS, + transaction_version: 1, + state_version: 1, +}; + +/// The version information used to identify this runtime when compiled natively. +#[cfg(feature = "std")] +pub fn native_version() -> NativeVersion { + NativeVersion { runtime_version: VERSION, can_author_with: Default::default() } +} + +// Configure FRAME pallets to include in runtime. +impl frame_system::Config for Runtime { + /// The basic call filter to use in dispatchable. + type BaseCallFilter = Everything; + /// Block & extrinsics weights: base values and limits. + type BlockWeights = cfg::frame_system::RuntimeBlockWeights; + /// The maximum length of a block (in bytes). + type BlockLength = cfg::frame_system::RuntimeBlockLength; + /// The ubiquitous origin type. + type RuntimeOrigin = RuntimeOrigin; + /// The aggregated dispatch type that is available for extrinsics. + type RuntimeCall = RuntimeCall; + /// The index type for storing how many extrinsics an account has signed. + type Nonce = Nonce; + /// The type for hashing blocks and tries. + type Hash = Hash; + /// The hashing algorithm used. + type Hashing = BlakeTwo256; + /// The block type. + type Block = Block; + /// The identifier used to distinguish between accounts. + type AccountId = AccountId; + /// The lookup mechanism to get account ID from whatever is passed in dispatchers. + type Lookup = IdentityLookup; + /// The ubiquitous event type. + type RuntimeEvent = RuntimeEvent; + /// Maximum number of block number to block hash mappings to keep (oldest pruned first). + type BlockHashCount = cfg::frame_system::BlockHashCount; + /// The weight of database operations that the runtime can invoke. + type DbWeight = RocksDbWeight; + /// Runtime version. + type Version = cfg::frame_system::Version; + /// Converts a module to an index of this module in the runtime. + type PalletInfo = PalletInfo; + /// The data to be stored in an account. + type AccountData = (); + /// What to do if a new account is created. + type OnNewAccount = (); + /// What to do if an account is fully reaped from the system. + type OnKilledAccount = (); + /// Weight information for the extrinsics of this pallet. + type SystemWeightInfo = weights::frame_system_weights::ModuleWeight; + /// This is used as an identifier of the chain. 42 is the generic substrate prefix. + type SS58Prefix = cfg::frame_system::SS58Prefix; + /// The action to take on a Runtime Upgrade + type OnSetCode = cfg::frame_system::MaintenanceGatedSetCode; + /// The maximum number of consumers allowed on a single account. + type MaxConsumers = cfg::frame_system::MaxConsumers; + type RuntimeTask = RuntimeTask; + type SingleBlockMigrations = (); + type MultiBlockMigrator = (); + type PreInherents = (); + type PostInherents = (); + type PostTransactions = (); +} + +impl pallet_timestamp::Config for Runtime { + /// A timestamp: milliseconds since the unix epoch. + type Moment = u64; + type OnTimestampSet = (); + type MinimumPeriod = cfg::pallet_timestamp::MinimumPeriod; + type WeightInfo = weights::pallet_timestamp_weights::ModuleWeight; +} + +impl pallet_authorship::Config for Runtime { + type FindAuthor = pallet_session::FindAccountFromAuthorIndex; + type EventHandler = ParachainStaking; +} + +impl pallet_treasury::Config for Runtime { + type PalletId = cfg::pallet_treasury::TreasuryPalletId; + type Currency = orml_tokens::CurrencyAdapter; + type ApproveOrigin = EnsureRoot; + type RejectOrigin = EnsureRoot; + type RuntimeEvent = RuntimeEvent; + type OnSlash = (); + type ProposalBond = cfg::pallet_treasury::ProposalBond; + type ProposalBondMinimum = cfg::pallet_treasury::ProposalBondMinimum; + type ProposalBondMaximum = cfg::pallet_treasury::ProposalBondMaximum; + type SpendPeriod = cfg::pallet_treasury::SpendPeriod; + type Burn = cfg::pallet_treasury::Burn; + type BurnDestination = (); + type SpendFunds = (); + type WeightInfo = weights::pallet_treasury_weights::ModuleWeight; + type MaxApprovals = cfg::pallet_treasury::MaxApprovals; + type BeneficiaryLookup = IdentityLookup; + type Beneficiary = AccountId; + type AssetKind = (); + type PayoutPeriod = cfg::pallet_treasury::SpendPayoutPeriod; + type Paymaster = PayFromAccount; + type BalanceConverter = UnityAssetBalanceConversion; + #[cfg(not(feature = "runtime-benchmarks"))] + type SpendOrigin = NeverEnsureOrigin; + #[cfg(feature = "runtime-benchmarks")] + type BenchmarkHelper = (); + #[cfg(feature = "runtime-benchmarks")] + type SpendOrigin = EnsureRootWithSuccess>; +} + +parameter_types! { + pub TreasuryAccount: AccountId = cfg::TreasuryPalletIdOf::::get().into_account_truncating(); +} + +// The MaxLocks (on a who-token_id pair) that is allowed by orml_tokens +// must exceed the total possible locks that can be applied to it, ALL pallets considered +// This is because orml_tokens uses BoundedVec for Locks storage item and does not inform on failure +// Balances uses WeakBoundedVec and so does not fail +const_assert!( + ::MaxLocks::get() >= + ::MAX_VESTING_SCHEDULES +); + +const_assert!( + ::RewardsSchedulesLimit::get() >= + (::SchedulesPerBlock::get() - 1) * + ::BlocksPerRound::get() +); + +impl orml_tokens::Config for Runtime { + type RuntimeEvent = RuntimeEvent; + type Balance = Balance; + type Amount = Amount; + type CurrencyId = TokenId; + type WeightInfo = weights::orml_tokens_weights::ModuleWeight; + type ExistentialDeposits = cfg::orml_tokens::ExistentialDeposits; + type MaxLocks = cfg::orml_tokens::MaxLocks; + type DustRemovalWhitelist = + cfg::orml_tokens::DustRemovalWhitelist>; + type CurrencyHooks = (); + type MaxReserves = (); + type ReserveIdentifier = cfg::orml_tokens::ReserveIdentifier; + type NontransferableTokens = tokens::NontransferableTokens; + type NontransferableTokensAllowList = TransferMembers; +} + +impl pallet_xyk::Config for Runtime { + type RuntimeEvent = RuntimeEvent; + type MaintenanceStatusProvider = Maintenance; + type ActivationReservesProvider = MultiPurposeLiquidity; + type Currency = orml_tokens::MultiTokenCurrencyAdapter; + type NativeCurrencyId = tokens::RxTokenId; + type TreasuryPalletId = cfg::TreasuryPalletIdOf; + type BnbTreasurySubAccDerive = cfg::pallet_xyk::BnbTreasurySubAccDerive; + type PoolFeePercentage = fees::PoolFeePercentage; + type TreasuryFeePercentage = fees::TreasuryFeePercentage; + type BuyAndBurnFeePercentage = fees::BuyAndBurnFeePercentage; + type LiquidityMiningRewards = ProofOfStake; + type VestingProvider = Vesting; + type DisallowedPools = Bootstrap; + type DisabledTokens = + (cfg::pallet_xyk::TestTokensFilter, cfg::pallet_xyk::AssetRegisterFilter); + type AssetMetadataMutation = cfg::pallet_xyk::AssetMetadataMutation; + type FeeLockWeight = pallet_fee_lock::FeeLockWeightProvider; + type WeightInfo = weights::pallet_xyk_weights::ModuleWeight; +} + +impl pallet_stable_swap::Config for Runtime { + type RuntimeEvent = RuntimeEvent; + type Currency = orml_tokens::MultiTokenCurrencyAdapter; + type Balance = Balance; + type HigherPrecisionBalance = sp_core::U512; + type CurrencyId = TokenId; + type TreasuryPalletId = cfg::TreasuryPalletIdOf; + type BnbTreasurySubAccDerive = cfg::pallet_xyk::BnbTreasurySubAccDerive; + type MarketTotalFee = fees::MarketTotalFee; + type MarketTreasuryFeePart = fees::MarketTreasuryFeePart; + type MarketBnBFeePart = fees::MarketBnBFeePart; + type MaxApmCoeff = ConstU128<1_000_000>; + type DefaultApmCoeff = ConstU128<1_000>; + type MaxAssetsInPool = ConstU32<2>; + type WeightInfo = (); +} + +impl pallet_proof_of_stake::Config for Runtime { + type RuntimeEvent = RuntimeEvent; + type ActivationReservesProvider = MultiPurposeLiquidity; + type NativeCurrencyId = tokens::RxTokenId; + type Currency = orml_tokens::MultiTokenCurrencyAdapter; + type LiquidityMiningIssuanceVault = cfg::pallet_issuance::LiquidityMiningIssuanceVault; + type RewardsDistributionPeriod = cfg::SessionLenghtOf; + type WeightInfo = weights::pallet_proof_of_stake_weights::ModuleWeight; + type RewardsSchedulesLimit = cfg::pallet_proof_of_stake::RewardsSchedulesLimit; + type Min3rdPartyRewardValutationPerSession = + cfg::pallet_proof_of_stake::Min3rdPartyRewardValutationPerSession; + type Min3rdPartyRewardVolume = cfg::pallet_proof_of_stake::Min3rdPartyRewardVolume; + type SchedulesPerBlock = cfg::pallet_proof_of_stake::SchedulesPerBlock; + type ValuationApi = Market; + type NontransferableTokens = tokens::NontransferableTokens; + #[cfg(feature = "runtime-benchmarks")] + type Xyk = Xyk; +} + +impl pallet_bootstrap::BootstrapBenchmarkingConfig for Runtime {} + +impl pallet_bootstrap::Config for Runtime { + type RuntimeEvent = RuntimeEvent; + type MaintenanceStatusProvider = Maintenance; + type PoolCreateApi = Xyk; + type DefaultBootstrapPromotedPoolWeight = + cfg::pallet_bootstrap::DefaultBootstrapPromotedPoolWeight; + type BootstrapUpdateBuffer = cfg::pallet_bootstrap::BootstrapUpdateBuffer; + type Currency = orml_tokens::MultiTokenCurrencyAdapter; + type VestingProvider = Vesting; + type TreasuryPalletId = cfg::TreasuryPalletIdOf; + type RewardsApi = ProofOfStake; + type ClearStorageLimit = cfg::pallet_bootstrap::ClearStorageLimit; + type WeightInfo = weights::pallet_bootstrap_weights::ModuleWeight; + type AssetRegistryApi = cfg::pallet_bootstrap::EnableAssetPoolApi; +} + +impl pallet_utility_mangata::Config for Runtime { + type RuntimeEvent = RuntimeEvent; + type RuntimeCall = RuntimeCall; + type DisallowedInBatch = cfg::pallet_utility_mangata::DisallowedInBatch; + type PalletsOrigin = OriginCaller; + type WeightInfo = weights::pallet_utility_mangata_weights::ModuleWeight; +} + +use cfg::pallet_transaction_payment::{ + FeeHelpers, OnChargeHandler, ToAuthor, TriggerEvent, TwoCurrencyOnChargeAdapter, +}; + +// TODO: renaming foo causes compiler error +pub struct Foo(PhantomData); +impl TriggerEvent for Foo +where + T: frame_system::Config, +{ + fn trigger(who: T::AccountId, token_id: TokenId, fee: u128, tip: u128) { + TransactionPayment::deposit_event( + pallet_transaction_payment::Event::::TransactionFeePaid { + who, + token_id, + actual_fee: fee, + tip, + }, + ); + } +} + +impl Into for RuntimeCall { + fn into(self) -> CallType { + match self { + RuntimeCall::Market(pallet_market::Call::multiswap_asset { + swap_pool_list, + asset_id_in, + asset_amount_in, + asset_id_out, + min_amount_out, + }) => { + // we need the exact out value to consider high value swap for the bought asset too + let asset_amount_out = if swap_pool_list.len() == 1 { + Market::calculate_sell_price(swap_pool_list[0], asset_id_in, asset_amount_in) + .unwrap_or(min_amount_out) + } else { + min_amount_out + }; + CallType::Swap { + swap_pool_list, + asset_id_in, + asset_amount_in, + asset_id_out, + asset_amount_out, + } + }, + RuntimeCall::Market(pallet_market::Call::multiswap_asset_buy { + swap_pool_list, + asset_id_out, + asset_amount_out, + asset_id_in, + max_amount_in, + }) => { + // we need the exact in value to consider high value swap for the sold asset too + let asset_amount_in = if swap_pool_list.len() == 1 { + Market::calculate_buy_price(swap_pool_list[0], asset_id_out, asset_amount_out) + .unwrap_or(max_amount_in) + } else { + max_amount_in + }; + CallType::Swap { + swap_pool_list, + asset_id_in, + asset_amount_in, + asset_id_out, + asset_amount_out, + } + }, + RuntimeCall::FeeLock(pallet_fee_lock::Call::unlock_fee { .. }) => CallType::UnlockFee, + _ => CallType::Other, + } + } +} + +use sp_runtime::generic::{ExtendedCall, MetamaskSigningCtx}; +use sp_std::fmt::Write; + +impl ExtendedCall for RuntimeCall { + fn context(&self) -> Option { + let mut call = String::new(); + if let Some(url) = pallet_metamask_signature::Pallet::::get_decode_url() { + let _ = write!(&mut call, "{}", url); + } + let _ = write!(&mut call, "{}", array_bytes::bytes2hex("0x", self.encode())); + pallet_metamask_signature::Pallet::::get_eip_metadata() + .map(|eip| MetamaskSigningCtx { call, eip712: eip }) + } +} + +pub type OnChargeTransactionHandler = TwoCurrencyOnChargeAdapter< + orml_tokens::MultiTokenCurrencyAdapter, + ToAuthor, + tokens::RxTokenId, + tokens::EthTokenId, + frame_support::traits::ConstU128<30_000>, + Foo, +>; + +impl pallet_transaction_payment::Config for Runtime { + type RuntimeEvent = RuntimeEvent; + type OnChargeTransaction = OnChargeHandler< + orml_tokens::MultiTokenCurrencyAdapter, + ToAuthor, + OnChargeTransactionHandler, + FeeLock, + >; + type LengthToFee = cfg::pallet_transaction_payment::LengthToFee; + type WeightToFee = constants::fee::WeightToFee; + type FeeMultiplierUpdate = cfg::pallet_transaction_payment::FeeMultiplierUpdate; + type OperationalFeeMultiplier = cfg::pallet_transaction_payment::OperationalFeeMultiplier; +} + +parameter_types! { + pub const MaxCuratedTokens: u32 = 100; +} + +impl pallet_fee_lock::Config for Runtime { + type RuntimeEvent = RuntimeEvent; + type MaxCuratedTokens = cfg::pallet_fee_lock::MaxCuratedTokens; + type Tokens = orml_tokens::MultiTokenCurrencyAdapter; + type ValuateForNative = Market; + type NativeTokenId = tokens::RxTokenId; + type WeightInfo = weights::pallet_fee_lock_weights::ModuleWeight; + #[cfg(feature = "runtime-benchmarks")] + type Xyk = Xyk; +} + +impl pallet_session::Config for Runtime { + type RuntimeEvent = RuntimeEvent; + type ValidatorId = ::AccountId; + // we don't have stash and controller, thus we don't need the convert as well. + type ValidatorIdOf = ConvertInto; + type ShouldEndSession = ParachainStaking; + type NextSessionRotation = ParachainStaking; + type SessionManager = ParachainStaking; + type SessionHandler = ::KeyTypeIdProviders; + type Keys = SessionKeys; + type WeightInfo = weights::pallet_session_weights::ModuleWeight; +} + +impl pallet_aura::Config for Runtime { + type AuthorityId = AuraId; + type DisabledValidators = (); + type MaxAuthorities = cfg::pallet_aura::MaxAuthorities; + type AllowMultipleBlocksPerSlot = ConstBool; + + #[cfg(feature = "experimental")] + type SlotDuration = pallet_aura::MinimumPeriodTimesTwo; +} + +impl pallet_grandpa::Config for Runtime { + type RuntimeEvent = RuntimeEvent; + + type WeightInfo = (); + type MaxAuthorities = ConstU32<32>; + type MaxNominators = ConstU32<0>; + type MaxSetIdSessionEntries = ConstU64<0>; + + type KeyOwnerProof = sp_core::Void; + type EquivocationReportSystem = (); +} + +impl pallet_sudo_mangata::Config for Runtime { + type RuntimeEvent = RuntimeEvent; + type RuntimeCall = RuntimeCall; + type WeightInfo = (); +} + +impl pallet_sudo_origin::Config for Runtime { + type RuntimeEvent = RuntimeEvent; + type RuntimeCall = RuntimeCall; + type SudoOrigin = cfg::pallet_sudo_origin::SudoOrigin; +} + +type CouncilCollective = pallet_collective_mangata::Instance1; +impl pallet_collective_mangata::Config for Runtime { + type RuntimeOrigin = RuntimeOrigin; + type Proposal = RuntimeCall; + type RuntimeEvent = RuntimeEvent; + type MotionDuration = cfg::pallet_collective_mangata::CouncilMotionDuration; + type ProposalCloseDelay = cfg::pallet_collective_mangata::CouncilProposalCloseDelay; + type MaxProposals = cfg::pallet_collective_mangata::CouncilMaxProposals; + type MaxMembers = cfg::pallet_collective_mangata::CouncilMaxMembers; + type FoundationAccountsProvider = cfg::pallet_membership::FoundationAccountsProvider; + type DefaultVote = pallet_collective_mangata::PrimeDefaultVote; + type WeightInfo = weights::pallet_collective_mangata_weights::ModuleWeight; + type SetMembersOrigin = cfg::pallet_collective_mangata::SetMembersOrigin; + type MaxProposalWeight = cfg::pallet_collective_mangata::MaxProposalWeight; +} + +// To ensure that BlocksPerRound is not zero, breaking issuance calculations +// Also since 1 block is used for session change, atleast 1 block more needed for extrinsics to work +const_assert!(::BlocksPerRound::get() >= 2); + +impl parachain_staking::Config for Runtime { + type RuntimeEvent = RuntimeEvent; + type StakingReservesProvider = MultiPurposeLiquidity; + type Currency = orml_tokens::MultiTokenCurrencyAdapter; + type MonetaryGovernanceOrigin = EnsureRoot; + type BlocksPerRound = cfg::parachain_staking::BlocksPerRound; + type LeaveCandidatesDelay = cfg::parachain_staking::LeaveCandidatesDelay; + type CandidateBondDelay = cfg::parachain_staking::CandidateBondDelay; + type LeaveDelegatorsDelay = cfg::parachain_staking::LeaveDelegatorsDelay; + type RevokeDelegationDelay = cfg::parachain_staking::RevokeDelegationDelay; + type DelegationBondDelay = cfg::parachain_staking::DelegationBondDelay; + type RewardPaymentDelay = cfg::parachain_staking::RewardPaymentDelay; + type MinSelectedCandidates = cfg::parachain_staking::MinSelectedCandidates; + type MaxCollatorCandidates = cfg::parachain_staking::MaxCollatorCandidates; + type MaxTotalDelegatorsPerCandidate = cfg::parachain_staking::MaxTotalDelegatorsPerCandidate; + type MaxDelegatorsPerCandidate = cfg::parachain_staking::MaxDelegatorsPerCandidate; + type MaxDelegationsPerDelegator = cfg::parachain_staking::MaxDelegationsPerDelegator; + type DefaultCollatorCommission = cfg::parachain_staking::DefaultCollatorCommission; + type MinCollatorStk = cfg::parachain_staking::MinCollatorStk; + type MinCandidateStk = cfg::parachain_staking::MinCandidateStk; + type MinDelegation = cfg::parachain_staking::MinDelegatorStk; + type NativeTokenId = tokens::RxTokenId; + type ValuateForNative = Market; + type Issuance = Issuance; + type StakingIssuanceVault = cfg::parachain_staking::StakingIssuanceVaultOf; + type FallbackProvider = Council; + // type SequencerStakingProvider = SequencerStaking; + type WeightInfo = weights::parachain_staking_weights::ModuleWeight; + type DefaultPayoutLimit = cfg::parachain_staking::DefaultPayoutLimit; + type SequencerStakingRewards = SequencerStaking; +} + +impl parachain_staking::StakingBenchmarkConfig for Runtime { + #[cfg(feature = "runtime-benchmarks")] + type Balance = Balance; + #[cfg(feature = "runtime-benchmarks")] + type CurrencyId = TokenId; + #[cfg(feature = "runtime-benchmarks")] + type RewardsApi = ProofOfStake; + #[cfg(feature = "runtime-benchmarks")] + type Xyk = Xyk; +} + +impl pallet_xyk::XykBenchmarkingConfig for Runtime {} + +// Issuance history must be kept for atleast the staking reward delay +const_assert!( + ::RewardPaymentDelay::get() <= + ::HistoryLimit::get() +); + +impl pallet_issuance::Config for Runtime { + type RuntimeEvent = RuntimeEvent; + type NativeCurrencyId = tokens::RxTokenId; + type Tokens = orml_tokens::MultiTokenCurrencyAdapter; + type BlocksPerRound = cfg::parachain_staking::BlocksPerRound; + type HistoryLimit = cfg::pallet_issuance::HistoryLimit; + type LiquidityMiningIssuanceVault = cfg::pallet_issuance::LiquidityMiningIssuanceVault; + type StakingIssuanceVault = cfg::pallet_issuance::StakingIssuanceVault; + type SequencersIssuanceVault = cfg::pallet_issuance::SequencerIssuanceVault; + type TotalCrowdloanAllocation = cfg::pallet_issuance::TotalCrowdloanAllocation; + type LinearIssuanceAmount = cfg::pallet_issuance::LinearIssuanceAmount; + type LinearIssuanceBlocks = cfg::pallet_issuance::LinearIssuanceBlocks; + type LiquidityMiningSplit = cfg::pallet_issuance::LiquidityMiningSplit; + type StakingSplit = cfg::pallet_issuance::StakingSplit; + type SequencersSplit = cfg::pallet_issuance::SequencerSplit; + type ImmediateTGEReleasePercent = cfg::pallet_issuance::ImmediateTGEReleasePercent; + type TGEReleasePeriod = cfg::pallet_issuance::TGEReleasePeriod; + type TGEReleaseBegin = cfg::pallet_issuance::TGEReleaseBegin; + type VestingProvider = Vesting; + type WeightInfo = weights::pallet_issuance_weights::ModuleWeight; + type LiquidityMiningApi = ProofOfStake; +} + +impl pallet_vesting_mangata::Config for Runtime { + type RuntimeEvent = RuntimeEvent; + type Tokens = orml_tokens::MultiTokenCurrencyAdapter; + type BlockNumberToBalance = ConvertInto; + type MinVestedTransfer = cfg::pallet_vesting_mangata::MinVestedTransfer; + type BlockNumberProvider = System; + type WeightInfo = weights::pallet_vesting_mangata_weights::ModuleWeight; + // `VestingInfo` encode length is 36bytes. 28 schedules gets encoded as 1009 bytes, which is the + // highest number of schedules that encodes less than 2^10. + const MAX_VESTING_SCHEDULES: u32 = 50; + type UnvestedFundsAllowedWithdrawReasons = + cfg::pallet_vesting_mangata::UnvestedFundsAllowedWithdrawReasons; +} + +impl pallet_crowdloan_rewards::Config for Runtime { + type RuntimeEvent = RuntimeEvent; + type Initialized = cfg::pallet_crowdloan_rewards::Initialized; + type InitializationPayment = cfg::pallet_crowdloan_rewards::InitializationPayment; + type MaxInitContributors = cfg::pallet_crowdloan_rewards::MaxInitContributorsBatchSizes; + type MinimumReward = cfg::pallet_crowdloan_rewards::MinimumReward; + type RewardAddressRelayVoteThreshold = cfg::pallet_crowdloan_rewards::RelaySignaturesThreshold; + type NativeTokenId = tokens::RxTokenId; + type Tokens = orml_tokens::MultiTokenCurrencyAdapter; + type RelayChainAccountId = sp_runtime::AccountId20; + type RewardAddressChangeOrigin = EnsureRoot; + type SignatureNetworkIdentifier = cfg::pallet_crowdloan_rewards::SigantureNetworkIdentifier; + type RewardAddressAssociateOrigin = EnsureRoot; + type VestingBlockNumber = BlockNumber; + type VestingBlockProvider = System; + type VestingProvider = Vesting; + type WeightInfo = weights::pallet_crowdloan_rewards_weights::ModuleWeight; +} + +impl pallet_multipurpose_liquidity::Config for Runtime { + type RuntimeEvent = RuntimeEvent; + type MaxRelocks = cfg::MaxLocksOf; + type Tokens = orml_tokens::MultiTokenCurrencyAdapter; + type NativeCurrencyId = tokens::RxTokenId; + type VestingProvider = Vesting; + type Xyk = Xyk; + type WeightInfo = weights::pallet_multipurpose_liquidity_weights::ModuleWeight; +} + +impl orml_asset_registry::Config for Runtime { + type RuntimeEvent = RuntimeEvent; + type CustomMetadata = CustomMetadata; + type AssetId = TokenId; + type AuthorityOrigin = cfg::orml_asset_registry::AssetAuthority; + type AssetProcessor = cfg::orml_asset_registry::SequentialIdWithCreation; + type Balance = Balance; + type WeightInfo = weights::orml_asset_registry_weights::ModuleWeight; + type StringLimit = cfg::orml_asset_registry::StringLimit; + type Hash = Hash; + type Hashing = Keccak256; + type L1AssetAuthority = EitherOfDiverse< + EnsureRoot, + pallet_sequencer_staking::EnsureActiveSequencer, + >; +} + +use cfg::pallet_proxy::ProxyType; + +// TODO: ideally should be moved to common runtime +impl InstanceFilter for ProxyType { + fn filter(&self, c: &RuntimeCall) -> bool { + match self { + _ if matches!(c, RuntimeCall::Utility(..)) => true, + ProxyType::AutoCompound => false, + } + } + fn is_superset(&self, o: &Self) -> bool { + match (self, o) { + (x, y) if x == y => true, + _ => false, + } + } +} + +impl pallet_proxy::Config for Runtime { + type RuntimeEvent = RuntimeEvent; + type RuntimeCall = RuntimeCall; + type Currency = orml_tokens::CurrencyAdapter; + type ProxyType = cfg::pallet_proxy::ProxyType; + type ProxyDepositBase = cfg::pallet_proxy::ProxyDepositBase; + type ProxyDepositFactor = cfg::pallet_proxy::ProxyDepositFactor; + type MaxProxies = frame_support::traits::ConstU32<32>; + type WeightInfo = pallet_proxy::weights::SubstrateWeight; + type MaxPending = frame_support::traits::ConstU32<32>; + type CallHasher = BlakeTwo256; + type AnnouncementDepositBase = cfg::pallet_proxy::AnnouncementDepositBase; + type AnnouncementDepositFactor = cfg::pallet_proxy::AnnouncementDepositFactor; +} + +impl pallet_identity::Config for Runtime { + type RuntimeEvent = RuntimeEvent; + type Currency = orml_tokens::CurrencyAdapter; + type BasicDeposit = cfg::pallet_identity::BasicDeposit; + type ByteDeposit = cfg::pallet_identity::ByteDeposit; + type SubAccountDeposit = cfg::pallet_identity::SubAccountDeposit; + type MaxSubAccounts = cfg::pallet_identity::MaxSubAccounts; + type IdentityInformation = IdentityInfo; + type MaxRegistrars = cfg::pallet_identity::MaxRegistrars; + type ForceOrigin = cfg::pallet_identity::IdentityForceOrigin; + type RegistrarOrigin = cfg::pallet_identity::IdentityRegistrarOrigin; + type Slashed = Treasury; + type WeightInfo = pallet_identity::weights::SubstrateWeight; + type OffchainSignature = Signature; + type SigningPublicKey = ::Signer; + type UsernameAuthorityOrigin = EnsureRoot; + type PendingUsernameExpiration = cfg::pallet_identity::PendingUsernameExpiration; + type MaxSuffixLength = cfg::pallet_identity::MaxSuffixLength; + type MaxUsernameLength = cfg::pallet_identity::MaxUsernameLength; +} + +/// membership pallets is used to maintain Foundation accounts +/// only a `change_key` of existing -> new member account id is allowed +impl pallet_membership::Config for Runtime { + type RuntimeEvent = RuntimeEvent; + type AddOrigin = NeverEnsureOrigin<()>; + type RemoveOrigin = NeverEnsureOrigin<()>; + type SwapOrigin = NeverEnsureOrigin<()>; + type ResetOrigin = NeverEnsureOrigin<()>; + type PrimeOrigin = NeverEnsureOrigin<()>; + type MembershipInitialized = (); + type MembershipChanged = (); + type MaxMembers = cfg::pallet_membership::MaxMembersFoundation; + type WeightInfo = pallet_membership::weights::SubstrateWeight; +} + +impl pallet_maintenance::Config for Runtime { + type RuntimeEvent = RuntimeEvent; + type FoundationAccountsProvider = cfg::pallet_membership::FoundationAccountsProvider; +} + +impl pallet_rolldown::Config for Runtime { + type RuntimeEvent = RuntimeEvent; + type AddressConverter = pallet_rolldown::EthereumAddressConverter; + type SequencerStakingProvider = SequencerStaking; + type Tokens = orml_tokens::MultiTokenCurrencyAdapter; + type AssetRegistryProvider = cfg::orml_asset_registry::AssetRegistryProvider; + type RequestsPerBlock = cfg::pallet_rolldown::RequestsPerBlock; + // We havent spent any time considering rights multiplier being > 1. There might be some corner + // cases that should be investigated. + type RightsMultiplier = cfg::pallet_rolldown::RightsMultiplier; + type MaintenanceStatusProvider = Maintenance; + type ChainId = pallet_rolldown::messages::Chain; + type AssetAddressConverter = pallet_rolldown::MultiEvmChainAddressConverter; + type MerkleRootAutomaticBatchPeriod = cfg::pallet_rolldown::MerkleRootAutomaticBatchPeriod; + type MerkleRootAutomaticBatchSize = cfg::pallet_rolldown::MerkleRootAutomaticBatchSize; + type TreasuryPalletId = cfg::TreasuryPalletIdOf; + type NativeCurrencyId = tokens::RxTokenId; + type SequencerStakingRewards = SequencerStaking; + type WithdrawFee = cfg::pallet_rolldown::WithdrawFee; + type WeightInfo = weights::pallet_rolldown::ModuleWeight; + type NontransferableTokens = tokens::NontransferableTokens; +} + +impl pallet_rolldown::RolldownBenchmarkingConfig for Runtime {} + +impl pallet_sequencer_staking::Config for Runtime { + type RuntimeEvent = RuntimeEvent; + type Currency = orml_tokens::CurrencyAdapter; + type MinimumSequencers = frame_support::traits::ConstU32<2>; + type RolldownProvider = Rolldown; + type NoOfPastSessionsForEligibility = frame_support::traits::ConstU32<10>; + type MaxSequencers = frame_support::traits::ConstU32<3>; + type BlocksForSequencerUpdate = frame_support::traits::ConstU32<10>; + type CancellerRewardPercentage = cfg::pallet_sequencer_staking::CancellerRewardPercentage; + type ChainId = pallet_rolldown::messages::Chain; + type DefaultPayoutLimit = cfg::parachain_staking::DefaultPayoutLimit; + type SequencerIssuanceVault = cfg::pallet_issuance::SequencerIssuanceVault; + type RewardPaymentDelay = cfg::parachain_staking::RewardPaymentDelay; + type Issuance = Issuance; +} + +impl pallet_metamask_signature::Config for Runtime { + type RuntimeEvent = RuntimeEvent; + type StringLimit = frame_support::traits::ConstU32<32>; + type UrlStringLimit = frame_support::traits::ConstU32<1024>; +} + +impl pallet_market::Config for Runtime { + type RuntimeEvent = RuntimeEvent; + type Currency = orml_tokens::MultiTokenCurrencyAdapter; + type Balance = Balance; + type CurrencyId = TokenId; + type NativeCurrencyId = tokens::RxTokenId; + type Xyk = Xyk; + type StableSwap = StableSwap; + type Rewards = ProofOfStake; + type Vesting = Vesting; + type AssetRegistry = cfg::orml_asset_registry::AssetRegistryProvider; + type DisabledTokens = + (cfg::pallet_xyk::TestTokensFilter, cfg::pallet_xyk::AssetRegisterFilter); + type DisallowedPools = Bootstrap; + type MaintenanceStatusProvider = Maintenance; + type WeightInfo = weights::pallet_market_weights::ModuleWeight; + #[cfg(feature = "runtime-benchmarks")] + type ComputeIssuance = Issuance; + type NontransferableTokens = tokens::NontransferableTokens; + type FoundationAccountsProvider = cfg::pallet_membership::FoundationAccountsProvider; + type ArbitrageBot = tokens::ArbitrageBot; +} + +impl pallet_membership::Config for Runtime { + type RuntimeEvent = RuntimeEvent; + type AddOrigin = EnsureRoot; + type RemoveOrigin = EnsureRoot; + type SwapOrigin = EnsureRoot; + type ResetOrigin = EnsureRoot; + type PrimeOrigin = NeverEnsureOrigin<()>; + type MembershipInitialized = (); + type MembershipChanged = (); + type MaxMembers = cfg::pallet_membership::MaxMembersTransfer; + type WeightInfo = pallet_membership::weights::SubstrateWeight; +} + +// Create the runtime by composing the FRAME pallets that were previously configured. +construct_runtime!( + pub struct Runtime { + // System support stuff. + System: frame_system = 0, + Timestamp: pallet_timestamp = 2, + Utility: pallet_utility_mangata = 4, + Proxy: pallet_proxy = 5, + Maintenance: pallet_maintenance = 6, + Rolldown: pallet_rolldown = 7, + Metamask: pallet_metamask_signature = 8, + + // Monetary stuff. + Tokens: orml_tokens = 10, + TransactionPayment: pallet_transaction_payment = 11, + + // Xyk stuff + StableSwap: pallet_stable_swap exclude_parts { Call } = 12, + Xyk: pallet_xyk exclude_parts { Call } = 13, + ProofOfStake: pallet_proof_of_stake = 14, + + // Fee Locks + FeeLock: pallet_fee_lock = 15, + + // Vesting + Vesting: pallet_vesting_mangata = 17, + + // Crowdloan + Crowdloan: pallet_crowdloan_rewards = 18, + + // Issuance + Issuance: pallet_issuance = 19, + + // MultiPurposeLiquidity + MultiPurposeLiquidity: pallet_multipurpose_liquidity = 20, + + // Bootstrap + Bootstrap: pallet_bootstrap = 21, + + // AMM + Market: pallet_market = 22, + + // Collator support. The order of these 6 are important and shall not change. + Authorship: pallet_authorship = 29, + ParachainStaking: parachain_staking = 30, + SequencerStaking: pallet_sequencer_staking = 31, + Session: pallet_session = 32, + Aura: pallet_aura = 33, + Grandpa: pallet_grandpa = 34, + + AssetRegistry: orml_asset_registry = 53, + + // Governance stuff + Treasury: pallet_treasury = 60, + Sudo: pallet_sudo_mangata = 61, + SudoOrigin: pallet_sudo_origin = 62, + Council: pallet_collective_mangata:: = 63, + Identity: pallet_identity = 64, + FoundationMembers: pallet_membership:: = 65, + TransferMembers: pallet_membership:: = 66, + } +); + +#[cfg(feature = "runtime-benchmarks")] +#[macro_use] +extern crate frame_benchmarking; + +#[cfg(feature = "runtime-benchmarks")] +mod benches { + define_benchmarks!( + [frame_benchmarking, BaselineBench::] + [frame_system, SystemBench::] + [pallet_session, Session] + [pallet_timestamp, Timestamp] + [orml_asset_registry, AssetRegistry] + [orml_tokens, Tokens] + [parachain_staking, ParachainStaking] + [pallet_xyk, Xyk] + [pallet_treasury, Treasury] + [pallet_collective_mangata, Council] + [pallet_bootstrap, Bootstrap] + [pallet_crowdloan_rewards, Crowdloan] + [pallet_utility_mangata, Utility] + [pallet_vesting_mangata, Vesting] + [pallet_issuance, Issuance] + [pallet_multipurpose_liquidity, MultiPurposeLiquidity] + [pallet_fee_lock, FeeLock] + [pallet_proof_of_stake, ProofOfStake] + [pallet_rolldown, Rolldown] + [pallet_market, Market] + ); +} + +use frame_support::dispatch::GetDispatchInfo; + +impl_runtime_apis! { + + impl metamask_signature_runtime_api::MetamaskSignatureRuntimeApi for Runtime { + fn get_eip712_sign_data(encoded_call: Vec) -> String{ + if let Ok(extrinsic) = UncheckedExtrinsic::decode(& mut encoded_call.as_ref()) { + if let Some(MetamaskSigningCtx{call, ..}) = extrinsic.function.context() { + pallet_metamask_signature::Pallet::::eip712_payload(call) + }else{ + Default::default() + } + }else{ + Default::default() + } + } + } + + impl rolldown_runtime_api::RolldownRuntimeApi for Runtime { + fn get_native_sequencer_update(hex_payload: Vec) -> Option { + pallet_rolldown::Pallet::::convert_eth_l1update_to_substrate_l1update(hex_payload).ok() + } + + fn verify_sequencer_update(chain: pallet_rolldown::messages::Chain, hash: sp_core::H256, request_id: u128) -> Option { + pallet_rolldown::Pallet::::verify_sequencer_update(chain, hash, request_id) + } + + fn get_last_processed_request_on_l2(chain: pallet_rolldown::messages::Chain) -> Option{ + Some(Rolldown::get_last_processed_request_on_l2(chain)) + } + + fn get_number_of_pending_requests(chain: pallet_rolldown::messages::Chain) -> Option{ + Some(Rolldown::get_max_accepted_request_id_on_l2(chain).saturating_sub(Rolldown::get_last_processed_request_on_l2(chain))) + } + + fn get_total_number_of_deposits() -> u128 { + Rolldown::get_total_number_of_deposits() + } + + fn get_total_number_of_withdrawals() -> u128 { + Rolldown::get_total_number_of_withdrawals() + } + + fn get_merkle_root(chain: pallet_rolldown::messages::Chain, range : (u128, u128)) -> sp_core::H256{ + Rolldown::get_merkle_root(chain, range) + } + + fn get_merkle_proof_for_tx(chain: pallet_rolldown::messages::Chain, range : (u128, u128), tx_id: u128) -> Vec{ + Rolldown::get_merkle_proof_for_tx(chain, range, tx_id) + } + + fn verify_merkle_proof_for_tx( + chain: pallet_rolldown::messages::Chain, + range: (u128, u128), + tx_id: u128, + root: sp_core::H256, + proof: Vec, + ) -> bool { + Rolldown::verify_merkle_proof_for_tx( + chain, + range, + root, + tx_id, + proof, + ) + } + + fn get_abi_encoded_l2_request( + chain: pallet_rolldown::messages::Chain, + request_id: u128 + ) -> Vec{ + Rolldown::get_abi_encoded_l2_request(chain, request_id) + } + } + + impl proof_of_stake_runtime_api::ProofOfStakeApi for Runtime{ + fn calculate_native_rewards_amount( + user: AccountId, + liquidity_asset_id: TokenId, + ) -> Balance{ + pallet_proof_of_stake::Pallet::::calculate_native_rewards_amount(user, liquidity_asset_id) + .unwrap_or_default() + } + + fn calculate_3rdparty_rewards_amount( + user: AccountId, + liquidity_asset_id: TokenId, + reward_asset_id: TokenId, + ) -> Balance{ + pallet_proof_of_stake::Pallet::::calculate_3rdparty_rewards_amount(user, liquidity_asset_id, reward_asset_id) + .unwrap_or_default() + } + + fn calculate_3rdparty_rewards_all( + user: AccountId, + ) -> Vec<(TokenId, TokenId, Balance)>{ + pallet_proof_of_stake::Pallet::::calculate_3rdparty_rewards_all(user) + } + } + + impl ver_api::VerApi for Runtime { + fn get_signer( + tx: ::Extrinsic, + ) -> Option<(sp_runtime::AccountId20, u32)> { + if let Some(sig) = tx.signature.clone(){ + let nonce: frame_system::CheckNonce<_> = sig.2.4; + ::Lookup::lookup(sig.0) + .ok() + .and_then(|addr| Some((addr, nonce.0))) + }else{ + None + } + } + + fn is_storage_migration_scheduled() -> bool{ + Executive::runtime_upgraded() + } + + fn store_seed(seed: sp_core::H256){ + // initialize has been called already so we can fetch number from the storage + System::set_block_seed(&seed); + } + + fn get_previous_block_txs() -> Vec> { + System::get_previous_blocks_txs() + } + + fn pop_txs(count: u64) -> Vec> { + System::pop_txs(count as usize) + } + + fn create_enqueue_txs_inherent(txs: Vec<::Extrinsic>) -> ::Extrinsic { + for t in txs.iter() { + if let Some((_, _, extra)) = &t.signature { + let _ = extra.additional_signed(); + } + } + UncheckedExtrinsic::new_unsigned( + RuntimeCall::System(frame_system::Call::enqueue_txs{txs: + txs.into_iter() + .map(|tx| ( + tx.signature.clone().and_then(|sig| + ::Lookup::lookup(sig.0).ok() + ), + tx.encode()) + ).collect()})) + } + + fn can_enqueue_txs() -> bool { + System::can_enqueue_txs() + } + + fn start_prevalidation() { + System::set_prevalidation() + } + + fn account_extrinsic_dispatch_weight(consumed: ver_api::ConsumedWeight, tx: ::Extrinsic) -> Result { + let info = tx.get_dispatch_info(); + let maximum_weight = ::BlockWeights::get(); + frame_system::calculate_consumed_weight::(maximum_weight, consumed, &info) + .or(Err(())) + } + + } + + impl ver_api::VerNonceApi for Runtime { + fn enqueued_txs_count(acc: AccountId) -> u64 { + + System::enqueued_txs_count(&acc) as u64 + } + } + + impl xyk_runtime_api::XykRuntimeApi for Runtime { + fn calculate_sell_price( + input_reserve: Balance, + output_reserve: Balance, + sell_amount: Balance + ) -> Balance { + Xyk::calculate_sell_price(input_reserve, output_reserve, sell_amount) + .map_err(|e| + { + log::warn!(target:"xyk", "rpc 'XYK::calculate_sell_price' error: '{:?}', returning default value instead", e); + e + } + ).unwrap_or_default() + } + + fn calculate_buy_price( + input_reserve: Balance, + output_reserve: Balance, + buy_amount: Balance + ) -> Balance { + Xyk::calculate_buy_price(input_reserve, output_reserve, buy_amount) + .map_err(|e| + { + log::warn!(target:"xyk", "rpc 'XYK::calculate_buy_price' error: '{:?}', returning default value instead", e); + e + } + ).unwrap_or_default() + + } + + fn calculate_sell_price_id( + sold_token_id: TokenId, + bought_token_id: TokenId, + sell_amount: Balance + ) -> Balance { + Xyk::calculate_sell_price_id(sold_token_id, bought_token_id, sell_amount) + .map_err(|e| + { + log::warn!(target:"xyk", "rpc 'XYK::calculate_sell_price_id' error: '{:?}', returning default value instead", e); + e + } + ).unwrap_or_default() + } + + fn calculate_buy_price_id( + sold_token_id: TokenId, + bought_token_id: TokenId, + buy_amount: Balance + ) -> Balance { + Xyk::calculate_buy_price_id(sold_token_id, bought_token_id, buy_amount) + .map_err(|e| + { + log::warn!(target:"xyk", "rpc 'XYK::calculate_buy_price_id' error: '{:?}', returning default value instead", e); + e + } + ).unwrap_or_default() + } + + fn get_burn_amount( + first_asset_id: TokenId, + second_asset_id: TokenId, + liquidity_asset_amount: Balance + ) -> (Balance, Balance) { + Xyk::get_burn_amount(first_asset_id, second_asset_id, liquidity_asset_amount) + .map_err(|e| + { + log::warn!(target:"xyk", "rpc 'XYK::calculate_buy_price_id' error: '{:?}', returning default value instead", e); + e + } + ).unwrap_or_default() + } + + fn get_max_instant_burn_amount( + user: AccountId, + liquidity_asset_id: TokenId, + ) -> Balance { + Xyk::get_max_instant_burn_amount(&user, liquidity_asset_id) + } + + fn get_max_instant_unreserve_amount( + user: AccountId, + liquidity_asset_id: TokenId, + ) -> Balance { + Xyk::get_max_instant_unreserve_amount(&user, liquidity_asset_id) + } + + fn calculate_rewards_amount( + user: AccountId, + liquidity_asset_id: TokenId, + ) -> Balance { + ProofOfStake::calculate_rewards_amount(user, liquidity_asset_id) + .map_err(|e| + { + log::warn!(target:"xyk", "rpc 'XYK::calculate_buy_price_id' error: '{:?}', returning default value instead", e); + e + } + ).unwrap_or_default() + } + + fn calculate_balanced_sell_amount( + total_amount: Balance, + reserve_amount: Balance, + ) -> Balance { + Xyk::calculate_balanced_sell_amount(total_amount, reserve_amount) + .map_err(|e| + { + log::warn!(target:"xyk", "rpc 'XYK::calculate_balanced_sell_amount' error: '{:?}', returning default value instead", e); + e + } + ).unwrap_or_default() + } + + fn get_liq_tokens_for_trading() -> Vec { + Xyk::get_liq_tokens_for_trading() + .map_err(|e| + { + log::warn!(target:"xyk", "rpc 'XYK::get_liq_tokens_for_trading' error: '{:?}', returning default value instead", e); + e + } + ).unwrap_or_default() + } + + fn is_sell_asset_lock_free( + path: Vec, + input_amount: Balance, + ) -> Option{ + match (path.len(), pallet_fee_lock::FeeLockMetadata::::get()) { + (length, _) if length < 2 => { + None + } + (2, Some(feelock)) => { + let input = path.get(0)?; + let output = path.get(1)?; + let output_amount = Xyk::calculate_sell_price_id(*input, *output, input_amount).ok()?; + Some( + FeeHelpers::< + Runtime, + orml_tokens::MultiTokenCurrencyAdapter, + FeeLock, + >::is_high_value_swap(&feelock, *input, input_amount) + || + FeeHelpers::< + Runtime, + orml_tokens::MultiTokenCurrencyAdapter, + FeeLock, + >::is_high_value_swap(&feelock, *output, output_amount) + ) + } + (_, None) => { + Some(false) + } + (_, Some(_)) => { + Some(true) + } + } + } + + fn is_buy_asset_lock_free( + path: Vec, + input_amount: Balance, + ) -> Option{ + match (path.len(), pallet_fee_lock::FeeLockMetadata::::get()) { + (length, _) if length < 2 => { + None + } + (2, Some(feelock)) => { + let input = path.get(0)?; + let output = path.get(1)?; + let output_amount = Xyk::calculate_buy_price_id(*input, *output, input_amount).ok()?; + Some( + FeeHelpers::< + Runtime, + orml_tokens::MultiTokenCurrencyAdapter, + FeeLock, + >::is_high_value_swap(&feelock, *input, input_amount) + || + FeeHelpers::< + Runtime, + orml_tokens::MultiTokenCurrencyAdapter, + FeeLock, + >::is_high_value_swap(&feelock, *output, output_amount) + ) + } + (_, None) => { + Some(false) + } + (_, Some(_)) => { + Some(true) + } + } + } + + fn get_tradeable_tokens() -> Vec> { + orml_asset_registry::Metadata::::iter() + .filter_map(|(token_id, metadata)| { + if !metadata.name.is_empty() + && !metadata.symbol.is_empty() + && metadata.additional.xyk.as_ref().map_or(true, |xyk| !xyk.operations_disabled) + { + let rpc_metadata = RpcAssetMetadata { + token_id: token_id, + decimals: metadata.decimals, + name: metadata.name.to_vec(), + symbol: metadata.symbol.to_vec(), + }; + Some(rpc_metadata) + } else { + None + } + }) + .collect::>() + } + + fn get_total_number_of_swaps() -> u128 { + Xyk::get_total_number_of_swaps() + } + } + + impl pallet_market::MarketRuntimeApi for Runtime { + fn calculate_sell_price(pool_id: TokenId, sell_asset_id: TokenId, sell_amount: Balance) -> Option { + Market::calculate_sell_price(pool_id, sell_asset_id, sell_amount) + } + + fn calculate_sell_price_with_impact(pool_id: TokenId, sell_asset_id: TokenId, sell_amount: Balance) -> Option<(Balance, Balance)> { + Market::calculate_sell_price_with_impact(pool_id, sell_asset_id, sell_amount) + } + + fn calculate_buy_price(pool_id: TokenId, buy_asset_id: TokenId, buy_amount: Balance) -> Option { + Market::calculate_buy_price(pool_id, buy_asset_id, buy_amount) + } + + fn calculate_buy_price_with_impact(pool_id: TokenId, buy_asset_id: TokenId, buy_amount: Balance) -> Option<(Balance, Balance)> { + Market::calculate_buy_price_with_impact(pool_id, buy_asset_id, buy_amount) + } + + fn get_burn_amount(pool_id: TokenId, lp_burn_amount: Balance) -> Option<(Balance, Balance)> { + Market::get_burn_amount(pool_id, lp_burn_amount) + } + + fn get_tradeable_tokens() -> Vec> { + orml_asset_registry::Metadata::::iter() + .filter_map(|(token_id, metadata)| { + if !metadata.name.is_empty() + && !metadata.symbol.is_empty() + && metadata.additional.xyk.as_ref().map_or(true, |xyk| !xyk.operations_disabled) + { + let rpc_metadata = pallet_market::RpcAssetMetadata { + token_id: token_id, + decimals: metadata.decimals, + name: metadata.name.to_vec(), + symbol: metadata.symbol.to_vec(), + }; + Some(rpc_metadata) + } else { + None + } + }) + .collect::>() + } + + fn get_pools_for_trading() -> Vec { + Market::get_pools_for_trading() + } + + fn calculate_expected_amount_for_minting( + pool_id: TokenId, + asset_id: TokenId, + amount: Balance, + ) -> Option { + Market::calculate_expected_amount_for_minting(pool_id, asset_id, amount) + } + + fn calculate_expected_lp_minted( + pool_id: TokenId, + amounts: (Balance, Balance), + ) -> Option { + Market::calculate_expected_lp_minted(pool_id, amounts) + } + + fn get_pools(pool_id: Option) -> Vec> { + Market::get_pools(pool_id).into_iter() + .map(|(info, reserve)| pallet_market::RpcPoolInfo { + pool_id: info.pool_id, + kind: info.kind, + lp_token_id: info.pool_id, + assets: vec![info.pool.0, info.pool.1], + reserves: vec![reserve.0, reserve.1], + }) + .collect() + } + } + + impl sp_api::Core for Runtime { + fn version() -> RuntimeVersion { + VERSION + } + + fn execute_block(block: Block) { + let header = block.header(); + let author = + // since execute_block_ver_impl is called at the end here + // and execute_block_ver_impl initializes the block + // That means that at this point the block is not initialized + // That means session was not on_initialize + // That means we can use aura validator set + // as they have not been overwritten yet + + pallet_aura::FindAccountFromAuthorIndex::::find_author( + header.digest().logs().iter().filter_map(|d| d.as_pre_runtime()) + ).expect("Could not find AuRa author index!") + .to_raw_vec(); + Executive::execute_block_ver_impl(block, author); + } + + fn initialize_block(header: &::Header) -> ExtrinsicInclusionMode { + Executive::initialize_block(header) + } + } + + impl sp_api::Metadata for Runtime { + fn metadata() -> OpaqueMetadata { + OpaqueMetadata::new(Runtime::metadata().into()) + } + + fn metadata_at_version(version: u32) -> Option { + Runtime::metadata_at_version(version) + } + + fn metadata_versions() -> sp_std::vec::Vec { + Runtime::metadata_versions() + } + } + + impl sp_block_builder::BlockBuilder for Runtime { + fn apply_extrinsic(extrinsic: ::Extrinsic) -> ApplyExtrinsicResult { + Executive::apply_extrinsic(extrinsic) + } + + fn finalize_block() -> ::Header { + Executive::finalize_block() + } + + fn inherent_extrinsics(data: sp_inherents::InherentData) -> Vec<::Extrinsic> { + data.create_extrinsics() + } + + fn check_inherents( + block: Block, + data: sp_inherents::InherentData, + ) -> sp_inherents::CheckInherentsResult { + data.check_extrinsics(&block) + } + } + + impl sp_transaction_pool::runtime_api::TaggedTransactionQueue for Runtime { + fn validate_transaction( + source: TransactionSource, + tx: ::Extrinsic, + block_hash: ::Hash, + ) -> TransactionValidity { + Executive::validate_transaction(source, tx, block_hash) + } + } + + impl sp_offchain::OffchainWorkerApi for Runtime { + fn offchain_worker(header: &::Header) { + Executive::offchain_worker(header) + } + } + + impl sp_consensus_aura::AuraApi for Runtime { + fn slot_duration() -> sp_consensus_aura::SlotDuration { + sp_consensus_aura::SlotDuration::from_millis(Aura::slot_duration()) + } + + fn authorities() -> Vec { + Aura::authorities().into_inner() + } + } + + impl sp_session::SessionKeys for Runtime { + fn generate_session_keys(seed: Option>) -> Vec { + SessionKeys::generate(seed) + } + + fn decode_session_keys( + encoded: Vec, + ) -> Option, KeyTypeId)>> { + SessionKeys::decode_into_raw_public_keys(&encoded) + } + } + + impl sp_consensus_grandpa::GrandpaApi for Runtime { + fn grandpa_authorities() -> sp_consensus_grandpa::AuthorityList { + Grandpa::grandpa_authorities() + } + + fn current_set_id() -> sp_consensus_grandpa::SetId { + Grandpa::current_set_id() + } + + fn submit_report_equivocation_unsigned_extrinsic( + _equivocation_proof: sp_consensus_grandpa::EquivocationProof< + ::Hash, + NumberFor, + >, + _key_owner_proof: sp_consensus_grandpa::OpaqueKeyOwnershipProof, + ) -> Option<()> { + None + } + + fn generate_key_ownership_proof( + _set_id: sp_consensus_grandpa::SetId, + _authority_id: GrandpaId, + ) -> Option { + // NOTE: this is the only implementation possible since we've + // defined our key owner proof type as a bottom type (i.e. a type + // with no values). + None + } + } + + impl frame_system_rpc_runtime_api::AccountNonceApi for Runtime { + fn account_nonce(account: AccountId) -> Nonce { + System::account_nonce(account) + } + } + + impl pallet_transaction_payment_rpc_runtime_api::TransactionPaymentApi for Runtime { + fn query_info( + uxt: ::Extrinsic, + len: u32, + ) -> pallet_transaction_payment_rpc_runtime_api::RuntimeDispatchInfo { + TransactionPayment::query_info(uxt, len) + } + fn query_fee_details( + uxt: ::Extrinsic, + len: u32, + ) -> pallet_transaction_payment::FeeDetails { + TransactionPayment::query_fee_details(uxt, len) + } + fn query_weight_to_fee(weight: Weight) -> Balance { + TransactionPayment::weight_to_fee(weight) + } + fn query_length_to_fee(length: u32) -> Balance { + TransactionPayment::length_to_fee(length) + } + } + + impl pallet_transaction_payment_rpc_runtime_api::TransactionPaymentCallApi + for Runtime + { + fn query_call_info( + call: RuntimeCall, + len: u32, + ) -> pallet_transaction_payment::RuntimeDispatchInfo { + TransactionPayment::query_call_info(call, len) + } + fn query_call_fee_details( + call: RuntimeCall, + len: u32, + ) -> pallet_transaction_payment::FeeDetails { + TransactionPayment::query_call_fee_details(call, len) + } + fn query_weight_to_fee(weight: Weight) -> Balance { + TransactionPayment::weight_to_fee(weight) + } + fn query_length_to_fee(length: u32) -> Balance { + TransactionPayment::length_to_fee(length) + } + } + + #[cfg(feature = "try-runtime")] + impl frame_try_runtime::TryRuntime for Runtime { + fn on_runtime_upgrade(checks: frame_try_runtime::UpgradeCheckSelect) -> (Weight, Weight) { + // TODO: Maybe checks should be overridden with `frame_try_runtime::UpgradeCheckSelect::All` + // NOTE: intentional unwrap: we don't want to propagate the error backwards, and want to + // have a backtrace here. If any of the pre/post migration checks fail, we shall stop + // right here and right now. + let weight = Executive::try_runtime_upgrade(checks).unwrap(); + (weight, cfg::frame_system::RuntimeBlockWeights::get().max_block) + } + + fn execute_block( + block: Block, + state_root_check: bool, + signature_check: bool, + select: frame_try_runtime::TryStateSelect + ) -> Weight { + log::info!( + target: "node-runtime", + "try-runtime: executing block {:?} / root checks: {:?} / try-state-select: {:?}", + block.header.hash(), + state_root_check, + select, + ); + // NOTE: intentional unwrap: we don't want to propagate the error backwards, and want to + // have a backtrace here. + Executive::try_execute_block(block, state_root_check, signature_check, select).expect("execute-block failed") + } + } + + #[cfg(feature = "runtime-benchmarks")] + impl frame_benchmarking::Benchmark for Runtime { + fn benchmark_metadata(extra: bool) -> ( + Vec, + Vec, + ) { + use frame_benchmarking::{baseline, Benchmarking, BenchmarkList}; + use frame_support::traits::StorageInfoTrait; + use frame_system_benchmarking::Pallet as SystemBench; + use baseline::Pallet as BaselineBench; + + let mut list = Vec::::new(); + list_benchmarks!(list, extra); + + let storage_info = AllPalletsWithSystem::storage_info(); + + (list, storage_info) + } + + fn dispatch_benchmark( + config: frame_benchmarking::BenchmarkConfig + ) -> Result, sp_runtime::RuntimeString> { + use frame_benchmarking::{baseline, Benchmarking, BenchmarkBatch}; + use sp_storage::TrackedStorageKey; + use frame_system_benchmarking::Pallet as SystemBench; + use baseline::Pallet as BaselineBench; + + impl frame_system_benchmarking::Config for Runtime {} + impl baseline::Config for Runtime {} + + use frame_support::traits::WhitelistedStorageKeys; + let whitelist: Vec = AllPalletsWithSystem::whitelisted_storage_keys(); + + let mut batches = Vec::::new(); + let params = (&config, &whitelist); + add_benchmarks!(params, batches); + + Ok(batches) + } + } +} diff --git a/gasp-node/rollup/runtime/src/runtime_config.rs b/gasp-node/rollup/runtime/src/runtime_config.rs new file mode 100644 index 000000000..e5a590b4f --- /dev/null +++ b/gasp-node/rollup/runtime/src/runtime_config.rs @@ -0,0 +1,1389 @@ +#![cfg_attr(not(feature = "std"), no_std)] + +pub use crate::*; + +pub mod currency { + use super::Balance; + + pub const MILLICENTS: Balance = CENTS / 1000; + pub const CENTS: Balance = DOLLARS / 100; // assume this is worth about a cent. + pub const DOLLARS: Balance = super::consts::UNIT; + + pub const fn deposit(items: u32, bytes: u32) -> Balance { + items as Balance * 5000 * DOLLARS + (bytes as Balance) * 60 * CENTS + } +} + +pub mod types { + use super::*; + + pub type TokenId = u32; + pub type Balance = u128; + pub type Amount = i128; + + // /// Alias to 512-bit hash when used in the context of a transaction signature on the chain. + pub type Signature = EthereumSignature; + + pub type Signer = ::Signer; + + // /// Some way of identifying an account on the chain. We intentionally make it equivalent + // /// to the public key of our transaction signing scheme. + pub type AccountId = <::Signer as IdentifyAccount>::AccountId; + + // /// Index of a transaction in the chain. + pub type Nonce = u32; + + // /// A hash of some data used by the chain. + pub type Hash = sp_core::H256; + + // /// An index to a block. + pub type BlockNumber = u32; + + // /// The address format for describing accounts. + pub type Address = AccountId; +} + +pub mod tokens { + use super::*; + pub const RX_TOKEN_ID: TokenId = 0; + pub const ETH_TOKEN_ID: TokenId = 1; + + #[cfg(any(feature = "unlocked", feature = "runtime-benchmarks"))] + pub type NontransferableTokens = Nothing; + #[cfg(not(any(feature = "unlocked", feature = "runtime-benchmarks")))] + pub type NontransferableTokens = Equals>; + + #[cfg(any(feature = "unlocked", feature = "runtime-benchmarks"))] + pub type ArbitrageBot = Nothing; + #[cfg(not(any(feature = "unlocked", feature = "runtime-benchmarks")))] + pub type ArbitrageBot = Equals; + + parameter_types! { + pub const RxTokenId: TokenId = RX_TOKEN_ID; + pub const EthTokenId: TokenId = ETH_TOKEN_ID; + pub ArbitrageBotAddr: AccountId = sp_runtime::AccountId20::from( + hex_literal::hex!["0286Ffa54213778E064179E9B6F083ecb584E862"] + ); + } +} + +pub mod fees { + use super::*; + // xyk uses 10_000 as fee multiplier + pub type PoolFeePercentage = frame_support::traits::ConstU128<20>; + pub type TreasuryFeePercentage = frame_support::traits::ConstU128<5>; + pub type BuyAndBurnFeePercentage = frame_support::traits::ConstU128<5>; + + // market & stableswap uses 1e10 precision; 1*1e10 == 100% + // 0.3%, sum of above fees + pub type MarketTotalFee = ConstU128<30_000_000>; + // 33% of total fee goes to treasury + pub type MarketTreasuryFeePart = ConstU128<3_333_333_334>; + // 50% of treasury fee gets to burn + pub type MarketBnBFeePart = ConstU128<5_000_000_000>; + + pub const FEE_PRECISION: u128 = pallet_stable_swap::Pallet::::FEE_DENOMINATOR; +} + +/// Opaque types. These are used by the CLI to instantiate machinery that don't need to know +/// the specifics of the runtime. They can then be made to be agnostic over specific formats +/// of data like extrinsics, allowing for them to continue syncing the network through upgrades +/// to even the core data structures. +pub mod opaque { + use super::*; + use sp_runtime::{ + generic, + traits::{BlakeTwo256, Hash as HashT}, + }; + + pub use sp_runtime::OpaqueExtrinsic as UncheckedExtrinsic; + /// Opaque block header type. + pub type Header = generic::HeaderVer; + /// Opaque block type. + pub type Block = generic::Block; + /// Opaque block identifier type. + pub type BlockId = generic::BlockId; + /// Opaque block hash type. + pub type Hash = ::Output; +} + +pub mod runtime_types { + use super::*; + + pub type SignedExtra = ( + frame_system::CheckSpecVersion, + frame_system::CheckTxVersion, + frame_system::CheckGenesis, + frame_system::CheckEra, + frame_system::CheckNonce, + frame_system::CheckWeight, + pallet_transaction_payment::ChargeTransactionPayment, + frame_system::CheckNonZeroSender, + ); + + pub type SignedPayload = + generic::SignedPayload>; + pub type UncheckedExtrinsic = + generic::UncheckedExtrinsic>; + pub type CheckedExtrinsic = + generic::CheckedExtrinsic>; + pub type Header = generic::HeaderVer; + pub type Block = + generic::Block>; + pub type SignedBlock = generic::SignedBlock>; + pub type BlockId = generic::BlockId>; + + pub type OpaqueBlock = generic::Block; + pub type OpaqueBlockId = generic::BlockId; +} + +pub mod consts { + use super::*; + /// This determines the average expected block time that we are targeting. + /// Blocks will be produced at a minimum duration defined by `SLOT_DURATION`. + /// `SLOT_DURATION` is picked up by `pallet_timestamp` which is in turn picked + /// up by `pallet_aura` to implement `fn slot_duration()`. + /// + /// Change this to adjust the block time. + pub const MILLISECS_PER_BLOCK: u64 = 6000; + pub const SLOT_DURATION: u64 = MILLISECS_PER_BLOCK; + + // Time is measured by number of blocks. + pub const MINUTES: BlockNumber = 60_000 / (MILLISECS_PER_BLOCK as BlockNumber); + pub const HOURS: BlockNumber = MINUTES * 60; + pub const DAYS: BlockNumber = HOURS * 24; + + // Unit = the base number of indivisible units for balance + pub const UNIT: Balance = 1_000_000_000_000_000_000; + pub const MILLIUNIT: Balance = 1_000_000_000_000_000; + pub const MICROUNIT: Balance = 1_000_000_000_000; + + /// We allow for 2 seconds of compute with a 6 second average block time, with maximum proof size. + /// NOTE: reduced by half comparing to origin impl as we want to fill block only up to 50% + /// so there is room for new extrinsics in the next block + pub const MAXIMUM_BLOCK_WEIGHT: Weight = + Weight::from_parts(WEIGHT_REF_TIME_PER_SECOND, u64::MAX); +} + +pub enum CallType { + UnlockFee, + UtilityInnerCall, + Other, + Swap { + swap_pool_list: Vec, + asset_id_in: TokenId, + asset_amount_in: Balance, + asset_id_out: TokenId, + asset_amount_out: Balance, + }, +} + +pub mod config { + use super::*; + + pub type TreasuryPalletIdOf = ::PalletId; + + pub struct TreasuryAccountIdOf(PhantomData); + impl Get for TreasuryAccountIdOf { + fn get() -> T::AccountId { + TreasuryPalletIdOf::::get().into_account_truncating() + } + } + pub struct BnbAccountIdOf(PhantomData); + impl Get for BnbAccountIdOf { + fn get() -> T::AccountId { + TreasuryPalletIdOf::::get() + .into_sub_account_truncating(pallet_xyk::BnbTreasurySubAccDerive::get()) + } + } + + pub type ExistentialDepositsOf = ::ExistentialDeposits; + pub type MaxLocksOf = ::MaxLocks; + pub type SessionLenghtOf = ::BlocksPerRound; + + pub mod frame_system { + use super::*; + + /// We assume that ~5% of the block weight is consumed by `on_initialize` handlers. This is + /// used to limit the maximal weight of a single extrinsic. + pub const AVERAGE_ON_INITIALIZE_RATIO: Perbill = Perbill::from_percent(10); + + /// We allow `Normal` extrinsics to fill up the block up to 75%, the rest can be used by + /// `Operational` extrinsics. + pub const NORMAL_DISPATCH_RATIO: Perbill = Perbill::from_percent(75); + + pub type MaxConsumers = frame_support::traits::ConstU32<16>; + + parameter_types! { + pub const BaseWeightOffset: Weight = Weight::from_parts(::DbWeight::get().read, 0); + pub const VerExtrinsicBaseWeight: Weight = weights::VerExtrinsicBaseWeight::get().saturating_add(BaseWeightOffset::get()); + pub const BlockHashCount: BlockNumber = 2400; + pub const Version: sp_version::RuntimeVersion = crate::VERSION; + // This part is copied from Substrate's `bin/node/runtime/src/lib.rs`. + // The `RuntimeBlockLength` and `RuntimeBlockWeights` exist here because the + // `DeletionWeightLimit` and `DeletionQueueDepth` depend on those to parameterize + // the lazy contract deletion. + pub RuntimeBlockLength: BlockLength = + BlockLength::max_with_normal_ratio(5 * 1024 * 1024, NORMAL_DISPATCH_RATIO); + pub RuntimeBlockWeights: BlockWeights = BlockWeights::builder() + .base_block(weights::VerBlockExecutionWeight::get()) + .for_class(DispatchClass::all(), |weights| { + weights.base_extrinsic = VerExtrinsicBaseWeight::get(); + }) + .for_class(DispatchClass::Normal, |weights| { + weights.max_total = Some(NORMAL_DISPATCH_RATIO * consts::MAXIMUM_BLOCK_WEIGHT); + }) + .for_class(DispatchClass::Operational, |weights| { + weights.max_total = Some(consts::MAXIMUM_BLOCK_WEIGHT); + // Operational transactions have some extra reserved space, so that they + // are included even if block reached `MAXIMUM_BLOCK_WEIGHT`. + weights.reserved = Some( + consts::MAXIMUM_BLOCK_WEIGHT - NORMAL_DISPATCH_RATIO * consts::MAXIMUM_BLOCK_WEIGHT + ); + }) + .avg_block_initialization(AVERAGE_ON_INITIALIZE_RATIO) + .build_or_panic(); + pub const SS58Prefix: u16 = 42; + } + + pub struct MaintenanceGatedSetCode(PhantomData, PhantomData); + + impl SetCode for MaintenanceGatedSetCode + where + T: ::pallet_maintenance::Config, + E: SetCode, + { + fn set_code(code: Vec) -> DispatchResult { + if !::pallet_maintenance::Pallet::::is_upgradable() { + return Err(::pallet_maintenance::Error::::UpgradeBlockedByMaintenance.into()) + } + E::set_code(code) + } + } + } + + pub mod pallet_timestamp { + use super::*; + + // NOTE: Currently it is not possible to change the slot duration after the chain has started. + // Attempting to do so will brick block production. + parameter_types! { + pub const MinimumPeriod: u64 = consts::MILLISECS_PER_BLOCK / 2; + } + } + + pub mod pallet_treasury { + use super::*; + + parameter_types! { + pub const TreasuryPalletId: PalletId = PalletId(*b"py/trsry"); + } + + parameter_types! { + pub const ProposalBond: Permill = Permill::from_percent(5); + pub const ProposalBondMinimum: Balance = 1 * currency::DOLLARS; + pub const ProposalBondMaximum: Option = None; + pub const SpendPeriod: BlockNumber = 1 * consts::DAYS; + pub const Burn: Permill = Permill::from_percent(0); + pub const MaxApprovals: u32 = 100; + pub const SpendPayoutPeriod: BlockNumber = 30 * consts::DAYS; + } + } + + pub mod orml_tokens { + use super::*; + + parameter_types! { + pub const MaxLocks: u32 = 50; + } + + parameter_type_with_key! { + pub ExistentialDeposits: |_currency_id: TokenId| -> Balance { + 0 + }; + } + + pub struct DustRemovalWhitelist>(PhantomData); + impl> Contains for DustRemovalWhitelist { + fn contains(a: &AccountId) -> bool { + *a == T::get() + } + } + + pub type ReserveIdentifier = [u8; 8]; + } + + pub mod pallet_xyk { + use super::*; + + parameter_types! { + pub const BnbTreasurySubAccDerive: [u8; 4] = *b"bnbt"; + } + + pub struct TestTokensFilter; + impl Contains for TestTokensFilter { + fn contains(_: &TokenId) -> bool { + // we dont want to allow doing anything with dummy assets previously + // used for testing + false + } + } + + pub struct AssetRegisterFilter(PhantomData); + impl Contains for AssetRegisterFilter + where + T: ::orml_asset_registry::Config< + CustomMetadata = CustomMetadata, + AssetId = TokenId, + Balance = Balance, + >, + { + fn contains(t: &TokenId) -> bool { + let meta: Option<_> = ::orml_asset_registry::Metadata::::get(*t); + if let Some(xyk) = meta.and_then(|m| m.additional.xyk) { + return xyk.operations_disabled + } + return false + } + } + + pub struct AssetMetadataMutation(PhantomData); + + impl AssetMetadataMutationTrait for AssetMetadataMutation + where + T: ::orml_asset_registry::Config< + CustomMetadata = CustomMetadata, + AssetId = TokenId, + Balance = Balance, + StringLimit = orml_asset_registry::StringLimit, + >, + { + fn set_asset_info( + asset: TokenId, + name: Vec, + symbol: Vec, + decimals: u32, + ) -> DispatchResult { + let metadata = AssetMetadata { + name: BoundedVec::truncate_from(name), + symbol: BoundedVec::truncate_from(symbol), + decimals, + existential_deposit: Default::default(), + additional: Default::default(), + }; + ::orml_asset_registry::Pallet::::do_register_asset_without_asset_processor( + metadata, asset, + )?; + Ok(()) + } + } + } + + pub mod pallet_bootstrap { + use super::*; + + parameter_types! { + pub const BootstrapUpdateBuffer: BlockNumber = 300; + pub const DefaultBootstrapPromotedPoolWeight: u8 = 0u8; + pub const ClearStorageLimit: u32 = 100u32; + } + + pub struct EnableAssetPoolApi(PhantomData); + impl AssetRegistryApi for EnableAssetPoolApi + where + T: ::orml_asset_registry::Config< + CustomMetadata = CustomMetadata, + AssetId = TokenId, + Balance = Balance, + >, + { + fn enable_pool_creation(assets: (TokenId, TokenId)) -> bool { + for &asset in [assets.0, assets.1].iter() { + let meta_maybe: Option<_> = ::orml_asset_registry::Metadata::::get(asset); + if let Some(xyk) = meta_maybe.clone().and_then(|m| m.additional.xyk) { + let mut additional = meta_maybe.unwrap().additional; + if xyk.operations_disabled { + additional.xyk = Some(XykMetadata { operations_disabled: false }); + match ::orml_asset_registry::Pallet::::do_update_asset( + asset, + None, + None, + None, + None, + Some(additional), + ) { + Ok(_) => {}, + Err(e) => { + log::error!(target: "bootstrap", "cannot modify {} asset: {:?}!", asset, e); + return false + }, + } + } + } + } + true + } + } + } + + pub mod pallet_transaction_payment { + use crate::*; + + parameter_types! { + pub const OperationalFeeMultiplier: u8 = 5; + pub const TransactionByteFee: Balance = 5 * consts::MILLIUNIT; + pub ConstFeeMultiplierValue: Multiplier = Multiplier::saturating_from_rational(1, 1); + } + + pub type LengthToFee = ConstantMultiplier; + pub type FeeMultiplierUpdate = ConstFeeMultiplier; + + pub type ORMLCurrencyAdapterNegativeImbalance = + <::orml_tokens::MultiTokenCurrencyAdapter as MultiTokenCurrency< + ::AccountId, + >>::NegativeImbalance; + + pub trait OnMultiTokenUnbalanced< + TokenIdType, + Imbalance: ::frame_support::traits::TryDrop + MultiTokenImbalanceWithZeroTrait, + > + { + /// Handler for some imbalances. The different imbalances might have different origins or + /// meanings, dependent on the context. Will default to simply calling on_unbalanced for all + /// of them. Infallible. + fn on_unbalanceds(token_id: TokenIdType, amounts: impl Iterator) + where + Imbalance: ::frame_support::traits::Imbalance, + { + Self::on_unbalanced(amounts.fold(Imbalance::from_zero(token_id), |i, x| x.merge(i))) + } + + /// Handler for some imbalance. Infallible. + fn on_unbalanced(amount: Imbalance) { + amount.try_drop().unwrap_or_else(Self::on_nonzero_unbalanced) + } + + /// Actually handle a non-zero imbalance. You probably want to implement this rather than + /// `on_unbalanced`. + fn on_nonzero_unbalanced(amount: Imbalance) { + drop(amount); + } + } + + pub struct ToAuthor(PhantomData); + impl + OnMultiTokenUnbalanced> for ToAuthor + { + fn on_nonzero_unbalanced(amount: ORMLCurrencyAdapterNegativeImbalance) { + if let Some(author) = ::pallet_authorship::Pallet::::author() { + <::orml_tokens::MultiTokenCurrencyAdapter as MultiTokenCurrency< + ::AccountId, + >>::resolve_creating(amount.0, &author, amount); + } + } + } + + #[derive(Encode, Decode, TypeInfo)] + pub enum LiquidityInfoEnum, T: frame_system::Config> { + Imbalance((C::CurrencyId, NegativeImbalanceOf)), + FeeLock, + FeeLockSwap((pallet_market::PoolInfo, C::CurrencyId, C::Balance)), + } + + pub struct FeeHelpers(PhantomData<(T, Currency, FeeLock)>); + impl FeeHelpers + where + T: pallet_fee_lock::Config, + Currency: + MultiTokenCurrencyExtended, + FeeLock: FeeLockTriggerTrait, + sp_runtime::AccountId20: From + Into, + { + pub fn is_high_value_swap( + fee_lock_metadata: &pallet_fee_lock::FeeLockMetadataInfo, + asset_id: u32, + asset_amount: u128, + ) -> bool { + if let (true, Some(valuation)) = ( + fee_lock_metadata.is_whitelisted(asset_id), + FeeLock::get_swap_valuation_for_token(asset_id, asset_amount), + ) { + valuation >= fee_lock_metadata.swap_value_threshold + } else { + false + } + } + + pub fn can_withdraw_fee( + who: &::AccountId, + fee_lock_metadata: pallet_fee_lock::FeeLockMetadataInfo, + swap_pool_list: Vec, + asset_id_in: TokenId, + asset_amount_in: Balance, + asset_id_out: TokenId, + asset_amount_out: Balance, + ) -> Result>, TransactionValidityError> { + if swap_pool_list.len() == 0 { + return Err(TransactionValidityError::Invalid( + InvalidTransaction::SwapPrevalidation.into(), + )); + } + let fee_pool = Market::get_pool_info(swap_pool_list[0]).map_err(|_| { + TransactionValidityError::Invalid(InvalidTransaction::SwapPrevalidation.into()) + })?; + + if fee_pool.pool.0 != asset_id_in && fee_pool.pool.1 != asset_id_in { + return Err(TransactionValidityError::Invalid( + InvalidTransaction::SwapPrevalidation.into(), + )); + } + + // all swaps pay the trade commision, check balance in case of failure + // min fee is 3, trsy, bnb & pool + let fee = sp_runtime::helpers_128bit::multiply_by_rational_with_rounding( + asset_amount_in, + fees::MarketTotalFee::get(), + fees::FEE_PRECISION, + sp_runtime::Rounding::Down, + ) + .map(|f| f.max(3)); + let balance = Currency::available_balance(asset_id_in, &who.clone()); + if fee.map_or(true, |f| f > balance) { + return Err(TransactionValidityError::Invalid( + InvalidTransaction::SwapPrevalidation.into(), + )); + } + + // no fee lock for allowed high value single pool swap, for both sold & bought asset + if swap_pool_list.len() == 1 && + (Self::is_high_value_swap(&fee_lock_metadata, asset_id_in, asset_amount_in) || + Self::is_high_value_swap( + &fee_lock_metadata, + asset_id_out, + asset_amount_out, + )) { + let _ = FeeLock::unlock_fee(who); + // fee lock for multiswap + } else { + FeeLock::process_fee_lock(who).map_err(|_| { + TransactionValidityError::Invalid(InvalidTransaction::ProcessFeeLock.into()) + })?; + } + + // unwrap ok due above map_or check + Ok(Some(LiquidityInfoEnum::FeeLockSwap((fee_pool, asset_id_in, fee.unwrap())))) + } + + pub fn settle_fees( + who: &::AccountId, + pool: pallet_market::PoolInfo, + fee_asset: TokenId, + fee: Balance, + ) -> Result<(), TransactionValidityError> { + let trsy_account = config::TreasuryAccountIdOf::::get().into(); + let bnb_account = config::BnbAccountIdOf::::get().into(); + + let to_trsy = sp_runtime::helpers_128bit::multiply_by_rational_with_rounding( + fee, + fees::MarketTreasuryFeePart::get(), + fees::FEE_PRECISION, + sp_runtime::Rounding::Down, + ) + .ok_or(TransactionValidityError::Invalid(InvalidTransaction::Payment.into()))?; + + let to_bnb = sp_runtime::helpers_128bit::multiply_by_rational_with_rounding( + to_trsy, + fees::MarketBnBFeePart::get(), + fees::FEE_PRECISION, + sp_runtime::Rounding::Down, + ) + .ok_or(TransactionValidityError::Invalid(InvalidTransaction::Payment.into()))?; + + Currency::transfer( + fee_asset, + who, + &trsy_account, + to_trsy - to_bnb, + ExistenceRequirement::AllowDeath, + ) + .map_err(|_| { + TransactionValidityError::Invalid(InvalidTransaction::Payment.into()) + })?; + + Currency::transfer( + fee_asset, + who, + &bnb_account, + to_bnb, + ExistenceRequirement::AllowDeath, + ) + .map_err(|_| { + TransactionValidityError::Invalid(InvalidTransaction::Payment.into()) + })?; + + // + match pool.kind { + pallet_market::PoolKind::StableSwap => { + StableSwap::settle_pool_fees( + &who.clone().into(), + pool.pool_id, + fee_asset, + fee - to_trsy, + ) + .map_err(|_| { + TransactionValidityError::Invalid(InvalidTransaction::Payment.into()) + })?; + }, + pallet_market::PoolKind::Xyk => { + Xyk::settle_pool_fees( + &who.clone().into(), + pool.pool_id, + fee_asset, + fee - to_trsy, + ) + .map_err(|_| { + TransactionValidityError::Invalid(InvalidTransaction::Payment.into()) + })?; + }, + } + + Ok(()) + } + } + + #[derive(Encode, Decode, Clone, TypeInfo)] + pub struct OnChargeHandler(PhantomData<(C, OU, OCA, OFLA)>); + + /// Default implementation for a Currency and an OnUnbalanced handler. + /// + /// The unbalance handler is given 2 unbalanceds in [`OnUnbalanced::on_unbalanceds`]: fee and + /// then tip. + impl OnChargeTransaction for OnChargeHandler + where + T: pallet_transaction_payment::Config + + pallet_treasury::Config + + pallet_fee_lock::Config, + ::RuntimeCall: + Into + Dispatchable, + C: MultiTokenCurrencyExtended, + OU: OnMultiTokenUnbalanced>, + OCA: OnChargeTransaction< + T, + LiquidityInfo = Option>, + Balance = Balance, + >, + OFLA: FeeLockTriggerTrait, + sp_runtime::AccountId20: From + Into, + { + type LiquidityInfo = Option>; + type Balance = Balance; + + /// Withdraw the predicted fee from the transaction origin. + /// + /// Note: The `fee` already includes the `tip`. + fn withdraw_fee( + who: &T::AccountId, + call: &T::RuntimeCall, + info: &DispatchInfoOf, + fee: Self::Balance, + tip: Self::Balance, + ) -> Result { + let call_type: crate::CallType = (*call).clone().into(); + + match call_type { + crate::CallType::Swap { .. } => { + ensure!( + tip.is_zero(), + TransactionValidityError::Invalid( + InvalidTransaction::TippingNotAllowedForSwaps.into(), + ) + ); + }, + _ => {}, + }; + + // THIS IS NOT PROXY PALLET COMPATIBLE, YET + // Also ugly implementation to keep it maleable for now + match (call_type, pallet_fee_lock::FeeLockMetadata::::get()) { + (crate::CallType::UnlockFee, _) => { + let imb = C::withdraw( + tokens::RxTokenId::get().into(), + who, + tip, + WithdrawReasons::TIP, + ExistenceRequirement::KeepAlive, + ) + .map_err(|_| { + TransactionValidityError::Invalid(InvalidTransaction::Payment.into()) + })?; + + OU::on_unbalanceds(tokens::RxTokenId::get().into(), Some(imb).into_iter()); + OFLA::can_unlock_fee(who).map_err(|_| { + TransactionValidityError::Invalid(InvalidTransaction::UnlockFee.into()) + })?; + Ok(Some(LiquidityInfoEnum::FeeLock)) + }, + ( + CallType::Swap { + swap_pool_list, + asset_id_in, + asset_amount_in, + asset_id_out, + asset_amount_out, + }, + Some(fee_lock_metadata), + ) => FeeHelpers::::can_withdraw_fee( + who, + fee_lock_metadata, + swap_pool_list, + asset_id_in, + asset_amount_in, + asset_id_out, + asset_amount_out, + ), + _ => OCA::withdraw_fee(who, call, info, fee, tip), + } + } + + /// Hand the fee and the tip over to the `[OnUnbalanced]` implementation. + /// Since the predicted fee might have been too high, parts of the fee may + /// be refunded. + /// + /// Note: The `corrected_fee` already includes the `tip`. + fn correct_and_deposit_fee( + who: &T::AccountId, + dispatch_info: &DispatchInfoOf, + post_info: &PostDispatchInfo, + corrected_fee: Self::Balance, + tip: Self::Balance, + already_withdrawn: Self::LiquidityInfo, + ) -> Result<(), TransactionValidityError> { + match already_withdrawn { + Some(LiquidityInfoEnum::Imbalance(_)) => OCA::correct_and_deposit_fee( + who, + dispatch_info, + post_info, + corrected_fee, + tip, + already_withdrawn, + ), + Some(LiquidityInfoEnum::FeeLock) => Ok(()), + Some(LiquidityInfoEnum::FeeLockSwap((pool, fee_asset, fee))) => { + if post_info.pays_fee == Pays::Yes { + FeeHelpers::::settle_fees(who, pool, fee_asset, fee) + .map_err(|_| { + TransactionValidityError::Invalid( + InvalidTransaction::Payment.into(), + ) + })?; + } + Ok(()) + }, + None => Ok(()), + } + } + } + + #[derive(Encode, Decode, Clone, TypeInfo)] + pub struct TwoCurrencyOnChargeAdapter( + PhantomData<(C, OU, T1, T2, SF, TE)>, + ); + + type NegativeImbalanceOf = + ::AccountId>>::NegativeImbalance; + + pub trait TriggerEvent { + fn trigger(who: AccountIdT, token_id: TokenId, fee: Balance, tip: Balance); + } + + /// Default implementation for a Currency and an OnUnbalanced handler. + /// + /// The unbalance handler is given 2 unbalanceds in [`OnUnbalanced::on_unbalanceds`]: fee and + /// then tip. + impl OnChargeTransaction + for TwoCurrencyOnChargeAdapter + where + T: pallet_transaction_payment::Config, + TE: TriggerEvent, + C: MultiTokenCurrency, + OU: OnMultiTokenUnbalanced>, + T1: Get, + T2: Get, + SF: Get, + { + type LiquidityInfo = Option>; + type Balance = Balance; + + /// Withdraw the predicted fee from the transaction origin. + /// + /// Note: The `fee` already includes the `tip`. + fn withdraw_fee( + who: &T::AccountId, + _call: &T::RuntimeCall, + _info: &DispatchInfoOf, + fee: Self::Balance, + tip: Self::Balance, + ) -> Result { + if fee.is_zero() { + return Ok(None) + } + + let withdraw_reason = if tip.is_zero() { + WithdrawReasons::TRANSACTION_PAYMENT + } else { + WithdrawReasons::TRANSACTION_PAYMENT | WithdrawReasons::TIP + }; + + let fee_2 = fee / SF::get(); + + match C::withdraw( + T1::get(), + who, + fee, + withdraw_reason, + ExistenceRequirement::KeepAlive, + ) { + Ok(imbalance) => Ok(Some(LiquidityInfoEnum::Imbalance((T1::get(), imbalance)))), + Err(_) if fee_2.is_zero() => Err(InvalidTransaction::Payment.into()), + Err(_) => match C::withdraw( + T2::get(), + who, + fee_2, + withdraw_reason, + ExistenceRequirement::KeepAlive, + ) { + Ok(imbalance) => + Ok(Some(LiquidityInfoEnum::Imbalance((T2::get(), imbalance)))), + Err(_) => Err(InvalidTransaction::Payment.into()), + }, + } + } + + /// Hand the fee and the tip over to the `[OnUnbalanced]` implementation. + /// Since the predicted fee might have been too high, parts of the fee may + /// be refunded. + /// + /// Note: The `corrected_fee` already includes the `tip`. + fn correct_and_deposit_fee( + who: &T::AccountId, + _dispatch_info: &DispatchInfoOf, + _post_info: &PostDispatchInfoOf, + corrected_fee: Self::Balance, + tip: Self::Balance, + already_withdrawn: Self::LiquidityInfo, + ) -> Result<(), TransactionValidityError> { + if let Some(LiquidityInfoEnum::Imbalance((token_id, paid))) = already_withdrawn { + let (corrected_fee, tip) = if token_id == T2::get() { + (corrected_fee / SF::get(), tip / SF::get()) + } else { + (corrected_fee, tip) + }; + // Calculate how much refund we should return + let refund_amount = paid.peek().saturating_sub(corrected_fee); + // refund to the the account that paid the fees. If this fails, the + // account might have dropped below the existential balance. In + // that case we don't refund anything. + let refund_imbalance = C::deposit_into_existing(token_id, &who, refund_amount) + .unwrap_or_else(|_| C::PositiveImbalance::from_zero(token_id)); + // merge the imbalance caused by paying the fees and refunding parts of it again. + let adjusted_paid = match paid.offset(refund_imbalance) { + SameOrOther::Same(a) => Ok(a), + SameOrOther::None => Ok(C::NegativeImbalance::from_zero(token_id)), + SameOrOther::Other(b) => Err(b), + } + .map_err(|_| TransactionValidityError::Invalid(InvalidTransaction::Payment))?; + // Call someone else to handle the imbalance (fee and tip separately) + let (tip_imb, fee) = adjusted_paid.split(tip); + OU::on_unbalanceds(token_id, Some(fee).into_iter().chain(Some(tip_imb))); + TE::trigger(who.clone(), token_id.into(), corrected_fee.into(), tip.into()); + } + Ok(()) + } + } + } + + pub mod pallet_fee_lock { + use crate::*; + parameter_types! { + pub const MaxCuratedTokens: u32 = 100; + } + } + + pub mod pallet_aura { + use crate::*; + parameter_types! { + pub const MaxAuthorities: u32 = 100_000; + } + } + + pub mod pallet_sudo_origin { + use crate::*; + pub type SudoOrigin = + pallet_collective_mangata::EnsureProportionMoreThan; + } + + pub mod pallet_collective_mangata { + use crate::*; + #[cfg(not(feature = "fast-runtime"))] + parameter_types! { + pub const CouncilProposalCloseDelay: BlockNumber = 3 * consts::DAYS; + } + + #[cfg(feature = "fast-runtime")] + parameter_types! { + pub const CouncilProposalCloseDelay: BlockNumber = 6 * consts::MINUTES; + } + + #[cfg(feature = "runtime-benchamarks")] + parameter_types! { + pub const CouncilProposalCloseDelay: BlockNumber = 0.into(); + } + + parameter_types! { + pub const CouncilMotionDuration: BlockNumber = 5 * consts::DAYS; + pub const CouncilMaxProposals: u32 = 100; + pub const CouncilMaxMembers: u32 = 100; + pub MaxProposalWeight: Weight = Perbill::from_percent(50) * config::frame_system::RuntimeBlockWeights::get().max_block; + } + + pub type SetMembersOrigin = EnsureRoot; + } + + pub mod pallet_membership { + use crate::*; + + parameter_types! { + pub const MaxMembersFoundation: u32 = 3; + pub const MaxMembersTransfer: u32 = 50; + } + + // todo change the `Get` to `Contains` trait and use membership pallet directly + pub struct FoundationAccountsProvider; + impl Get> for FoundationAccountsProvider { + fn get() -> Vec { + FoundationMembers::members().to_vec() + } + } + } + + pub mod parachain_staking { + use crate::*; + + pub type StakingIssuanceVaultOf = + ::StakingIssuanceVault; + #[cfg(feature = "fast-runtime")] + parameter_types! { + /// Default SessionLenght is every 2 minutes (10 * 12 second block times) + pub const BlocksPerRound: u32 = 2 * consts::MINUTES; + } + + #[cfg(not(feature = "fast-runtime"))] + parameter_types! { + /// Default SessionLenght is every 4 hours (1200 * 12 second block times) + pub const BlocksPerRound: u32 = 4 * consts::HOURS; + } + + parameter_types! { + pub const DefaultPayoutLimit: u32 = 3; + /// Collator candidate exit delay (number of rounds) + pub const LeaveCandidatesDelay: u32 = 2; + /// Collator candidate bond increases/decreases delay (number of rounds) + pub const CandidateBondDelay: u32 = 2; + /// Delegator exit delay (number of rounds) + pub const LeaveDelegatorsDelay: u32 = 2; + /// Delegation revocations delay (number of rounds) + pub const RevokeDelegationDelay: u32 = 2; + /// Delegation bond increases/decreases delay (number of rounds) + pub const DelegationBondDelay: u32 = 2; + /// Reward payments delay (number of rounds) + pub const RewardPaymentDelay: u32 = 2; + /// Minimum collators selected per round, default at genesis and minimum forever after + pub const MinSelectedCandidates: u32 = 50; + /// Maximum collator candidates allowed + pub const MaxCollatorCandidates: u32 = 100; + /// Maximum delegators allowed per candidate + pub const MaxTotalDelegatorsPerCandidate: u32 = 30; + /// Maximum delegators counted per candidate + pub const MaxDelegatorsPerCandidate: u32 = 30; + /// Maximum delegations per delegator + pub const MaxDelegationsPerDelegator: u32 = 30; + /// Default fixed percent a collator takes off the top of due rewards + pub const DefaultCollatorCommission: Perbill = Perbill::from_percent(20); + /// Minimum stake required to become a collator + pub const MinCollatorStk: u128 = 10 * currency::DOLLARS; + /// Minimum stake required to be reserved to be a candidate + pub const MinCandidateStk: u128 = if cfg!(feature = "runtime-benchmarks") { + // For benchmarking + 1 * currency::DOLLARS + } else { + // ACTUAL + 500_000 * currency::DOLLARS + }; + /// Minimum stake required to be reserved to be a delegator + pub const MinDelegatorStk: u128 = 1 * currency::CENTS; + } + } + + pub mod pallet_issuance { + use crate::*; + parameter_types! { + pub const HistoryLimit: u32 = 10u32; + + pub const LiquidityMiningIssuanceVaultId: PalletId = PalletId(*b"py/lqmiv"); + pub LiquidityMiningIssuanceVault: AccountId = LiquidityMiningIssuanceVaultId::get().into_account_truncating(); + pub const StakingIssuanceVaultId: PalletId = PalletId(*b"py/stkiv"); + pub StakingIssuanceVault: AccountId = StakingIssuanceVaultId::get().into_account_truncating(); + pub const SequencerIssuanceVaultId: PalletId = PalletId(*b"py/seqiv"); + pub SequencerIssuanceVault: AccountId = SequencerIssuanceVaultId::get().into_account_truncating(); + + pub const TotalCrowdloanAllocation: Balance = 0 * DOLLARS; + pub const LinearIssuanceAmount: Balance = 10_200_000 * DOLLARS; // LinearIssuanceAmount/(LinearIssuanceBlocks/BlocksPerRound) is the value that is issued every session indefintely FOREVER! + pub const LinearIssuanceBlocks: u32 = 10_512_000u32; // 2 years + pub const LiquidityMiningSplit: Perbill = Perbill::from_parts(686000000); // 6_997_200 + pub const StakingSplit: Perbill = Perbill::from_parts(314000000); // 3_202_800 + pub const SequencerSplit: Perbill = Perbill::from_parts(0); // 0 + pub const ImmediateTGEReleasePercent: Percent = Percent::from_percent(100); + // Just some safe values to avoid zero diision etc + // TGE happens on L1 either way + pub const TGEReleasePeriod: u32 = 100u32; // 2 years + pub const TGEReleaseBegin: u32 = 10u32; // Two weeks into chain start + } + } + + pub mod orml_asset_registry { + use crate::*; + + const LIQUIDITY_TOKEN_IDENTIFIER: &[u8] = b"LiquidityPoolToken"; + const HEX_INDICATOR: &[u8] = b"0x"; + const TOKEN_SYMBOL: &[u8] = b"TKN"; + const TOKEN_SYMBOL_SEPARATOR: &[u8] = b"-"; + const DEFAULT_DECIMALS: u32 = 18u32; + + parameter_types! { + pub const StringLimit: u32 = 50; + } + + pub type AssetMetadataOf = AssetMetadata; + type CurrencyAdapter = orml_tokens::MultiTokenCurrencyAdapter; + + pub struct SequentialIdWithCreation(PhantomData); + impl AssetProcessor for SequentialIdWithCreation + where + T: orml_asset_registry::Config, + T: orml_tokens::Config, + T: pallet_treasury::Config, + TokenId: From<::CurrencyId>, + { + fn pre_register( + id: Option, + asset_metadata: AssetMetadataOf, + ) -> Result<(TokenId, AssetMetadataOf), DispatchError> { + let next_id = CurrencyAdapter::::get_next_currency_id(); + let asset_id = id.unwrap_or(next_id.into()); + let treasury_account = + config::TreasuryPalletIdOf::::get().into_account_truncating(); + + match asset_id.cmp(&next_id.into()) { + Ordering::Equal => + CurrencyAdapter::::create(&treasury_account, Default::default()) + .and_then(|created_asset_id| { + match created_asset_id.cmp(&asset_id.into()) { + Ordering::Equal => Ok((asset_id, asset_metadata)), + _ => + Err(orml_asset_registry::Error::::InvalidAssetId.into()), + } + }), + Ordering::Less => Ok((asset_id, asset_metadata)), + _ => Err(orml_asset_registry::Error::::InvalidAssetId.into()), + } + } + } + + pub struct AssetAuthority(PhantomData); + impl EnsureOriginWithArg> for AssetAuthority + where + T: frame_system::Config, + { + type Success = (); + + fn try_origin( + origin: T::RuntimeOrigin, + _asset_id: &Option, + ) -> Result { + as EnsureOrigin>::try_origin(origin) + } + + #[cfg(feature = "runtime-benchmarks")] + fn try_successful_origin(_: &Option) -> Result { + Ok(T::RuntimeOrigin::root()) + } + } + + pub struct AssetRegistryProvider(PhantomData); + impl< + T: orml_asset_registry::Config, + > AssetRegistryProviderTrait for AssetRegistryProvider + { + fn get_l1_asset_id(l1_asset: L1Asset) -> Option { + orml_asset_registry::L1AssetToId::::get(l1_asset) + } + + fn create_l1_asset(l1_asset: L1Asset) -> Result { + let metadata = AssetMetadata { + decimals: 18_u32, + name: b"L1Asset".to_vec().try_into().unwrap(), + symbol: b"L1Asset".to_vec().try_into().unwrap(), + existential_deposit: Zero::zero(), + additional: CustomMetadata { xcm: None, xyk: None }, + }; + + orml_asset_registry::Pallet::::do_register_l1_asset(metadata, None, l1_asset) + } + + fn get_asset_l1_id(asset_id: T::AssetId) -> Option { + orml_asset_registry::IdToL1Asset::::get(asset_id) + } + + fn create_pool_asset( + lp_asset: T::AssetId, + asset_1: T::AssetId, + asset_2: T::AssetId, + ) -> DispatchResult { + let name_lp = format_asset_id(lp_asset); + let name_asset_1 = format_asset_id(asset_1); + let name_asset_2 = format_asset_id(asset_2); + + let mut name: Vec = Vec::::new(); + name.extend_from_slice(LIQUIDITY_TOKEN_IDENTIFIER); + name.extend_from_slice(HEX_INDICATOR); + name.extend_from_slice(&name_lp); + + let mut symbol: Vec = Vec::::new(); + symbol.extend_from_slice(TOKEN_SYMBOL); + symbol.extend_from_slice(HEX_INDICATOR); + symbol.extend_from_slice(&name_asset_1); + symbol.extend_from_slice(TOKEN_SYMBOL_SEPARATOR); + symbol.extend_from_slice(TOKEN_SYMBOL); + symbol.extend_from_slice(HEX_INDICATOR); + symbol.extend_from_slice(&name_asset_2); + + let metadata = AssetMetadata { + decimals: DEFAULT_DECIMALS, + name: BoundedVec::truncate_from(name), + symbol: BoundedVec::truncate_from(symbol), + existential_deposit: Zero::zero(), + additional: Default::default(), + }; + + orml_asset_registry::Pallet::::do_register_asset_without_asset_processor( + metadata, lp_asset, + ) + } + } + + impl> orml_traits::asset_registry::Inspect + for AssetRegistryProvider + { + type AssetId = T::AssetId; + type Balance = T::Balance; + type CustomMetadata = T::CustomMetadata; + type StringLimit = T::StringLimit; + + fn metadata( + asset_id: &Self::AssetId, + ) -> Option> { + orml_asset_registry::Pallet::::metadata(asset_id) + } + } + + // 48 in utf-8 '0' + // 55 in utf-8 '0' + gap between '9' and 'A' + pub fn format_asset_id(num: TokenId) -> Vec { + let mut result = Vec::new(); + for bytes in num.to_be_bytes().iter() { + match (bytes >> 4) as u8 { + x @ 0u8..=9u8 => result.push(x + 48), + x => result.push(x + 55), + } + match (bytes & 0b0000_1111) as u8 { + x @ 0u8..=9u8 => result.push(x + 48), + x => result.push(x + 55), + } + } + result + } + } + + pub mod pallet_identity { + use crate::*; + parameter_types! { + // difference of 26 bytes on-chain for the registration and 9 bytes on-chain for the identity + // information, already accounted for by the byte deposit + pub const BasicDeposit: Balance = deposit(1, 17); + pub const ByteDeposit: Balance = deposit(0, 1); + // Add item in storage, and takes 97 bytes, AccountId + (AccountId, [u8,32]) + pub const SubAccountDeposit: Balance = deposit(1, 97); + pub const MaxSubAccounts: u32 = 100; + pub const MaxAdditionalFields: u32 = 100; + pub const MaxRegistrars: u32 = 20; + pub const PendingUsernameExpiration: u32 = 7 * consts::DAYS; + pub const MaxSuffixLength: u32 = 7; + pub const MaxUsernameLength: u32 = 32; + } + + pub type IdentityForceOrigin = EnsureRoot; + pub type IdentityRegistrarOrigin = EnsureRoot; + } + + pub mod pallet_utility_mangata { + use super::*; + + #[derive( + Copy, + Clone, + Eq, + PartialEq, + Ord, + PartialOrd, + Encode, + Decode, + RuntimeDebug, + MaxEncodedLen, + TypeInfo, + )] + pub struct DisallowedInBatch(PhantomData); + + impl Contains for DisallowedInBatch + where + T: ::frame_system::Config, + ::RuntimeCall: Into, + { + fn contains(c: &T::RuntimeCall) -> bool { + let call: crate::CallType = (c.clone()).into(); + + match call { + CallType::Swap { .. } => true, + _ => false, + } + } + } + } + + pub mod pallet_vesting_mangata { + use super::*; + parameter_types! { + pub const MinVestedTransfer: Balance = 100 * currency::DOLLARS; + pub UnvestedFundsAllowedWithdrawReasons: WithdrawReasons = + WithdrawReasons::except(WithdrawReasons::TRANSFER | WithdrawReasons::RESERVE); + } + } + + pub mod pallet_crowdloan_rewards { + use super::*; + parameter_types! { + pub const Initialized: bool = false; + pub const InitializationPayment: Perbill = Perbill::from_parts(214285700); + pub const MaxInitContributorsBatchSizes: u32 = 100; + pub const MinimumReward: Balance = 0; + pub const RelaySignaturesThreshold: Perbill = Perbill::from_percent(100); + pub const SigantureNetworkIdentifier: &'static [u8] = b"mangata-"; + } + } + + pub mod pallet_proxy { + use super::*; + // Proxy Pallet + /// The type used to represent the kinds of proxying allowed. + #[derive( + Copy, + Clone, + Eq, + PartialEq, + Ord, + PartialOrd, + Encode, + Decode, + RuntimeDebug, + MaxEncodedLen, + TypeInfo, + )] + pub enum ProxyType { + AutoCompound, + } + + impl Default for ProxyType { + fn default() -> Self { + Self::AutoCompound + } + } + + parameter_types! { + pub const ProxyDepositBase: Balance = deposit(1, 16); + pub const ProxyDepositFactor: Balance = deposit(0, 33); + pub const AnnouncementDepositBase: Balance = deposit(1, 16); + pub const AnnouncementDepositFactor: Balance = deposit(0, 68); + } + } + + pub mod pallet_proof_of_stake { + use super::*; + + parameter_types! { + pub const RewardsSchedulesLimit: u32 = 10_000u32; + // NOTE: 1725 is how much USDT you get for one MGX as of 12.2023 + pub const Min3rdPartyRewardValutationPerSession: u128 = 10 * 1725 * currency::DOLLARS; + pub const Min3rdPartyRewardVolume: u128 = 10_000 * 1725 * currency::DOLLARS; + pub const SchedulesPerBlock: u32 = 5; + } + } + + pub mod pallet_sequencer_staking { + use super::*; + + parameter_types! { + pub const CancellerRewardPercentage: Permill = Permill::from_percent(20); + } + } + + pub mod pallet_rolldown { + use super::*; + + parameter_types! { + pub const CancellerRewardPercentage: Permill = Permill::from_percent(20); + pub const RequestsPerBlock: u128 = 50; + pub const RightsMultiplier: u128 = 1; + } + + #[cfg(feature = "fast-runtime")] + parameter_types! { + pub const MerkleRootAutomaticBatchPeriod: u128 = 25; + pub const MerkleRootAutomaticBatchSize: u128 = 32; + } + + #[cfg(not(feature = "fast-runtime"))] + parameter_types! { + pub const MerkleRootAutomaticBatchPeriod: u128 = 600; + pub const MerkleRootAutomaticBatchSize: u128 = 500; + } + + pub struct WithdrawFee; + impl Convert<::pallet_rolldown::messages::Chain, Balance> for WithdrawFee { + fn convert(chain: ::pallet_rolldown::messages::Chain) -> Balance { + match chain { + ::pallet_rolldown::messages::Chain::Ethereum => 5 * currency::DOLLARS, + ::pallet_rolldown::messages::Chain::Arbitrum => 5 * currency::DOLLARS, + ::pallet_rolldown::messages::Chain::Base => 5 * currency::DOLLARS, + } + } + } + } + + pub mod pallet_market { + use super::*; + + // in case of other valuation use a struct + // that impl the ValuateFor trait and delegates to Market for Valuate impl + // use such struct in pallet configs instead of Market + impl ValuateFor for Market {} + } +} diff --git a/gasp-node/rollup/runtime/src/weights/block_weights.rs b/gasp-node/rollup/runtime/src/weights/block_weights.rs new file mode 100644 index 000000000..24993a10c --- /dev/null +++ b/gasp-node/rollup/runtime/src/weights/block_weights.rs @@ -0,0 +1,65 @@ +//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 32.0.0 +//! DATE: 2024-11-29 (Y/M/D) +//! HOSTNAME: `98ec9bd8ef1f`, CPU: `AMD EPYC 7B13` +//! +//! SHORT-NAME: `block`, LONG-NAME: `BlockExecution`, RUNTIME: `Rollup Local` +//! WARMUPS: `10`, REPEAT: `100` +//! WEIGHT-PATH: `` +//! WEIGHT-METRIC: `Average`, WEIGHT-MUL: `1.0`, WEIGHT-ADD: `0` + +// Executed Command: +// target/release/rollup-node +// benchmark +// overhead +// --chain +// rollup-local +// -lblock_builder=debug +// --max-ext-per-block +// 50000 +// --base-path +// . + +use sp_core::parameter_types; +use sp_weights::{constants::WEIGHT_REF_TIME_PER_NANOS, Weight}; + +parameter_types! { + /// Time to execute an empty block. + /// Calculated by multiplying the *Average* with `1.0` and adding `0`. + /// + /// Stats nanoseconds: + /// Min, Max: 30_924_759, 36_916_389 + /// Average: 31_490_935 + /// Median: 31_336_589 + /// Std-Dev: 697753.75 + /// + /// Percentiles nanoseconds: + /// 99th: 33_779_619 + /// 95th: 32_274_600 + /// 75th: 31_631_190 + pub const BlockExecutionWeight: Weight = + Weight::from_parts(WEIGHT_REF_TIME_PER_NANOS.saturating_mul(31_490_935), 0); +} + +#[cfg(test)] +mod test_weights { + use sp_weights::constants; + + /// Checks that the weight exists and is sane. + // NOTE: If this test fails but you are sure that the generated values are fine, + // you can delete it. + #[test] + fn sane() { + let w = super::BlockExecutionWeight::get(); + + // At least 100 µs. + assert!( + w.ref_time() >= 100u64 * constants::WEIGHT_REF_TIME_PER_MICROS, + "Weight should be at least 100 µs." + ); + // At most 50 ms. + assert!( + w.ref_time() <= 50u64 * constants::WEIGHT_REF_TIME_PER_MILLIS, + "Weight should be at most 50 ms." + ); + } +} diff --git a/gasp-node/rollup/runtime/src/weights/extrinsic_weights.rs b/gasp-node/rollup/runtime/src/weights/extrinsic_weights.rs new file mode 100644 index 000000000..6d602b628 --- /dev/null +++ b/gasp-node/rollup/runtime/src/weights/extrinsic_weights.rs @@ -0,0 +1,65 @@ +//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 32.0.0 +//! DATE: 2024-11-29 (Y/M/D) +//! HOSTNAME: `98ec9bd8ef1f`, CPU: `AMD EPYC 7B13` +//! +//! SHORT-NAME: `extrinsic`, LONG-NAME: `ExtrinsicBase`, RUNTIME: `Rollup Local` +//! WARMUPS: `10`, REPEAT: `100` +//! WEIGHT-PATH: `` +//! WEIGHT-METRIC: `Average`, WEIGHT-MUL: `1.0`, WEIGHT-ADD: `0` + +// Executed Command: +// target/release/rollup-node +// benchmark +// overhead +// --chain +// rollup-local +// -lblock_builder=debug +// --max-ext-per-block +// 50000 +// --base-path +// . + +use sp_core::parameter_types; +use sp_weights::{constants::WEIGHT_REF_TIME_PER_NANOS, Weight}; + +parameter_types! { + /// Time to execute a NO-OP extrinsic, for example `System::remark`. + /// Calculated by multiplying the *Average* with `1.0` and adding `0`. + /// + /// Stats nanoseconds: + /// Min, Max: 208_592, 256_940 + /// Average: 212_669 + /// Median: 211_383 + /// Std-Dev: 5780.19 + /// + /// Percentiles nanoseconds: + /// 99th: 242_190 + /// 95th: 217_274 + /// 75th: 212_527 + pub const ExtrinsicBaseWeight: Weight = + Weight::from_parts(WEIGHT_REF_TIME_PER_NANOS.saturating_mul(212_669), 0); +} + +#[cfg(test)] +mod test_weights { + use sp_weights::constants; + + /// Checks that the weight exists and is sane. + // NOTE: If this test fails but you are sure that the generated values are fine, + // you can delete it. + #[test] + fn sane() { + let w = super::ExtrinsicBaseWeight::get(); + + // At least 10 µs. + assert!( + w.ref_time() >= 10u64 * constants::WEIGHT_REF_TIME_PER_MICROS, + "Weight should be at least 10 µs." + ); + // At most 1 ms. + assert!( + w.ref_time() <= constants::WEIGHT_REF_TIME_PER_MILLIS, + "Weight should be at most 1 ms." + ); + } +} diff --git a/gasp-node/rollup/runtime/src/weights/frame_system.rs b/gasp-node/rollup/runtime/src/weights/frame_system.rs new file mode 100644 index 000000000..899f19ea5 --- /dev/null +++ b/gasp-node/rollup/runtime/src/weights/frame_system.rs @@ -0,0 +1,223 @@ +// This file is part of Mangata. + +// Copyright (C) 2020-2022 Mangata Foundation. +// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 + +// This program 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. + +// This program 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 this program. If not, see . + +//! Autogenerated weights for frame_system +//! +//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 32.0.0 +//! DATE: 2024-11-29, STEPS: `50`, REPEAT: 20, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! EXECUTION: , WASM-EXECUTION: Compiled, CHAIN: Some("rollup-local"), DB CACHE: 1024 + +// Executed Command: +// target/release/rollup-node +// benchmark +// pallet +// -l=info,runtime::collective=warn,xyk=warn +// --chain +// rollup-local +// --wasm-execution +// compiled +// --pallet +// * +// --extrinsic +// * +// --steps +// 50 +// --repeat +// 20 +// --template +// ./templates/module-weight-template.hbs +// --output +// ./benchmarks/ + +#![cfg_attr(rustfmt, rustfmt_skip)] +#![allow(unused_parens)] +#![allow(unused_imports)] +#![allow(clippy::unnecessary_cast)] + +use frame_support::{traits::Get, weights::{Weight, constants::RocksDbWeight}}; +use sp_std::marker::PhantomData; + +/// Weight functions needed for frame_system. +pub trait WeightInfo { + fn remark(b: u32, ) -> Weight; + fn remark_with_event(b: u32, ) -> Weight; + fn set_heap_pages() -> Weight; + fn set_code() -> Weight; + fn set_storage(i: u32, ) -> Weight; + fn kill_storage(i: u32, ) -> Weight; + fn kill_prefix(p: u32, ) -> Weight; + fn authorize_upgrade() -> Weight; + fn apply_authorized_upgrade() -> Weight; +} + +/// Weights for frame_system using the Mangata node and recommended hardware. +pub struct ModuleWeight(PhantomData); +impl frame_system::WeightInfo for ModuleWeight { + fn remark(b: u32, ) -> Weight { + (Weight::from_parts(17_595_859, 0)) + // Standard Error: 3 + .saturating_add((Weight::from_parts(320, 0)).saturating_mul(b as u64)) + } + fn remark_with_event(b: u32, ) -> Weight { + (Weight::from_parts(13_544_792, 0)) + // Standard Error: 1 + .saturating_add((Weight::from_parts(1_429, 0)).saturating_mul(b as u64)) + } + // Storage: `System::Digest` (r:1 w:1) + // Proof: `System::Digest` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + // Storage: UNKNOWN KEY `0x3a686561707061676573` (r:0 w:1) + // Proof: UNKNOWN KEY `0x3a686561707061676573` (r:0 w:1) + fn set_heap_pages() -> Weight { + (Weight::from_parts(6_691_000, 0)) + .saturating_add(T::DbWeight::get().reads(1 as u64)) + .saturating_add(T::DbWeight::get().writes(2 as u64)) + } + // Storage: `Maintenance::MaintenanceStatus` (r:1 w:0) + // Proof: `Maintenance::MaintenanceStatus` (`max_values`: Some(1), `max_size`: Some(2), added: 497, mode: `MaxEncodedLen`) + // Storage: `System::Digest` (r:1 w:1) + // Proof: `System::Digest` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + // Storage: UNKNOWN KEY `0x3a636f6465` (r:0 w:1) + // Proof: UNKNOWN KEY `0x3a636f6465` (r:0 w:1) + fn set_code() -> Weight { + (Weight::from_parts(133_505_038_000, 0)) + .saturating_add(T::DbWeight::get().reads(2 as u64)) + .saturating_add(T::DbWeight::get().writes(2 as u64)) + } + // Storage: `Skipped::Metadata` (r:0 w:0) + // Proof: `Skipped::Metadata` (`max_values`: None, `max_size`: None, mode: `Measured`) + fn set_storage(i: u32, ) -> Weight { + (Weight::from_parts(3_780_000, 0)) + // Standard Error: 1_050 + .saturating_add((Weight::from_parts(1_197_417, 0)).saturating_mul(i as u64)) + .saturating_add(T::DbWeight::get().writes((1 as u64).saturating_mul(i as u64))) + } + // Storage: `Skipped::Metadata` (r:0 w:0) + // Proof: `Skipped::Metadata` (`max_values`: None, `max_size`: None, mode: `Measured`) + fn kill_storage(i: u32, ) -> Weight { + (Weight::from_parts(3_750_000, 0)) + // Standard Error: 1_618 + .saturating_add((Weight::from_parts(855_537, 0)).saturating_mul(i as u64)) + .saturating_add(T::DbWeight::get().writes((1 as u64).saturating_mul(i as u64))) + } + // Storage: `Skipped::Metadata` (r:0 w:0) + // Proof: `Skipped::Metadata` (`max_values`: None, `max_size`: None, mode: `Measured`) + fn kill_prefix(p: u32, ) -> Weight { + (Weight::from_parts(6_800_000, 0)) + // Standard Error: 2_455 + .saturating_add((Weight::from_parts(1_479_400, 0)).saturating_mul(p as u64)) + .saturating_add(T::DbWeight::get().reads((1 as u64).saturating_mul(p as u64))) + .saturating_add(T::DbWeight::get().writes((1 as u64).saturating_mul(p as u64))) + } + // Storage: `System::AuthorizedUpgrade` (r:0 w:1) + // Proof: `System::AuthorizedUpgrade` (`max_values`: Some(1), `max_size`: Some(33), added: 528, mode: `MaxEncodedLen`) + fn authorize_upgrade() -> Weight { + (Weight::from_parts(16_100_000, 0)) + .saturating_add(T::DbWeight::get().writes(1 as u64)) + } + // Storage: `System::AuthorizedUpgrade` (r:1 w:1) + // Proof: `System::AuthorizedUpgrade` (`max_values`: Some(1), `max_size`: Some(33), added: 528, mode: `MaxEncodedLen`) + // Storage: `Maintenance::MaintenanceStatus` (r:1 w:0) + // Proof: `Maintenance::MaintenanceStatus` (`max_values`: Some(1), `max_size`: Some(2), added: 497, mode: `MaxEncodedLen`) + // Storage: `System::Digest` (r:1 w:1) + // Proof: `System::Digest` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + // Storage: UNKNOWN KEY `0x3a636f6465` (r:0 w:1) + // Proof: UNKNOWN KEY `0x3a636f6465` (r:0 w:1) + fn apply_authorized_upgrade() -> Weight { + (Weight::from_parts(135_625_658_000, 0)) + .saturating_add(T::DbWeight::get().reads(3 as u64)) + .saturating_add(T::DbWeight::get().writes(3 as u64)) + } +} + +// For backwards compatibility and tests +impl WeightInfo for () { + fn remark(b: u32, ) -> Weight { + (Weight::from_parts(17_595_859, 0)) + // Standard Error: 3 + .saturating_add((Weight::from_parts(320, 0)).saturating_mul(b as u64)) + } + fn remark_with_event(b: u32, ) -> Weight { + (Weight::from_parts(13_544_792, 0)) + // Standard Error: 1 + .saturating_add((Weight::from_parts(1_429, 0)).saturating_mul(b as u64)) + } + // Storage: `System::Digest` (r:1 w:1) + // Proof: `System::Digest` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + // Storage: UNKNOWN KEY `0x3a686561707061676573` (r:0 w:1) + // Proof: UNKNOWN KEY `0x3a686561707061676573` (r:0 w:1) + fn set_heap_pages() -> Weight { + (Weight::from_parts(6_691_000, 0)) + .saturating_add(RocksDbWeight::get().reads(1 as u64)) + .saturating_add(RocksDbWeight::get().writes(2 as u64)) + } + // Storage: `Maintenance::MaintenanceStatus` (r:1 w:0) + // Proof: `Maintenance::MaintenanceStatus` (`max_values`: Some(1), `max_size`: Some(2), added: 497, mode: `MaxEncodedLen`) + // Storage: `System::Digest` (r:1 w:1) + // Proof: `System::Digest` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + // Storage: UNKNOWN KEY `0x3a636f6465` (r:0 w:1) + // Proof: UNKNOWN KEY `0x3a636f6465` (r:0 w:1) + fn set_code() -> Weight { + (Weight::from_parts(133_505_038_000, 0)) + .saturating_add(RocksDbWeight::get().reads(2 as u64)) + .saturating_add(RocksDbWeight::get().writes(2 as u64)) + } + // Storage: `Skipped::Metadata` (r:0 w:0) + // Proof: `Skipped::Metadata` (`max_values`: None, `max_size`: None, mode: `Measured`) + fn set_storage(i: u32, ) -> Weight { + (Weight::from_parts(3_780_000, 0)) + // Standard Error: 1_050 + .saturating_add((Weight::from_parts(1_197_417, 0)).saturating_mul(i as u64)) + .saturating_add(RocksDbWeight::get().writes((1 as u64).saturating_mul(i as u64))) + } + // Storage: `Skipped::Metadata` (r:0 w:0) + // Proof: `Skipped::Metadata` (`max_values`: None, `max_size`: None, mode: `Measured`) + fn kill_storage(i: u32, ) -> Weight { + (Weight::from_parts(3_750_000, 0)) + // Standard Error: 1_618 + .saturating_add((Weight::from_parts(855_537, 0)).saturating_mul(i as u64)) + .saturating_add(RocksDbWeight::get().writes((1 as u64).saturating_mul(i as u64))) + } + // Storage: `Skipped::Metadata` (r:0 w:0) + // Proof: `Skipped::Metadata` (`max_values`: None, `max_size`: None, mode: `Measured`) + fn kill_prefix(p: u32, ) -> Weight { + (Weight::from_parts(6_800_000, 0)) + // Standard Error: 2_455 + .saturating_add((Weight::from_parts(1_479_400, 0)).saturating_mul(p as u64)) + .saturating_add(RocksDbWeight::get().reads((1 as u64).saturating_mul(p as u64))) + .saturating_add(RocksDbWeight::get().writes((1 as u64).saturating_mul(p as u64))) + } + // Storage: `System::AuthorizedUpgrade` (r:0 w:1) + // Proof: `System::AuthorizedUpgrade` (`max_values`: Some(1), `max_size`: Some(33), added: 528, mode: `MaxEncodedLen`) + fn authorize_upgrade() -> Weight { + (Weight::from_parts(16_100_000, 0)) + .saturating_add(RocksDbWeight::get().writes(1 as u64)) + } + // Storage: `System::AuthorizedUpgrade` (r:1 w:1) + // Proof: `System::AuthorizedUpgrade` (`max_values`: Some(1), `max_size`: Some(33), added: 528, mode: `MaxEncodedLen`) + // Storage: `Maintenance::MaintenanceStatus` (r:1 w:0) + // Proof: `Maintenance::MaintenanceStatus` (`max_values`: Some(1), `max_size`: Some(2), added: 497, mode: `MaxEncodedLen`) + // Storage: `System::Digest` (r:1 w:1) + // Proof: `System::Digest` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + // Storage: UNKNOWN KEY `0x3a636f6465` (r:0 w:1) + // Proof: UNKNOWN KEY `0x3a636f6465` (r:0 w:1) + fn apply_authorized_upgrade() -> Weight { + (Weight::from_parts(135_625_658_000, 0)) + .saturating_add(RocksDbWeight::get().reads(3 as u64)) + .saturating_add(RocksDbWeight::get().writes(3 as u64)) + } +} diff --git a/gasp-node/rollup/runtime/src/weights/mod.rs b/gasp-node/rollup/runtime/src/weights/mod.rs new file mode 100644 index 000000000..6599da0a5 --- /dev/null +++ b/gasp-node/rollup/runtime/src/weights/mod.rs @@ -0,0 +1,43 @@ +#![allow(clippy::unnecessary_cast)] + +mod block_weights; +mod extrinsic_weights; + +pub use block_weights::BlockExecutionWeight as VerBlockExecutionWeight; +pub use extrinsic_weights::ExtrinsicBaseWeight as VerExtrinsicBaseWeight; + +pub mod frame_system; +pub mod orml_asset_registry; +pub mod orml_tokens; +pub mod pallet_bootstrap; +pub mod pallet_collective_mangata; +pub mod pallet_crowdloan_rewards; +pub mod pallet_fee_lock; +pub mod pallet_issuance; +pub mod pallet_market; +pub mod pallet_multipurpose_liquidity; +pub mod pallet_proof_of_stake; +pub mod pallet_rolldown; +pub mod pallet_session; +pub mod pallet_timestamp; +pub mod pallet_treasury; +pub mod pallet_utility_mangata; +pub mod pallet_vesting_mangata; +pub mod pallet_xyk; +pub mod parachain_staking; + +pub use self::{ + frame_system as frame_system_weights, orml_asset_registry as orml_asset_registry_weights, + orml_tokens as orml_tokens_weights, pallet_bootstrap as pallet_bootstrap_weights, + pallet_collective_mangata as pallet_collective_mangata_weights, + pallet_crowdloan_rewards as pallet_crowdloan_rewards_weights, + pallet_fee_lock as pallet_fee_lock_weights, pallet_issuance as pallet_issuance_weights, + pallet_market as pallet_market_weights, + pallet_multipurpose_liquidity as pallet_multipurpose_liquidity_weights, + pallet_proof_of_stake as pallet_proof_of_stake_weights, + pallet_rolldown as pallet_rolldown_weights, pallet_session as pallet_session_weights, + pallet_timestamp as pallet_timestamp_weights, pallet_treasury as pallet_treasury_weights, + pallet_utility_mangata as pallet_utility_mangata_weights, + pallet_vesting_mangata as pallet_vesting_mangata_weights, pallet_xyk as pallet_xyk_weights, + parachain_staking as parachain_staking_weights, +}; diff --git a/gasp-node/rollup/runtime/src/weights/orml_asset_registry.rs b/gasp-node/rollup/runtime/src/weights/orml_asset_registry.rs new file mode 100644 index 000000000..76f2f7928 --- /dev/null +++ b/gasp-node/rollup/runtime/src/weights/orml_asset_registry.rs @@ -0,0 +1,104 @@ +// This file is part of Mangata. + +// Copyright (C) 2020-2022 Mangata Foundation. +// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 + +// This program 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. + +// This program 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 this program. If not, see . + +//! Autogenerated weights for orml_asset_registry +//! +//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 32.0.0 +//! DATE: 2024-11-29, STEPS: `50`, REPEAT: 20, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! EXECUTION: , WASM-EXECUTION: Compiled, CHAIN: Some("rollup-local"), DB CACHE: 1024 + +// Executed Command: +// target/release/rollup-node +// benchmark +// pallet +// -l=info,runtime::collective=warn,xyk=warn +// --chain +// rollup-local +// --wasm-execution +// compiled +// --pallet +// * +// --extrinsic +// * +// --steps +// 50 +// --repeat +// 20 +// --template +// ./templates/module-weight-template.hbs +// --output +// ./benchmarks/ + +#![cfg_attr(rustfmt, rustfmt_skip)] +#![allow(unused_parens)] +#![allow(unused_imports)] +#![allow(clippy::unnecessary_cast)] + +use frame_support::{traits::Get, weights::{Weight, constants::RocksDbWeight}}; +use sp_std::marker::PhantomData; + +/// Weight functions needed for orml_asset_registry. +pub trait WeightInfo { + fn register_asset() -> Weight; + fn update_asset() -> Weight; +} + +/// Weights for orml_asset_registry using the Mangata node and recommended hardware. +pub struct ModuleWeight(PhantomData); +impl orml_asset_registry::WeightInfo for ModuleWeight { + // Storage: `Tokens::NextCurrencyId` (r:1 w:1) + // Proof: `Tokens::NextCurrencyId` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) + // Storage: `Tokens::Accounts` (r:1 w:0) + // Proof: `Tokens::Accounts` (`max_values`: None, `max_size`: Some(96), added: 2571, mode: `MaxEncodedLen`) + // Storage: `AssetRegistry::Metadata` (r:1 w:1) + // Proof: `AssetRegistry::Metadata` (`max_values`: None, `max_size`: Some(153), added: 2628, mode: `MaxEncodedLen`) + fn register_asset() -> Weight { + (Weight::from_parts(28_460_000, 0)) + .saturating_add(T::DbWeight::get().reads(3 as u64)) + .saturating_add(T::DbWeight::get().writes(2 as u64)) + } + // Storage: `AssetRegistry::Metadata` (r:1 w:1) + // Proof: `AssetRegistry::Metadata` (`max_values`: None, `max_size`: Some(153), added: 2628, mode: `MaxEncodedLen`) + fn update_asset() -> Weight { + (Weight::from_parts(18_100_000, 0)) + .saturating_add(T::DbWeight::get().reads(1 as u64)) + .saturating_add(T::DbWeight::get().writes(1 as u64)) + } +} + +// For backwards compatibility and tests +impl WeightInfo for () { + // Storage: `Tokens::NextCurrencyId` (r:1 w:1) + // Proof: `Tokens::NextCurrencyId` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) + // Storage: `Tokens::Accounts` (r:1 w:0) + // Proof: `Tokens::Accounts` (`max_values`: None, `max_size`: Some(96), added: 2571, mode: `MaxEncodedLen`) + // Storage: `AssetRegistry::Metadata` (r:1 w:1) + // Proof: `AssetRegistry::Metadata` (`max_values`: None, `max_size`: Some(153), added: 2628, mode: `MaxEncodedLen`) + fn register_asset() -> Weight { + (Weight::from_parts(28_460_000, 0)) + .saturating_add(RocksDbWeight::get().reads(3 as u64)) + .saturating_add(RocksDbWeight::get().writes(2 as u64)) + } + // Storage: `AssetRegistry::Metadata` (r:1 w:1) + // Proof: `AssetRegistry::Metadata` (`max_values`: None, `max_size`: Some(153), added: 2628, mode: `MaxEncodedLen`) + fn update_asset() -> Weight { + (Weight::from_parts(18_100_000, 0)) + .saturating_add(RocksDbWeight::get().reads(1 as u64)) + .saturating_add(RocksDbWeight::get().writes(1 as u64)) + } +} diff --git a/gasp-node/rollup/runtime/src/weights/orml_tokens.rs b/gasp-node/rollup/runtime/src/weights/orml_tokens.rs new file mode 100644 index 000000000..f62431770 --- /dev/null +++ b/gasp-node/rollup/runtime/src/weights/orml_tokens.rs @@ -0,0 +1,215 @@ +// This file is part of Mangata. + +// Copyright (C) 2020-2022 Mangata Foundation. +// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 + +// This program 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. + +// This program 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 this program. If not, see . + +//! Autogenerated weights for orml_tokens +//! +//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 32.0.0 +//! DATE: 2024-11-29, STEPS: `50`, REPEAT: 20, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! EXECUTION: , WASM-EXECUTION: Compiled, CHAIN: Some("rollup-local"), DB CACHE: 1024 + +// Executed Command: +// target/release/rollup-node +// benchmark +// pallet +// -l=info,runtime::collective=warn,xyk=warn +// --chain +// rollup-local +// --wasm-execution +// compiled +// --pallet +// * +// --extrinsic +// * +// --steps +// 50 +// --repeat +// 20 +// --template +// ./templates/module-weight-template.hbs +// --output +// ./benchmarks/ + +#![cfg_attr(rustfmt, rustfmt_skip)] +#![allow(unused_parens)] +#![allow(unused_imports)] +#![allow(clippy::unnecessary_cast)] + +use frame_support::{traits::Get, weights::{Weight, constants::RocksDbWeight}}; +use sp_std::marker::PhantomData; + +/// Weight functions needed for orml_tokens. +pub trait WeightInfo { + fn transfer() -> Weight; + fn transfer_all() -> Weight; + fn transfer_keep_alive() -> Weight; + fn force_transfer() -> Weight; + fn set_balance() -> Weight; + fn create() -> Weight; + fn mint() -> Weight; +} + +/// Weights for orml_tokens using the Mangata node and recommended hardware. +pub struct ModuleWeight(PhantomData); +impl orml_tokens::WeightInfo for ModuleWeight { + // Storage: `Tokens::Accounts` (r:2 w:2) + // Proof: `Tokens::Accounts` (`max_values`: None, `max_size`: Some(96), added: 2571, mode: `MaxEncodedLen`) + // Storage: `System::Account` (r:1 w:1) + // Proof: `System::Account` (`max_values`: None, `max_size`: Some(52), added: 2527, mode: `MaxEncodedLen`) + fn transfer() -> Weight { + (Weight::from_parts(43_930_000, 0)) + .saturating_add(T::DbWeight::get().reads(3 as u64)) + .saturating_add(T::DbWeight::get().writes(3 as u64)) + } + // Storage: `Tokens::Accounts` (r:2 w:2) + // Proof: `Tokens::Accounts` (`max_values`: None, `max_size`: Some(96), added: 2571, mode: `MaxEncodedLen`) + // Storage: `System::Account` (r:1 w:1) + // Proof: `System::Account` (`max_values`: None, `max_size`: Some(52), added: 2527, mode: `MaxEncodedLen`) + fn transfer_all() -> Weight { + (Weight::from_parts(45_840_000, 0)) + .saturating_add(T::DbWeight::get().reads(3 as u64)) + .saturating_add(T::DbWeight::get().writes(3 as u64)) + } + // Storage: `Tokens::Accounts` (r:2 w:2) + // Proof: `Tokens::Accounts` (`max_values`: None, `max_size`: Some(96), added: 2571, mode: `MaxEncodedLen`) + // Storage: `System::Account` (r:1 w:1) + // Proof: `System::Account` (`max_values`: None, `max_size`: Some(52), added: 2527, mode: `MaxEncodedLen`) + fn transfer_keep_alive() -> Weight { + (Weight::from_parts(41_180_000, 0)) + .saturating_add(T::DbWeight::get().reads(3 as u64)) + .saturating_add(T::DbWeight::get().writes(3 as u64)) + } + // Storage: `Tokens::Accounts` (r:2 w:2) + // Proof: `Tokens::Accounts` (`max_values`: None, `max_size`: Some(96), added: 2571, mode: `MaxEncodedLen`) + // Storage: `System::Account` (r:2 w:1) + // Proof: `System::Account` (`max_values`: None, `max_size`: Some(52), added: 2527, mode: `MaxEncodedLen`) + fn force_transfer() -> Weight { + (Weight::from_parts(45_650_000, 0)) + .saturating_add(T::DbWeight::get().reads(4 as u64)) + .saturating_add(T::DbWeight::get().writes(3 as u64)) + } + // Storage: `Tokens::Accounts` (r:1 w:1) + // Proof: `Tokens::Accounts` (`max_values`: None, `max_size`: Some(96), added: 2571, mode: `MaxEncodedLen`) + // Storage: `Tokens::TotalIssuance` (r:1 w:1) + // Proof: `Tokens::TotalIssuance` (`max_values`: None, `max_size`: Some(28), added: 2503, mode: `MaxEncodedLen`) + fn set_balance() -> Weight { + (Weight::from_parts(24_150_000, 0)) + .saturating_add(T::DbWeight::get().reads(2 as u64)) + .saturating_add(T::DbWeight::get().writes(2 as u64)) + } + // Storage: `Tokens::NextCurrencyId` (r:1 w:1) + // Proof: `Tokens::NextCurrencyId` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) + // Storage: `Tokens::Accounts` (r:1 w:1) + // Proof: `Tokens::Accounts` (`max_values`: None, `max_size`: Some(96), added: 2571, mode: `MaxEncodedLen`) + // Storage: `Tokens::TotalIssuance` (r:1 w:1) + // Proof: `Tokens::TotalIssuance` (`max_values`: None, `max_size`: Some(28), added: 2503, mode: `MaxEncodedLen`) + // Storage: `System::Account` (r:1 w:1) + // Proof: `System::Account` (`max_values`: None, `max_size`: Some(52), added: 2527, mode: `MaxEncodedLen`) + fn create() -> Weight { + (Weight::from_parts(43_090_000, 0)) + .saturating_add(T::DbWeight::get().reads(4 as u64)) + .saturating_add(T::DbWeight::get().writes(4 as u64)) + } + // Storage: `Tokens::NextCurrencyId` (r:1 w:0) + // Proof: `Tokens::NextCurrencyId` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) + // Storage: `Tokens::Accounts` (r:1 w:1) + // Proof: `Tokens::Accounts` (`max_values`: None, `max_size`: Some(96), added: 2571, mode: `MaxEncodedLen`) + // Storage: `Tokens::TotalIssuance` (r:1 w:1) + // Proof: `Tokens::TotalIssuance` (`max_values`: None, `max_size`: Some(28), added: 2503, mode: `MaxEncodedLen`) + // Storage: `System::Account` (r:1 w:1) + // Proof: `System::Account` (`max_values`: None, `max_size`: Some(52), added: 2527, mode: `MaxEncodedLen`) + fn mint() -> Weight { + (Weight::from_parts(42_870_000, 0)) + .saturating_add(T::DbWeight::get().reads(4 as u64)) + .saturating_add(T::DbWeight::get().writes(3 as u64)) + } +} + +// For backwards compatibility and tests +impl WeightInfo for () { + // Storage: `Tokens::Accounts` (r:2 w:2) + // Proof: `Tokens::Accounts` (`max_values`: None, `max_size`: Some(96), added: 2571, mode: `MaxEncodedLen`) + // Storage: `System::Account` (r:1 w:1) + // Proof: `System::Account` (`max_values`: None, `max_size`: Some(52), added: 2527, mode: `MaxEncodedLen`) + fn transfer() -> Weight { + (Weight::from_parts(43_930_000, 0)) + .saturating_add(RocksDbWeight::get().reads(3 as u64)) + .saturating_add(RocksDbWeight::get().writes(3 as u64)) + } + // Storage: `Tokens::Accounts` (r:2 w:2) + // Proof: `Tokens::Accounts` (`max_values`: None, `max_size`: Some(96), added: 2571, mode: `MaxEncodedLen`) + // Storage: `System::Account` (r:1 w:1) + // Proof: `System::Account` (`max_values`: None, `max_size`: Some(52), added: 2527, mode: `MaxEncodedLen`) + fn transfer_all() -> Weight { + (Weight::from_parts(45_840_000, 0)) + .saturating_add(RocksDbWeight::get().reads(3 as u64)) + .saturating_add(RocksDbWeight::get().writes(3 as u64)) + } + // Storage: `Tokens::Accounts` (r:2 w:2) + // Proof: `Tokens::Accounts` (`max_values`: None, `max_size`: Some(96), added: 2571, mode: `MaxEncodedLen`) + // Storage: `System::Account` (r:1 w:1) + // Proof: `System::Account` (`max_values`: None, `max_size`: Some(52), added: 2527, mode: `MaxEncodedLen`) + fn transfer_keep_alive() -> Weight { + (Weight::from_parts(41_180_000, 0)) + .saturating_add(RocksDbWeight::get().reads(3 as u64)) + .saturating_add(RocksDbWeight::get().writes(3 as u64)) + } + // Storage: `Tokens::Accounts` (r:2 w:2) + // Proof: `Tokens::Accounts` (`max_values`: None, `max_size`: Some(96), added: 2571, mode: `MaxEncodedLen`) + // Storage: `System::Account` (r:2 w:1) + // Proof: `System::Account` (`max_values`: None, `max_size`: Some(52), added: 2527, mode: `MaxEncodedLen`) + fn force_transfer() -> Weight { + (Weight::from_parts(45_650_000, 0)) + .saturating_add(RocksDbWeight::get().reads(4 as u64)) + .saturating_add(RocksDbWeight::get().writes(3 as u64)) + } + // Storage: `Tokens::Accounts` (r:1 w:1) + // Proof: `Tokens::Accounts` (`max_values`: None, `max_size`: Some(96), added: 2571, mode: `MaxEncodedLen`) + // Storage: `Tokens::TotalIssuance` (r:1 w:1) + // Proof: `Tokens::TotalIssuance` (`max_values`: None, `max_size`: Some(28), added: 2503, mode: `MaxEncodedLen`) + fn set_balance() -> Weight { + (Weight::from_parts(24_150_000, 0)) + .saturating_add(RocksDbWeight::get().reads(2 as u64)) + .saturating_add(RocksDbWeight::get().writes(2 as u64)) + } + // Storage: `Tokens::NextCurrencyId` (r:1 w:1) + // Proof: `Tokens::NextCurrencyId` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) + // Storage: `Tokens::Accounts` (r:1 w:1) + // Proof: `Tokens::Accounts` (`max_values`: None, `max_size`: Some(96), added: 2571, mode: `MaxEncodedLen`) + // Storage: `Tokens::TotalIssuance` (r:1 w:1) + // Proof: `Tokens::TotalIssuance` (`max_values`: None, `max_size`: Some(28), added: 2503, mode: `MaxEncodedLen`) + // Storage: `System::Account` (r:1 w:1) + // Proof: `System::Account` (`max_values`: None, `max_size`: Some(52), added: 2527, mode: `MaxEncodedLen`) + fn create() -> Weight { + (Weight::from_parts(43_090_000, 0)) + .saturating_add(RocksDbWeight::get().reads(4 as u64)) + .saturating_add(RocksDbWeight::get().writes(4 as u64)) + } + // Storage: `Tokens::NextCurrencyId` (r:1 w:0) + // Proof: `Tokens::NextCurrencyId` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) + // Storage: `Tokens::Accounts` (r:1 w:1) + // Proof: `Tokens::Accounts` (`max_values`: None, `max_size`: Some(96), added: 2571, mode: `MaxEncodedLen`) + // Storage: `Tokens::TotalIssuance` (r:1 w:1) + // Proof: `Tokens::TotalIssuance` (`max_values`: None, `max_size`: Some(28), added: 2503, mode: `MaxEncodedLen`) + // Storage: `System::Account` (r:1 w:1) + // Proof: `System::Account` (`max_values`: None, `max_size`: Some(52), added: 2527, mode: `MaxEncodedLen`) + fn mint() -> Weight { + (Weight::from_parts(42_870_000, 0)) + .saturating_add(RocksDbWeight::get().reads(4 as u64)) + .saturating_add(RocksDbWeight::get().writes(3 as u64)) + } +} diff --git a/gasp-node/rollup/runtime/src/weights/pallet_bootstrap.rs b/gasp-node/rollup/runtime/src/weights/pallet_bootstrap.rs new file mode 100644 index 000000000..e5d8f451c --- /dev/null +++ b/gasp-node/rollup/runtime/src/weights/pallet_bootstrap.rs @@ -0,0 +1,282 @@ +// This file is part of Mangata. + +// Copyright (C) 2020-2022 Mangata Foundation. +// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 + +// This program 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. + +// This program 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 this program. If not, see . + +//! Autogenerated weights for pallet_bootstrap +//! +//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 32.0.0 +//! DATE: 2024-11-29, STEPS: `50`, REPEAT: 20, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! EXECUTION: , WASM-EXECUTION: Compiled, CHAIN: Some("rollup-local"), DB CACHE: 1024 + +// Executed Command: +// target/release/rollup-node +// benchmark +// pallet +// -l=info,runtime::collective=warn,xyk=warn +// --chain +// rollup-local +// --wasm-execution +// compiled +// --pallet +// * +// --extrinsic +// * +// --steps +// 50 +// --repeat +// 20 +// --template +// ./templates/module-weight-template.hbs +// --output +// ./benchmarks/ + +#![cfg_attr(rustfmt, rustfmt_skip)] +#![allow(unused_parens)] +#![allow(unused_imports)] +#![allow(clippy::unnecessary_cast)] + +use frame_support::{traits::Get, weights::{Weight, constants::RocksDbWeight}}; +use sp_std::marker::PhantomData; + +/// Weight functions needed for pallet_bootstrap. +pub trait WeightInfo { + fn schedule_bootstrap() -> Weight; + fn provision() -> Weight; + fn claim_and_activate_liquidity_tokens() -> Weight; + fn finalize() -> Weight; +} + +/// Weights for pallet_bootstrap using the Mangata node and recommended hardware. +pub struct ModuleWeight(PhantomData); +impl pallet_bootstrap::WeightInfo for ModuleWeight { + // Storage: `Bootstrap::Phase` (r:1 w:0) + // Proof: `Bootstrap::Phase` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + // Storage: `Bootstrap::BootstrapSchedule` (r:1 w:1) + // Proof: `Bootstrap::BootstrapSchedule` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + // Storage: `Tokens::NextCurrencyId` (r:1 w:0) + // Proof: `Tokens::NextCurrencyId` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) + // Storage: `Xyk::Pools` (r:2 w:0) + // Proof: `Xyk::Pools` (`max_values`: None, `max_size`: Some(56), added: 2531, mode: `MaxEncodedLen`) + // Storage: `Bootstrap::PromoteBootstrapPool` (r:0 w:1) + // Proof: `Bootstrap::PromoteBootstrapPool` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + // Storage: `Bootstrap::ActivePair` (r:0 w:1) + // Proof: `Bootstrap::ActivePair` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + fn schedule_bootstrap() -> Weight { + (Weight::from_parts(25_700_000, 0)) + .saturating_add(T::DbWeight::get().reads(5 as u64)) + .saturating_add(T::DbWeight::get().writes(3 as u64)) + } + // Storage: `Maintenance::MaintenanceStatus` (r:1 w:0) + // Proof: `Maintenance::MaintenanceStatus` (`max_values`: Some(1), `max_size`: Some(2), added: 497, mode: `MaxEncodedLen`) + // Storage: `Bootstrap::ActivePair` (r:1 w:0) + // Proof: `Bootstrap::ActivePair` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + // Storage: `Bootstrap::Phase` (r:1 w:0) + // Proof: `Bootstrap::Phase` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + // Storage: `Bootstrap::WhitelistedAccount` (r:1 w:0) + // Proof: `Bootstrap::WhitelistedAccount` (`max_values`: None, `max_size`: None, mode: `Measured`) + // Storage: `Bootstrap::BootstrapSchedule` (r:1 w:0) + // Proof: `Bootstrap::BootstrapSchedule` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + // Storage: `Tokens::Accounts` (r:2 w:2) + // Proof: `Tokens::Accounts` (`max_values`: None, `max_size`: Some(96), added: 2571, mode: `MaxEncodedLen`) + // Storage: `System::Account` (r:1 w:1) + // Proof: `System::Account` (`max_values`: None, `max_size`: Some(52), added: 2527, mode: `MaxEncodedLen`) + // Storage: `Bootstrap::Provisions` (r:1 w:1) + // Proof: `Bootstrap::Provisions` (`max_values`: None, `max_size`: None, mode: `Measured`) + // Storage: `Bootstrap::Valuations` (r:1 w:1) + // Proof: `Bootstrap::Valuations` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + // Storage: `Bootstrap::ProvisionAccounts` (r:0 w:1) + // Proof: `Bootstrap::ProvisionAccounts` (`max_values`: None, `max_size`: None, mode: `Measured`) + fn provision() -> Weight { + (Weight::from_parts(80_350_000, 0)) + .saturating_add(T::DbWeight::get().reads(10 as u64)) + .saturating_add(T::DbWeight::get().writes(6 as u64)) + } + // Storage: `Bootstrap::Phase` (r:1 w:0) + // Proof: `Bootstrap::Phase` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + // Storage: `Bootstrap::MintedLiquidity` (r:1 w:0) + // Proof: `Bootstrap::MintedLiquidity` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + // Storage: `Bootstrap::ArchivedBootstrap` (r:1 w:0) + // Proof: `Bootstrap::ArchivedBootstrap` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + // Storage: `Bootstrap::ActivePair` (r:1 w:0) + // Proof: `Bootstrap::ActivePair` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + // Storage: `Bootstrap::ClaimedRewards` (r:2 w:2) + // Proof: `Bootstrap::ClaimedRewards` (`max_values`: None, `max_size`: None, mode: `Measured`) + // Storage: `Bootstrap::Valuations` (r:1 w:0) + // Proof: `Bootstrap::Valuations` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + // Storage: `Bootstrap::Provisions` (r:2 w:0) + // Proof: `Bootstrap::Provisions` (`max_values`: None, `max_size`: None, mode: `Measured`) + // Storage: `Bootstrap::VestedProvisions` (r:2 w:0) + // Proof: `Bootstrap::VestedProvisions` (`max_values`: None, `max_size`: None, mode: `Measured`) + // Storage: `Tokens::Accounts` (r:2 w:2) + // Proof: `Tokens::Accounts` (`max_values`: None, `max_size`: Some(96), added: 2571, mode: `MaxEncodedLen`) + // Storage: `ProofOfStake::PromotedPoolRewards` (r:1 w:0) + // Proof: `ProofOfStake::PromotedPoolRewards` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + // Storage: `MultiPurposeLiquidity::ReserveStatus` (r:1 w:1) + // Proof: `MultiPurposeLiquidity::ReserveStatus` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`) + // Storage: `ProofOfStake::RewardsInfo` (r:1 w:1) + // Proof: `ProofOfStake::RewardsInfo` (`max_values`: None, `max_size`: None, mode: `Measured`) + // Storage: `ProofOfStake::TotalActivatedLiquidity` (r:1 w:1) + // Proof: `ProofOfStake::TotalActivatedLiquidity` (`max_values`: None, `max_size`: None, mode: `Measured`) + // Storage: `Bootstrap::ProvisionAccounts` (r:0 w:1) + // Proof: `Bootstrap::ProvisionAccounts` (`max_values`: None, `max_size`: None, mode: `Measured`) + fn claim_and_activate_liquidity_tokens() -> Weight { + (Weight::from_parts(184_391_000, 0)) + .saturating_add(T::DbWeight::get().reads(17 as u64)) + .saturating_add(T::DbWeight::get().writes(8 as u64)) + } + // Storage: `Bootstrap::Phase` (r:1 w:1) + // Proof: `Bootstrap::Phase` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + // Storage: `Bootstrap::ProvisionAccounts` (r:1 w:0) + // Proof: `Bootstrap::ProvisionAccounts` (`max_values`: None, `max_size`: None, mode: `Measured`) + // Storage: `Bootstrap::VestedProvisions` (r:1 w:0) + // Proof: `Bootstrap::VestedProvisions` (`max_values`: None, `max_size`: None, mode: `Measured`) + // Storage: `Bootstrap::WhitelistedAccount` (r:1 w:0) + // Proof: `Bootstrap::WhitelistedAccount` (`max_values`: None, `max_size`: None, mode: `Measured`) + // Storage: `Bootstrap::ClaimedRewards` (r:1 w:0) + // Proof: `Bootstrap::ClaimedRewards` (`max_values`: None, `max_size`: None, mode: `Measured`) + // Storage: `Bootstrap::Provisions` (r:1 w:0) + // Proof: `Bootstrap::Provisions` (`max_values`: None, `max_size`: None, mode: `Measured`) + // Storage: `Bootstrap::MintedLiquidity` (r:1 w:1) + // Proof: `Bootstrap::MintedLiquidity` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + // Storage: `Tokens::Accounts` (r:1 w:0) + // Proof: `Tokens::Accounts` (`max_values`: None, `max_size`: Some(96), added: 2571, mode: `MaxEncodedLen`) + // Storage: `Bootstrap::BootstrapSchedule` (r:1 w:1) + // Proof: `Bootstrap::BootstrapSchedule` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + // Storage: `Bootstrap::ArchivedBootstrap` (r:1 w:1) + // Proof: `Bootstrap::ArchivedBootstrap` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + // Storage: `Bootstrap::Valuations` (r:0 w:1) + // Proof: `Bootstrap::Valuations` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + // Storage: `Bootstrap::PromoteBootstrapPool` (r:0 w:1) + // Proof: `Bootstrap::PromoteBootstrapPool` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + // Storage: `Bootstrap::ActivePair` (r:0 w:1) + // Proof: `Bootstrap::ActivePair` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + fn finalize() -> Weight { + (Weight::from_parts(64_060_000, 0)) + .saturating_add(T::DbWeight::get().reads(10 as u64)) + .saturating_add(T::DbWeight::get().writes(7 as u64)) + } +} + +// For backwards compatibility and tests +impl WeightInfo for () { + // Storage: `Bootstrap::Phase` (r:1 w:0) + // Proof: `Bootstrap::Phase` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + // Storage: `Bootstrap::BootstrapSchedule` (r:1 w:1) + // Proof: `Bootstrap::BootstrapSchedule` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + // Storage: `Tokens::NextCurrencyId` (r:1 w:0) + // Proof: `Tokens::NextCurrencyId` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) + // Storage: `Xyk::Pools` (r:2 w:0) + // Proof: `Xyk::Pools` (`max_values`: None, `max_size`: Some(56), added: 2531, mode: `MaxEncodedLen`) + // Storage: `Bootstrap::PromoteBootstrapPool` (r:0 w:1) + // Proof: `Bootstrap::PromoteBootstrapPool` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + // Storage: `Bootstrap::ActivePair` (r:0 w:1) + // Proof: `Bootstrap::ActivePair` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + fn schedule_bootstrap() -> Weight { + (Weight::from_parts(25_700_000, 0)) + .saturating_add(RocksDbWeight::get().reads(5 as u64)) + .saturating_add(RocksDbWeight::get().writes(3 as u64)) + } + // Storage: `Maintenance::MaintenanceStatus` (r:1 w:0) + // Proof: `Maintenance::MaintenanceStatus` (`max_values`: Some(1), `max_size`: Some(2), added: 497, mode: `MaxEncodedLen`) + // Storage: `Bootstrap::ActivePair` (r:1 w:0) + // Proof: `Bootstrap::ActivePair` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + // Storage: `Bootstrap::Phase` (r:1 w:0) + // Proof: `Bootstrap::Phase` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + // Storage: `Bootstrap::WhitelistedAccount` (r:1 w:0) + // Proof: `Bootstrap::WhitelistedAccount` (`max_values`: None, `max_size`: None, mode: `Measured`) + // Storage: `Bootstrap::BootstrapSchedule` (r:1 w:0) + // Proof: `Bootstrap::BootstrapSchedule` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + // Storage: `Tokens::Accounts` (r:2 w:2) + // Proof: `Tokens::Accounts` (`max_values`: None, `max_size`: Some(96), added: 2571, mode: `MaxEncodedLen`) + // Storage: `System::Account` (r:1 w:1) + // Proof: `System::Account` (`max_values`: None, `max_size`: Some(52), added: 2527, mode: `MaxEncodedLen`) + // Storage: `Bootstrap::Provisions` (r:1 w:1) + // Proof: `Bootstrap::Provisions` (`max_values`: None, `max_size`: None, mode: `Measured`) + // Storage: `Bootstrap::Valuations` (r:1 w:1) + // Proof: `Bootstrap::Valuations` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + // Storage: `Bootstrap::ProvisionAccounts` (r:0 w:1) + // Proof: `Bootstrap::ProvisionAccounts` (`max_values`: None, `max_size`: None, mode: `Measured`) + fn provision() -> Weight { + (Weight::from_parts(80_350_000, 0)) + .saturating_add(RocksDbWeight::get().reads(10 as u64)) + .saturating_add(RocksDbWeight::get().writes(6 as u64)) + } + // Storage: `Bootstrap::Phase` (r:1 w:0) + // Proof: `Bootstrap::Phase` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + // Storage: `Bootstrap::MintedLiquidity` (r:1 w:0) + // Proof: `Bootstrap::MintedLiquidity` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + // Storage: `Bootstrap::ArchivedBootstrap` (r:1 w:0) + // Proof: `Bootstrap::ArchivedBootstrap` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + // Storage: `Bootstrap::ActivePair` (r:1 w:0) + // Proof: `Bootstrap::ActivePair` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + // Storage: `Bootstrap::ClaimedRewards` (r:2 w:2) + // Proof: `Bootstrap::ClaimedRewards` (`max_values`: None, `max_size`: None, mode: `Measured`) + // Storage: `Bootstrap::Valuations` (r:1 w:0) + // Proof: `Bootstrap::Valuations` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + // Storage: `Bootstrap::Provisions` (r:2 w:0) + // Proof: `Bootstrap::Provisions` (`max_values`: None, `max_size`: None, mode: `Measured`) + // Storage: `Bootstrap::VestedProvisions` (r:2 w:0) + // Proof: `Bootstrap::VestedProvisions` (`max_values`: None, `max_size`: None, mode: `Measured`) + // Storage: `Tokens::Accounts` (r:2 w:2) + // Proof: `Tokens::Accounts` (`max_values`: None, `max_size`: Some(96), added: 2571, mode: `MaxEncodedLen`) + // Storage: `ProofOfStake::PromotedPoolRewards` (r:1 w:0) + // Proof: `ProofOfStake::PromotedPoolRewards` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + // Storage: `MultiPurposeLiquidity::ReserveStatus` (r:1 w:1) + // Proof: `MultiPurposeLiquidity::ReserveStatus` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`) + // Storage: `ProofOfStake::RewardsInfo` (r:1 w:1) + // Proof: `ProofOfStake::RewardsInfo` (`max_values`: None, `max_size`: None, mode: `Measured`) + // Storage: `ProofOfStake::TotalActivatedLiquidity` (r:1 w:1) + // Proof: `ProofOfStake::TotalActivatedLiquidity` (`max_values`: None, `max_size`: None, mode: `Measured`) + // Storage: `Bootstrap::ProvisionAccounts` (r:0 w:1) + // Proof: `Bootstrap::ProvisionAccounts` (`max_values`: None, `max_size`: None, mode: `Measured`) + fn claim_and_activate_liquidity_tokens() -> Weight { + (Weight::from_parts(184_391_000, 0)) + .saturating_add(RocksDbWeight::get().reads(17 as u64)) + .saturating_add(RocksDbWeight::get().writes(8 as u64)) + } + // Storage: `Bootstrap::Phase` (r:1 w:1) + // Proof: `Bootstrap::Phase` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + // Storage: `Bootstrap::ProvisionAccounts` (r:1 w:0) + // Proof: `Bootstrap::ProvisionAccounts` (`max_values`: None, `max_size`: None, mode: `Measured`) + // Storage: `Bootstrap::VestedProvisions` (r:1 w:0) + // Proof: `Bootstrap::VestedProvisions` (`max_values`: None, `max_size`: None, mode: `Measured`) + // Storage: `Bootstrap::WhitelistedAccount` (r:1 w:0) + // Proof: `Bootstrap::WhitelistedAccount` (`max_values`: None, `max_size`: None, mode: `Measured`) + // Storage: `Bootstrap::ClaimedRewards` (r:1 w:0) + // Proof: `Bootstrap::ClaimedRewards` (`max_values`: None, `max_size`: None, mode: `Measured`) + // Storage: `Bootstrap::Provisions` (r:1 w:0) + // Proof: `Bootstrap::Provisions` (`max_values`: None, `max_size`: None, mode: `Measured`) + // Storage: `Bootstrap::MintedLiquidity` (r:1 w:1) + // Proof: `Bootstrap::MintedLiquidity` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + // Storage: `Tokens::Accounts` (r:1 w:0) + // Proof: `Tokens::Accounts` (`max_values`: None, `max_size`: Some(96), added: 2571, mode: `MaxEncodedLen`) + // Storage: `Bootstrap::BootstrapSchedule` (r:1 w:1) + // Proof: `Bootstrap::BootstrapSchedule` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + // Storage: `Bootstrap::ArchivedBootstrap` (r:1 w:1) + // Proof: `Bootstrap::ArchivedBootstrap` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + // Storage: `Bootstrap::Valuations` (r:0 w:1) + // Proof: `Bootstrap::Valuations` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + // Storage: `Bootstrap::PromoteBootstrapPool` (r:0 w:1) + // Proof: `Bootstrap::PromoteBootstrapPool` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + // Storage: `Bootstrap::ActivePair` (r:0 w:1) + // Proof: `Bootstrap::ActivePair` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + fn finalize() -> Weight { + (Weight::from_parts(64_060_000, 0)) + .saturating_add(RocksDbWeight::get().reads(10 as u64)) + .saturating_add(RocksDbWeight::get().writes(7 as u64)) + } +} diff --git a/gasp-node/rollup/runtime/src/weights/pallet_collective_mangata.rs b/gasp-node/rollup/runtime/src/weights/pallet_collective_mangata.rs new file mode 100644 index 000000000..58b0e0667 --- /dev/null +++ b/gasp-node/rollup/runtime/src/weights/pallet_collective_mangata.rs @@ -0,0 +1,432 @@ +// This file is part of Mangata. + +// Copyright (C) 2020-2022 Mangata Foundation. +// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 + +// This program 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. + +// This program 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 this program. If not, see . + +//! Autogenerated weights for pallet_collective_mangata +//! +//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 32.0.0 +//! DATE: 2024-11-29, STEPS: `50`, REPEAT: 20, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! EXECUTION: , WASM-EXECUTION: Compiled, CHAIN: Some("rollup-local"), DB CACHE: 1024 + +// Executed Command: +// target/release/rollup-node +// benchmark +// pallet +// -l=info,runtime::collective=warn,xyk=warn +// --chain +// rollup-local +// --wasm-execution +// compiled +// --pallet +// * +// --extrinsic +// * +// --steps +// 50 +// --repeat +// 20 +// --template +// ./templates/module-weight-template.hbs +// --output +// ./benchmarks/ + +#![cfg_attr(rustfmt, rustfmt_skip)] +#![allow(unused_parens)] +#![allow(unused_imports)] +#![allow(clippy::unnecessary_cast)] + +use frame_support::{traits::Get, weights::{Weight, constants::RocksDbWeight}}; +use sp_std::marker::PhantomData; + +/// Weight functions needed for pallet_collective_mangata. +pub trait WeightInfo { + fn set_members(m: u32, n: u32, p: u32, ) -> Weight; + fn execute(b: u32, m: u32, ) -> Weight; + fn propose_execute(b: u32, m: u32, ) -> Weight; + fn propose_proposed(b: u32, m: u32, p: u32, ) -> Weight; + fn vote(m: u32, ) -> Weight; + fn close_early_disapproved(m: u32, p: u32, ) -> Weight; + fn close_early_approved(b: u32, m: u32, p: u32, ) -> Weight; + fn close_disapproved(m: u32, p: u32, ) -> Weight; + fn close_approved(b: u32, m: u32, p: u32, ) -> Weight; + fn disapprove_proposal(p: u32, ) -> Weight; +} + +/// Weights for pallet_collective_mangata using the Mangata node and recommended hardware. +pub struct ModuleWeight(PhantomData); +impl pallet_collective_mangata::WeightInfo for ModuleWeight { + // Storage: `Council::Members` (r:1 w:1) + // Proof: `Council::Members` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + // Storage: `Council::Proposals` (r:1 w:0) + // Proof: `Council::Proposals` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + // Storage: `Council::Voting` (r:100 w:100) + // Proof: `Council::Voting` (`max_values`: None, `max_size`: None, mode: `Measured`) + // Storage: `Council::Prime` (r:0 w:1) + // Proof: `Council::Prime` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + fn set_members(m: u32, _n: u32, p: u32, ) -> Weight { + (Weight::from_parts(148_080_000, 0)) + // Standard Error: 70_056 + .saturating_add((Weight::from_parts(4_005_859, 0)).saturating_mul(m as u64)) + // Standard Error: 70_056 + .saturating_add((Weight::from_parts(8_923_337, 0)).saturating_mul(p as u64)) + .saturating_add(T::DbWeight::get().reads(2 as u64)) + .saturating_add(T::DbWeight::get().reads((1 as u64).saturating_mul(p as u64))) + .saturating_add(T::DbWeight::get().writes(2 as u64)) + .saturating_add(T::DbWeight::get().writes((1 as u64).saturating_mul(p as u64))) + } + // Storage: `Council::Members` (r:1 w:0) + // Proof: `Council::Members` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + fn execute(b: u32, m: u32, ) -> Weight { + (Weight::from_parts(24_704_254, 0)) + // Standard Error: 118 + .saturating_add((Weight::from_parts(2_079, 0)).saturating_mul(b as u64)) + // Standard Error: 1_220 + .saturating_add((Weight::from_parts(22_735, 0)).saturating_mul(m as u64)) + .saturating_add(T::DbWeight::get().reads(1 as u64)) + } + // Storage: `Council::Members` (r:1 w:0) + // Proof: `Council::Members` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + // Storage: `Council::ProposalOf` (r:1 w:0) + // Proof: `Council::ProposalOf` (`max_values`: None, `max_size`: None, mode: `Measured`) + fn propose_execute(b: u32, m: u32, ) -> Weight { + (Weight::from_parts(27_339_153, 0)) + // Standard Error: 134 + .saturating_add((Weight::from_parts(2_475, 0)).saturating_mul(b as u64)) + // Standard Error: 1_384 + .saturating_add((Weight::from_parts(36_408, 0)).saturating_mul(m as u64)) + .saturating_add(T::DbWeight::get().reads(2 as u64)) + } + // Storage: `Council::Members` (r:1 w:0) + // Proof: `Council::Members` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + // Storage: `Council::ProposalOf` (r:1 w:1) + // Proof: `Council::ProposalOf` (`max_values`: None, `max_size`: None, mode: `Measured`) + // Storage: `Council::Proposals` (r:1 w:1) + // Proof: `Council::Proposals` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + // Storage: `Council::ProposalCount` (r:1 w:1) + // Proof: `Council::ProposalCount` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + // Storage: `Council::ProposalProposedTime` (r:0 w:1) + // Proof: `Council::ProposalProposedTime` (`max_values`: None, `max_size`: None, mode: `Measured`) + // Storage: `Council::Voting` (r:0 w:1) + // Proof: `Council::Voting` (`max_values`: None, `max_size`: None, mode: `Measured`) + fn propose_proposed(b: u32, m: u32, p: u32, ) -> Weight { + (Weight::from_parts(41_070_816, 0)) + // Standard Error: 232 + .saturating_add((Weight::from_parts(3_561, 0)).saturating_mul(b as u64)) + // Standard Error: 2_427 + .saturating_add((Weight::from_parts(21_020, 0)).saturating_mul(m as u64)) + // Standard Error: 2_396 + .saturating_add((Weight::from_parts(324_501, 0)).saturating_mul(p as u64)) + .saturating_add(T::DbWeight::get().reads(4 as u64)) + .saturating_add(T::DbWeight::get().writes(5 as u64)) + } + // Storage: `Council::Members` (r:1 w:0) + // Proof: `Council::Members` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + // Storage: `Council::Voting` (r:1 w:1) + // Proof: `Council::Voting` (`max_values`: None, `max_size`: None, mode: `Measured`) + fn vote(m: u32, ) -> Weight { + (Weight::from_parts(41_208_337, 0)) + // Standard Error: 1_645 + .saturating_add((Weight::from_parts(61_793, 0)).saturating_mul(m as u64)) + .saturating_add(T::DbWeight::get().reads(2 as u64)) + .saturating_add(T::DbWeight::get().writes(1 as u64)) + } + // Storage: `Council::Members` (r:1 w:0) + // Proof: `Council::Members` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + // Storage: `Council::Voting` (r:1 w:1) + // Proof: `Council::Voting` (`max_values`: None, `max_size`: None, mode: `Measured`) + // Storage: `Council::ProposalProposedTime` (r:1 w:1) + // Proof: `Council::ProposalProposedTime` (`max_values`: None, `max_size`: None, mode: `Measured`) + // Storage: `FoundationMembers::Members` (r:1 w:0) + // Proof: `FoundationMembers::Members` (`max_values`: Some(1), `max_size`: Some(61), added: 556, mode: `MaxEncodedLen`) + // Storage: `Council::Proposals` (r:1 w:1) + // Proof: `Council::Proposals` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + // Storage: `Council::ProposalOf` (r:0 w:1) + // Proof: `Council::ProposalOf` (`max_values`: None, `max_size`: None, mode: `Measured`) + fn close_early_disapproved(m: u32, p: u32, ) -> Weight { + (Weight::from_parts(62_025_353, 0)) + // Standard Error: 3_748 + .saturating_add((Weight::from_parts(28_097, 0)).saturating_mul(m as u64)) + // Standard Error: 3_655 + .saturating_add((Weight::from_parts(336_610, 0)).saturating_mul(p as u64)) + .saturating_add(T::DbWeight::get().reads(5 as u64)) + .saturating_add(T::DbWeight::get().writes(4 as u64)) + } + // Storage: `Council::Members` (r:1 w:0) + // Proof: `Council::Members` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + // Storage: `Council::Voting` (r:1 w:1) + // Proof: `Council::Voting` (`max_values`: None, `max_size`: None, mode: `Measured`) + // Storage: `Council::ProposalProposedTime` (r:1 w:1) + // Proof: `Council::ProposalProposedTime` (`max_values`: None, `max_size`: None, mode: `Measured`) + // Storage: `FoundationMembers::Members` (r:1 w:0) + // Proof: `FoundationMembers::Members` (`max_values`: Some(1), `max_size`: Some(61), added: 556, mode: `MaxEncodedLen`) + // Storage: `Council::ProposalOf` (r:1 w:1) + // Proof: `Council::ProposalOf` (`max_values`: None, `max_size`: None, mode: `Measured`) + // Storage: `Council::Proposals` (r:1 w:1) + // Proof: `Council::Proposals` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + fn close_early_approved(b: u32, m: u32, p: u32, ) -> Weight { + (Weight::from_parts(80_472_792, 0)) + // Standard Error: 785 + .saturating_add((Weight::from_parts(3_376, 0)).saturating_mul(b as u64)) + // Standard Error: 8_300 + .saturating_add((Weight::from_parts(84_207, 0)).saturating_mul(m as u64)) + // Standard Error: 8_090 + .saturating_add((Weight::from_parts(373_260, 0)).saturating_mul(p as u64)) + .saturating_add(T::DbWeight::get().reads(6 as u64)) + .saturating_add(T::DbWeight::get().writes(4 as u64)) + } + // Storage: `Council::Members` (r:1 w:0) + // Proof: `Council::Members` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + // Storage: `Council::Voting` (r:1 w:1) + // Proof: `Council::Voting` (`max_values`: None, `max_size`: None, mode: `Measured`) + // Storage: `Council::ProposalProposedTime` (r:1 w:1) + // Proof: `Council::ProposalProposedTime` (`max_values`: None, `max_size`: None, mode: `Measured`) + // Storage: `Council::Prime` (r:1 w:0) + // Proof: `Council::Prime` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + // Storage: `Council::Proposals` (r:1 w:1) + // Proof: `Council::Proposals` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + // Storage: `Council::ProposalOf` (r:0 w:1) + // Proof: `Council::ProposalOf` (`max_values`: None, `max_size`: None, mode: `Measured`) + fn close_disapproved(m: u32, p: u32, ) -> Weight { + (Weight::from_parts(56_432_300, 0)) + // Standard Error: 4_211 + .saturating_add((Weight::from_parts(69_518, 0)).saturating_mul(m as u64)) + // Standard Error: 4_106 + .saturating_add((Weight::from_parts(338_049, 0)).saturating_mul(p as u64)) + .saturating_add(T::DbWeight::get().reads(5 as u64)) + .saturating_add(T::DbWeight::get().writes(4 as u64)) + } + // Storage: `Council::Members` (r:1 w:0) + // Proof: `Council::Members` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + // Storage: `Council::Voting` (r:1 w:1) + // Proof: `Council::Voting` (`max_values`: None, `max_size`: None, mode: `Measured`) + // Storage: `Council::ProposalProposedTime` (r:1 w:1) + // Proof: `Council::ProposalProposedTime` (`max_values`: None, `max_size`: None, mode: `Measured`) + // Storage: `Council::Prime` (r:1 w:0) + // Proof: `Council::Prime` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + // Storage: `Council::ProposalOf` (r:1 w:1) + // Proof: `Council::ProposalOf` (`max_values`: None, `max_size`: None, mode: `Measured`) + // Storage: `Council::Proposals` (r:1 w:1) + // Proof: `Council::Proposals` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + fn close_approved(b: u32, m: u32, p: u32, ) -> Weight { + (Weight::from_parts(81_511_415, 0)) + // Standard Error: 512 + .saturating_add((Weight::from_parts(3_154, 0)).saturating_mul(b as u64)) + // Standard Error: 5_417 + .saturating_add((Weight::from_parts(37_847, 0)).saturating_mul(m as u64)) + // Standard Error: 5_280 + .saturating_add((Weight::from_parts(347_334, 0)).saturating_mul(p as u64)) + .saturating_add(T::DbWeight::get().reads(6 as u64)) + .saturating_add(T::DbWeight::get().writes(4 as u64)) + } + // Storage: `Council::Proposals` (r:1 w:1) + // Proof: `Council::Proposals` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + // Storage: `Council::ProposalProposedTime` (r:0 w:1) + // Proof: `Council::ProposalProposedTime` (`max_values`: None, `max_size`: None, mode: `Measured`) + // Storage: `Council::Voting` (r:0 w:1) + // Proof: `Council::Voting` (`max_values`: None, `max_size`: None, mode: `Measured`) + // Storage: `Council::ProposalOf` (r:0 w:1) + // Proof: `Council::ProposalOf` (`max_values`: None, `max_size`: None, mode: `Measured`) + fn disapprove_proposal(p: u32, ) -> Weight { + (Weight::from_parts(29_340_936, 0)) + // Standard Error: 1_590 + .saturating_add((Weight::from_parts(274_517, 0)).saturating_mul(p as u64)) + .saturating_add(T::DbWeight::get().reads(1 as u64)) + .saturating_add(T::DbWeight::get().writes(4 as u64)) + } +} + +// For backwards compatibility and tests +impl WeightInfo for () { + // Storage: `Council::Members` (r:1 w:1) + // Proof: `Council::Members` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + // Storage: `Council::Proposals` (r:1 w:0) + // Proof: `Council::Proposals` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + // Storage: `Council::Voting` (r:100 w:100) + // Proof: `Council::Voting` (`max_values`: None, `max_size`: None, mode: `Measured`) + // Storage: `Council::Prime` (r:0 w:1) + // Proof: `Council::Prime` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + fn set_members(m: u32, _n: u32, p: u32, ) -> Weight { + (Weight::from_parts(148_080_000, 0)) + // Standard Error: 70_056 + .saturating_add((Weight::from_parts(4_005_859, 0)).saturating_mul(m as u64)) + // Standard Error: 70_056 + .saturating_add((Weight::from_parts(8_923_337, 0)).saturating_mul(p as u64)) + .saturating_add(RocksDbWeight::get().reads(2 as u64)) + .saturating_add(RocksDbWeight::get().reads((1 as u64).saturating_mul(p as u64))) + .saturating_add(RocksDbWeight::get().writes(2 as u64)) + .saturating_add(RocksDbWeight::get().writes((1 as u64).saturating_mul(p as u64))) + } + // Storage: `Council::Members` (r:1 w:0) + // Proof: `Council::Members` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + fn execute(b: u32, m: u32, ) -> Weight { + (Weight::from_parts(24_704_254, 0)) + // Standard Error: 118 + .saturating_add((Weight::from_parts(2_079, 0)).saturating_mul(b as u64)) + // Standard Error: 1_220 + .saturating_add((Weight::from_parts(22_735, 0)).saturating_mul(m as u64)) + .saturating_add(RocksDbWeight::get().reads(1 as u64)) + } + // Storage: `Council::Members` (r:1 w:0) + // Proof: `Council::Members` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + // Storage: `Council::ProposalOf` (r:1 w:0) + // Proof: `Council::ProposalOf` (`max_values`: None, `max_size`: None, mode: `Measured`) + fn propose_execute(b: u32, m: u32, ) -> Weight { + (Weight::from_parts(27_339_153, 0)) + // Standard Error: 134 + .saturating_add((Weight::from_parts(2_475, 0)).saturating_mul(b as u64)) + // Standard Error: 1_384 + .saturating_add((Weight::from_parts(36_408, 0)).saturating_mul(m as u64)) + .saturating_add(RocksDbWeight::get().reads(2 as u64)) + } + // Storage: `Council::Members` (r:1 w:0) + // Proof: `Council::Members` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + // Storage: `Council::ProposalOf` (r:1 w:1) + // Proof: `Council::ProposalOf` (`max_values`: None, `max_size`: None, mode: `Measured`) + // Storage: `Council::Proposals` (r:1 w:1) + // Proof: `Council::Proposals` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + // Storage: `Council::ProposalCount` (r:1 w:1) + // Proof: `Council::ProposalCount` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + // Storage: `Council::ProposalProposedTime` (r:0 w:1) + // Proof: `Council::ProposalProposedTime` (`max_values`: None, `max_size`: None, mode: `Measured`) + // Storage: `Council::Voting` (r:0 w:1) + // Proof: `Council::Voting` (`max_values`: None, `max_size`: None, mode: `Measured`) + fn propose_proposed(b: u32, m: u32, p: u32, ) -> Weight { + (Weight::from_parts(41_070_816, 0)) + // Standard Error: 232 + .saturating_add((Weight::from_parts(3_561, 0)).saturating_mul(b as u64)) + // Standard Error: 2_427 + .saturating_add((Weight::from_parts(21_020, 0)).saturating_mul(m as u64)) + // Standard Error: 2_396 + .saturating_add((Weight::from_parts(324_501, 0)).saturating_mul(p as u64)) + .saturating_add(RocksDbWeight::get().reads(4 as u64)) + .saturating_add(RocksDbWeight::get().writes(5 as u64)) + } + // Storage: `Council::Members` (r:1 w:0) + // Proof: `Council::Members` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + // Storage: `Council::Voting` (r:1 w:1) + // Proof: `Council::Voting` (`max_values`: None, `max_size`: None, mode: `Measured`) + fn vote(m: u32, ) -> Weight { + (Weight::from_parts(41_208_337, 0)) + // Standard Error: 1_645 + .saturating_add((Weight::from_parts(61_793, 0)).saturating_mul(m as u64)) + .saturating_add(RocksDbWeight::get().reads(2 as u64)) + .saturating_add(RocksDbWeight::get().writes(1 as u64)) + } + // Storage: `Council::Members` (r:1 w:0) + // Proof: `Council::Members` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + // Storage: `Council::Voting` (r:1 w:1) + // Proof: `Council::Voting` (`max_values`: None, `max_size`: None, mode: `Measured`) + // Storage: `Council::ProposalProposedTime` (r:1 w:1) + // Proof: `Council::ProposalProposedTime` (`max_values`: None, `max_size`: None, mode: `Measured`) + // Storage: `FoundationMembers::Members` (r:1 w:0) + // Proof: `FoundationMembers::Members` (`max_values`: Some(1), `max_size`: Some(61), added: 556, mode: `MaxEncodedLen`) + // Storage: `Council::Proposals` (r:1 w:1) + // Proof: `Council::Proposals` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + // Storage: `Council::ProposalOf` (r:0 w:1) + // Proof: `Council::ProposalOf` (`max_values`: None, `max_size`: None, mode: `Measured`) + fn close_early_disapproved(m: u32, p: u32, ) -> Weight { + (Weight::from_parts(62_025_353, 0)) + // Standard Error: 3_748 + .saturating_add((Weight::from_parts(28_097, 0)).saturating_mul(m as u64)) + // Standard Error: 3_655 + .saturating_add((Weight::from_parts(336_610, 0)).saturating_mul(p as u64)) + .saturating_add(RocksDbWeight::get().reads(5 as u64)) + .saturating_add(RocksDbWeight::get().writes(4 as u64)) + } + // Storage: `Council::Members` (r:1 w:0) + // Proof: `Council::Members` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + // Storage: `Council::Voting` (r:1 w:1) + // Proof: `Council::Voting` (`max_values`: None, `max_size`: None, mode: `Measured`) + // Storage: `Council::ProposalProposedTime` (r:1 w:1) + // Proof: `Council::ProposalProposedTime` (`max_values`: None, `max_size`: None, mode: `Measured`) + // Storage: `FoundationMembers::Members` (r:1 w:0) + // Proof: `FoundationMembers::Members` (`max_values`: Some(1), `max_size`: Some(61), added: 556, mode: `MaxEncodedLen`) + // Storage: `Council::ProposalOf` (r:1 w:1) + // Proof: `Council::ProposalOf` (`max_values`: None, `max_size`: None, mode: `Measured`) + // Storage: `Council::Proposals` (r:1 w:1) + // Proof: `Council::Proposals` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + fn close_early_approved(b: u32, m: u32, p: u32, ) -> Weight { + (Weight::from_parts(80_472_792, 0)) + // Standard Error: 785 + .saturating_add((Weight::from_parts(3_376, 0)).saturating_mul(b as u64)) + // Standard Error: 8_300 + .saturating_add((Weight::from_parts(84_207, 0)).saturating_mul(m as u64)) + // Standard Error: 8_090 + .saturating_add((Weight::from_parts(373_260, 0)).saturating_mul(p as u64)) + .saturating_add(RocksDbWeight::get().reads(6 as u64)) + .saturating_add(RocksDbWeight::get().writes(4 as u64)) + } + // Storage: `Council::Members` (r:1 w:0) + // Proof: `Council::Members` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + // Storage: `Council::Voting` (r:1 w:1) + // Proof: `Council::Voting` (`max_values`: None, `max_size`: None, mode: `Measured`) + // Storage: `Council::ProposalProposedTime` (r:1 w:1) + // Proof: `Council::ProposalProposedTime` (`max_values`: None, `max_size`: None, mode: `Measured`) + // Storage: `Council::Prime` (r:1 w:0) + // Proof: `Council::Prime` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + // Storage: `Council::Proposals` (r:1 w:1) + // Proof: `Council::Proposals` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + // Storage: `Council::ProposalOf` (r:0 w:1) + // Proof: `Council::ProposalOf` (`max_values`: None, `max_size`: None, mode: `Measured`) + fn close_disapproved(m: u32, p: u32, ) -> Weight { + (Weight::from_parts(56_432_300, 0)) + // Standard Error: 4_211 + .saturating_add((Weight::from_parts(69_518, 0)).saturating_mul(m as u64)) + // Standard Error: 4_106 + .saturating_add((Weight::from_parts(338_049, 0)).saturating_mul(p as u64)) + .saturating_add(RocksDbWeight::get().reads(5 as u64)) + .saturating_add(RocksDbWeight::get().writes(4 as u64)) + } + // Storage: `Council::Members` (r:1 w:0) + // Proof: `Council::Members` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + // Storage: `Council::Voting` (r:1 w:1) + // Proof: `Council::Voting` (`max_values`: None, `max_size`: None, mode: `Measured`) + // Storage: `Council::ProposalProposedTime` (r:1 w:1) + // Proof: `Council::ProposalProposedTime` (`max_values`: None, `max_size`: None, mode: `Measured`) + // Storage: `Council::Prime` (r:1 w:0) + // Proof: `Council::Prime` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + // Storage: `Council::ProposalOf` (r:1 w:1) + // Proof: `Council::ProposalOf` (`max_values`: None, `max_size`: None, mode: `Measured`) + // Storage: `Council::Proposals` (r:1 w:1) + // Proof: `Council::Proposals` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + fn close_approved(b: u32, m: u32, p: u32, ) -> Weight { + (Weight::from_parts(81_511_415, 0)) + // Standard Error: 512 + .saturating_add((Weight::from_parts(3_154, 0)).saturating_mul(b as u64)) + // Standard Error: 5_417 + .saturating_add((Weight::from_parts(37_847, 0)).saturating_mul(m as u64)) + // Standard Error: 5_280 + .saturating_add((Weight::from_parts(347_334, 0)).saturating_mul(p as u64)) + .saturating_add(RocksDbWeight::get().reads(6 as u64)) + .saturating_add(RocksDbWeight::get().writes(4 as u64)) + } + // Storage: `Council::Proposals` (r:1 w:1) + // Proof: `Council::Proposals` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + // Storage: `Council::ProposalProposedTime` (r:0 w:1) + // Proof: `Council::ProposalProposedTime` (`max_values`: None, `max_size`: None, mode: `Measured`) + // Storage: `Council::Voting` (r:0 w:1) + // Proof: `Council::Voting` (`max_values`: None, `max_size`: None, mode: `Measured`) + // Storage: `Council::ProposalOf` (r:0 w:1) + // Proof: `Council::ProposalOf` (`max_values`: None, `max_size`: None, mode: `Measured`) + fn disapprove_proposal(p: u32, ) -> Weight { + (Weight::from_parts(29_340_936, 0)) + // Standard Error: 1_590 + .saturating_add((Weight::from_parts(274_517, 0)).saturating_mul(p as u64)) + .saturating_add(RocksDbWeight::get().reads(1 as u64)) + .saturating_add(RocksDbWeight::get().writes(4 as u64)) + } +} diff --git a/gasp-node/rollup/runtime/src/weights/pallet_crowdloan_rewards.rs b/gasp-node/rollup/runtime/src/weights/pallet_crowdloan_rewards.rs new file mode 100644 index 000000000..2601a0565 --- /dev/null +++ b/gasp-node/rollup/runtime/src/weights/pallet_crowdloan_rewards.rs @@ -0,0 +1,299 @@ +// This file is part of Mangata. + +// Copyright (C) 2020-2022 Mangata Foundation. +// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 + +// This program 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. + +// This program 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 this program. If not, see . + +//! Autogenerated weights for pallet_crowdloan_rewards +//! +//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 32.0.0 +//! DATE: 2024-11-29, STEPS: `50`, REPEAT: 20, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! EXECUTION: , WASM-EXECUTION: Compiled, CHAIN: Some("rollup-local"), DB CACHE: 1024 + +// Executed Command: +// target/release/rollup-node +// benchmark +// pallet +// -l=info,runtime::collective=warn,xyk=warn +// --chain +// rollup-local +// --wasm-execution +// compiled +// --pallet +// * +// --extrinsic +// * +// --steps +// 50 +// --repeat +// 20 +// --template +// ./templates/module-weight-template.hbs +// --output +// ./benchmarks/ + +#![cfg_attr(rustfmt, rustfmt_skip)] +#![allow(unused_parens)] +#![allow(unused_imports)] +#![allow(clippy::unnecessary_cast)] + +use frame_support::{traits::Get, weights::{Weight, constants::RocksDbWeight}}; +use sp_std::marker::PhantomData; + +/// Weight functions needed for pallet_crowdloan_rewards. +pub trait WeightInfo { + fn set_crowdloan_allocation() -> Weight; + fn initialize_reward_vec(x: u32, ) -> Weight; + fn complete_initialization() -> Weight; + fn claim() -> Weight; + fn update_reward_address() -> Weight; + fn associate_native_identity() -> Weight; + fn change_association_with_relay_keys(x: u32, ) -> Weight; +} + +/// Weights for pallet_crowdloan_rewards using the Mangata node and recommended hardware. +pub struct ModuleWeight(PhantomData); +impl pallet_crowdloan_rewards::WeightInfo for ModuleWeight { + // Storage: `Crowdloan::Initialized` (r:1 w:0) + // Proof: `Crowdloan::Initialized` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + // Storage: `Crowdloan::CrowdloanId` (r:1 w:0) + // Proof: `Crowdloan::CrowdloanId` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + // Storage: `Crowdloan::InitializedRewardAmount` (r:1 w:0) + // Proof: `Crowdloan::InitializedRewardAmount` (`max_values`: None, `max_size`: None, mode: `Measured`) + // Storage: `Crowdloan::CrowdloanAllocation` (r:0 w:1) + // Proof: `Crowdloan::CrowdloanAllocation` (`max_values`: None, `max_size`: None, mode: `Measured`) + fn set_crowdloan_allocation() -> Weight { + (Weight::from_parts(11_700_000, 0)) + .saturating_add(T::DbWeight::get().reads(3 as u64)) + .saturating_add(T::DbWeight::get().writes(1 as u64)) + } + // Storage: `Crowdloan::Initialized` (r:1 w:0) + // Proof: `Crowdloan::Initialized` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + // Storage: `Crowdloan::CrowdloanId` (r:1 w:0) + // Proof: `Crowdloan::CrowdloanId` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + // Storage: `Crowdloan::InitializedRewardAmount` (r:1 w:1) + // Proof: `Crowdloan::InitializedRewardAmount` (`max_values`: None, `max_size`: None, mode: `Measured`) + // Storage: `Crowdloan::TotalContributors` (r:1 w:1) + // Proof: `Crowdloan::TotalContributors` (`max_values`: None, `max_size`: None, mode: `Measured`) + // Storage: `Crowdloan::CrowdloanAllocation` (r:1 w:0) + // Proof: `Crowdloan::CrowdloanAllocation` (`max_values`: None, `max_size`: None, mode: `Measured`) + // Storage: `Crowdloan::ClaimedRelayChainIds` (r:100 w:100) + // Proof: `Crowdloan::ClaimedRelayChainIds` (`max_values`: None, `max_size`: None, mode: `Measured`) + // Storage: `Crowdloan::UnassociatedContributions` (r:100 w:0) + // Proof: `Crowdloan::UnassociatedContributions` (`max_values`: None, `max_size`: None, mode: `Measured`) + // Storage: `Crowdloan::AccountsPayable` (r:100 w:100) + // Proof: `Crowdloan::AccountsPayable` (`max_values`: None, `max_size`: None, mode: `Measured`) + fn initialize_reward_vec(x: u32, ) -> Weight { + (Weight::from_parts(116_459_308, 0)) + // Standard Error: 36_116 + .saturating_add((Weight::from_parts(21_988_497, 0)).saturating_mul(x as u64)) + .saturating_add(T::DbWeight::get().reads(5 as u64)) + .saturating_add(T::DbWeight::get().reads((3 as u64).saturating_mul(x as u64))) + .saturating_add(T::DbWeight::get().writes(2 as u64)) + .saturating_add(T::DbWeight::get().writes((2 as u64).saturating_mul(x as u64))) + } + // Storage: `Crowdloan::Initialized` (r:1 w:1) + // Proof: `Crowdloan::Initialized` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + // Storage: `Crowdloan::CrowdloanId` (r:1 w:0) + // Proof: `Crowdloan::CrowdloanId` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + // Storage: `Crowdloan::InitializedRewardAmount` (r:1 w:0) + // Proof: `Crowdloan::InitializedRewardAmount` (`max_values`: None, `max_size`: None, mode: `Measured`) + // Storage: `Crowdloan::CrowdloanAllocation` (r:1 w:0) + // Proof: `Crowdloan::CrowdloanAllocation` (`max_values`: None, `max_size`: None, mode: `Measured`) + // Storage: `Crowdloan::TotalContributors` (r:1 w:0) + // Proof: `Crowdloan::TotalContributors` (`max_values`: None, `max_size`: None, mode: `Measured`) + // Storage: `Crowdloan::CrowdloanPeriod` (r:0 w:1) + // Proof: `Crowdloan::CrowdloanPeriod` (`max_values`: None, `max_size`: None, mode: `Measured`) + fn complete_initialization() -> Weight { + (Weight::from_parts(24_320_000, 0)) + .saturating_add(T::DbWeight::get().reads(5 as u64)) + .saturating_add(T::DbWeight::get().writes(2 as u64)) + } + // Storage: `Crowdloan::CrowdloanId` (r:1 w:0) + // Proof: `Crowdloan::CrowdloanId` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + // Storage: `Crowdloan::Initialized` (r:1 w:0) + // Proof: `Crowdloan::Initialized` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + // Storage: `Crowdloan::AccountsPayable` (r:1 w:1) + // Proof: `Crowdloan::AccountsPayable` (`max_values`: None, `max_size`: None, mode: `Measured`) + // Storage: `Tokens::NextCurrencyId` (r:1 w:0) + // Proof: `Tokens::NextCurrencyId` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) + // Storage: `Tokens::Accounts` (r:1 w:1) + // Proof: `Tokens::Accounts` (`max_values`: None, `max_size`: Some(96), added: 2571, mode: `MaxEncodedLen`) + // Storage: `Tokens::TotalIssuance` (r:1 w:1) + // Proof: `Tokens::TotalIssuance` (`max_values`: None, `max_size`: Some(28), added: 2503, mode: `MaxEncodedLen`) + // Storage: `Crowdloan::CrowdloanPeriod` (r:1 w:0) + // Proof: `Crowdloan::CrowdloanPeriod` (`max_values`: None, `max_size`: None, mode: `Measured`) + // Storage: `Vesting::Vesting` (r:1 w:1) + // Proof: `Vesting::Vesting` (`max_values`: None, `max_size`: Some(1857), added: 4332, mode: `MaxEncodedLen`) + // Storage: `Tokens::Locks` (r:1 w:1) + // Proof: `Tokens::Locks` (`max_values`: None, `max_size`: Some(1249), added: 3724, mode: `MaxEncodedLen`) + // Storage: `System::Account` (r:1 w:1) + // Proof: `System::Account` (`max_values`: None, `max_size`: Some(52), added: 2527, mode: `MaxEncodedLen`) + fn claim() -> Weight { + (Weight::from_parts(100_220_000, 0)) + .saturating_add(T::DbWeight::get().reads(10 as u64)) + .saturating_add(T::DbWeight::get().writes(6 as u64)) + } + // Storage: `Crowdloan::CrowdloanId` (r:1 w:0) + // Proof: `Crowdloan::CrowdloanId` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + // Storage: `Crowdloan::AccountsPayable` (r:2 w:2) + // Proof: `Crowdloan::AccountsPayable` (`max_values`: None, `max_size`: None, mode: `Measured`) + fn update_reward_address() -> Weight { + (Weight::from_parts(29_540_000, 0)) + .saturating_add(T::DbWeight::get().reads(3 as u64)) + .saturating_add(T::DbWeight::get().writes(2 as u64)) + } + // Storage: `Crowdloan::CrowdloanId` (r:1 w:0) + // Proof: `Crowdloan::CrowdloanId` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + // Storage: `Crowdloan::UnassociatedContributions` (r:1 w:1) + // Proof: `Crowdloan::UnassociatedContributions` (`max_values`: None, `max_size`: None, mode: `Measured`) + // Storage: `Crowdloan::ClaimedRelayChainIds` (r:1 w:1) + // Proof: `Crowdloan::ClaimedRelayChainIds` (`max_values`: None, `max_size`: None, mode: `Measured`) + // Storage: `Crowdloan::AccountsPayable` (r:1 w:1) + // Proof: `Crowdloan::AccountsPayable` (`max_values`: None, `max_size`: None, mode: `Measured`) + fn associate_native_identity() -> Weight { + (Weight::from_parts(96_270_000, 0)) + .saturating_add(T::DbWeight::get().reads(4 as u64)) + .saturating_add(T::DbWeight::get().writes(3 as u64)) + } + // Storage: `Crowdloan::CrowdloanId` (r:1 w:0) + // Proof: `Crowdloan::CrowdloanId` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + // Storage: `Crowdloan::AccountsPayable` (r:2 w:2) + // Proof: `Crowdloan::AccountsPayable` (`max_values`: None, `max_size`: None, mode: `Measured`) + fn change_association_with_relay_keys(x: u32, ) -> Weight { + (Weight::from_parts(65_550_833, 0)) + // Standard Error: 101_555 + .saturating_add((Weight::from_parts(51_585_316, 0)).saturating_mul(x as u64)) + .saturating_add(T::DbWeight::get().reads(3 as u64)) + .saturating_add(T::DbWeight::get().writes(2 as u64)) + } +} + +// For backwards compatibility and tests +impl WeightInfo for () { + // Storage: `Crowdloan::Initialized` (r:1 w:0) + // Proof: `Crowdloan::Initialized` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + // Storage: `Crowdloan::CrowdloanId` (r:1 w:0) + // Proof: `Crowdloan::CrowdloanId` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + // Storage: `Crowdloan::InitializedRewardAmount` (r:1 w:0) + // Proof: `Crowdloan::InitializedRewardAmount` (`max_values`: None, `max_size`: None, mode: `Measured`) + // Storage: `Crowdloan::CrowdloanAllocation` (r:0 w:1) + // Proof: `Crowdloan::CrowdloanAllocation` (`max_values`: None, `max_size`: None, mode: `Measured`) + fn set_crowdloan_allocation() -> Weight { + (Weight::from_parts(11_700_000, 0)) + .saturating_add(RocksDbWeight::get().reads(3 as u64)) + .saturating_add(RocksDbWeight::get().writes(1 as u64)) + } + // Storage: `Crowdloan::Initialized` (r:1 w:0) + // Proof: `Crowdloan::Initialized` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + // Storage: `Crowdloan::CrowdloanId` (r:1 w:0) + // Proof: `Crowdloan::CrowdloanId` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + // Storage: `Crowdloan::InitializedRewardAmount` (r:1 w:1) + // Proof: `Crowdloan::InitializedRewardAmount` (`max_values`: None, `max_size`: None, mode: `Measured`) + // Storage: `Crowdloan::TotalContributors` (r:1 w:1) + // Proof: `Crowdloan::TotalContributors` (`max_values`: None, `max_size`: None, mode: `Measured`) + // Storage: `Crowdloan::CrowdloanAllocation` (r:1 w:0) + // Proof: `Crowdloan::CrowdloanAllocation` (`max_values`: None, `max_size`: None, mode: `Measured`) + // Storage: `Crowdloan::ClaimedRelayChainIds` (r:100 w:100) + // Proof: `Crowdloan::ClaimedRelayChainIds` (`max_values`: None, `max_size`: None, mode: `Measured`) + // Storage: `Crowdloan::UnassociatedContributions` (r:100 w:0) + // Proof: `Crowdloan::UnassociatedContributions` (`max_values`: None, `max_size`: None, mode: `Measured`) + // Storage: `Crowdloan::AccountsPayable` (r:100 w:100) + // Proof: `Crowdloan::AccountsPayable` (`max_values`: None, `max_size`: None, mode: `Measured`) + fn initialize_reward_vec(x: u32, ) -> Weight { + (Weight::from_parts(116_459_308, 0)) + // Standard Error: 36_116 + .saturating_add((Weight::from_parts(21_988_497, 0)).saturating_mul(x as u64)) + .saturating_add(RocksDbWeight::get().reads(5 as u64)) + .saturating_add(RocksDbWeight::get().reads((3 as u64).saturating_mul(x as u64))) + .saturating_add(RocksDbWeight::get().writes(2 as u64)) + .saturating_add(RocksDbWeight::get().writes((2 as u64).saturating_mul(x as u64))) + } + // Storage: `Crowdloan::Initialized` (r:1 w:1) + // Proof: `Crowdloan::Initialized` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + // Storage: `Crowdloan::CrowdloanId` (r:1 w:0) + // Proof: `Crowdloan::CrowdloanId` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + // Storage: `Crowdloan::InitializedRewardAmount` (r:1 w:0) + // Proof: `Crowdloan::InitializedRewardAmount` (`max_values`: None, `max_size`: None, mode: `Measured`) + // Storage: `Crowdloan::CrowdloanAllocation` (r:1 w:0) + // Proof: `Crowdloan::CrowdloanAllocation` (`max_values`: None, `max_size`: None, mode: `Measured`) + // Storage: `Crowdloan::TotalContributors` (r:1 w:0) + // Proof: `Crowdloan::TotalContributors` (`max_values`: None, `max_size`: None, mode: `Measured`) + // Storage: `Crowdloan::CrowdloanPeriod` (r:0 w:1) + // Proof: `Crowdloan::CrowdloanPeriod` (`max_values`: None, `max_size`: None, mode: `Measured`) + fn complete_initialization() -> Weight { + (Weight::from_parts(24_320_000, 0)) + .saturating_add(RocksDbWeight::get().reads(5 as u64)) + .saturating_add(RocksDbWeight::get().writes(2 as u64)) + } + // Storage: `Crowdloan::CrowdloanId` (r:1 w:0) + // Proof: `Crowdloan::CrowdloanId` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + // Storage: `Crowdloan::Initialized` (r:1 w:0) + // Proof: `Crowdloan::Initialized` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + // Storage: `Crowdloan::AccountsPayable` (r:1 w:1) + // Proof: `Crowdloan::AccountsPayable` (`max_values`: None, `max_size`: None, mode: `Measured`) + // Storage: `Tokens::NextCurrencyId` (r:1 w:0) + // Proof: `Tokens::NextCurrencyId` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) + // Storage: `Tokens::Accounts` (r:1 w:1) + // Proof: `Tokens::Accounts` (`max_values`: None, `max_size`: Some(96), added: 2571, mode: `MaxEncodedLen`) + // Storage: `Tokens::TotalIssuance` (r:1 w:1) + // Proof: `Tokens::TotalIssuance` (`max_values`: None, `max_size`: Some(28), added: 2503, mode: `MaxEncodedLen`) + // Storage: `Crowdloan::CrowdloanPeriod` (r:1 w:0) + // Proof: `Crowdloan::CrowdloanPeriod` (`max_values`: None, `max_size`: None, mode: `Measured`) + // Storage: `Vesting::Vesting` (r:1 w:1) + // Proof: `Vesting::Vesting` (`max_values`: None, `max_size`: Some(1857), added: 4332, mode: `MaxEncodedLen`) + // Storage: `Tokens::Locks` (r:1 w:1) + // Proof: `Tokens::Locks` (`max_values`: None, `max_size`: Some(1249), added: 3724, mode: `MaxEncodedLen`) + // Storage: `System::Account` (r:1 w:1) + // Proof: `System::Account` (`max_values`: None, `max_size`: Some(52), added: 2527, mode: `MaxEncodedLen`) + fn claim() -> Weight { + (Weight::from_parts(100_220_000, 0)) + .saturating_add(RocksDbWeight::get().reads(10 as u64)) + .saturating_add(RocksDbWeight::get().writes(6 as u64)) + } + // Storage: `Crowdloan::CrowdloanId` (r:1 w:0) + // Proof: `Crowdloan::CrowdloanId` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + // Storage: `Crowdloan::AccountsPayable` (r:2 w:2) + // Proof: `Crowdloan::AccountsPayable` (`max_values`: None, `max_size`: None, mode: `Measured`) + fn update_reward_address() -> Weight { + (Weight::from_parts(29_540_000, 0)) + .saturating_add(RocksDbWeight::get().reads(3 as u64)) + .saturating_add(RocksDbWeight::get().writes(2 as u64)) + } + // Storage: `Crowdloan::CrowdloanId` (r:1 w:0) + // Proof: `Crowdloan::CrowdloanId` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + // Storage: `Crowdloan::UnassociatedContributions` (r:1 w:1) + // Proof: `Crowdloan::UnassociatedContributions` (`max_values`: None, `max_size`: None, mode: `Measured`) + // Storage: `Crowdloan::ClaimedRelayChainIds` (r:1 w:1) + // Proof: `Crowdloan::ClaimedRelayChainIds` (`max_values`: None, `max_size`: None, mode: `Measured`) + // Storage: `Crowdloan::AccountsPayable` (r:1 w:1) + // Proof: `Crowdloan::AccountsPayable` (`max_values`: None, `max_size`: None, mode: `Measured`) + fn associate_native_identity() -> Weight { + (Weight::from_parts(96_270_000, 0)) + .saturating_add(RocksDbWeight::get().reads(4 as u64)) + .saturating_add(RocksDbWeight::get().writes(3 as u64)) + } + // Storage: `Crowdloan::CrowdloanId` (r:1 w:0) + // Proof: `Crowdloan::CrowdloanId` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + // Storage: `Crowdloan::AccountsPayable` (r:2 w:2) + // Proof: `Crowdloan::AccountsPayable` (`max_values`: None, `max_size`: None, mode: `Measured`) + fn change_association_with_relay_keys(x: u32, ) -> Weight { + (Weight::from_parts(65_550_833, 0)) + // Standard Error: 101_555 + .saturating_add((Weight::from_parts(51_585_316, 0)).saturating_mul(x as u64)) + .saturating_add(RocksDbWeight::get().reads(3 as u64)) + .saturating_add(RocksDbWeight::get().writes(2 as u64)) + } +} diff --git a/gasp-node/rollup/runtime/src/weights/pallet_fee_lock.rs b/gasp-node/rollup/runtime/src/weights/pallet_fee_lock.rs new file mode 100644 index 000000000..e78a953c4 --- /dev/null +++ b/gasp-node/rollup/runtime/src/weights/pallet_fee_lock.rs @@ -0,0 +1,160 @@ +// This file is part of Mangata. + +// Copyright (C) 2020-2022 Mangata Foundation. +// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 + +// This program 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. + +// This program 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 this program. If not, see . + +//! Autogenerated weights for pallet_fee_lock +//! +//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 32.0.0 +//! DATE: 2024-11-29, STEPS: `50`, REPEAT: 20, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! EXECUTION: , WASM-EXECUTION: Compiled, CHAIN: Some("rollup-local"), DB CACHE: 1024 + +// Executed Command: +// target/release/rollup-node +// benchmark +// pallet +// -l=info,runtime::collective=warn,xyk=warn +// --chain +// rollup-local +// --wasm-execution +// compiled +// --pallet +// * +// --extrinsic +// * +// --steps +// 50 +// --repeat +// 20 +// --template +// ./templates/module-weight-template.hbs +// --output +// ./benchmarks/ + +#![cfg_attr(rustfmt, rustfmt_skip)] +#![allow(unused_parens)] +#![allow(unused_imports)] +#![allow(clippy::unnecessary_cast)] + +use frame_support::{traits::Get, weights::{Weight, constants::RocksDbWeight}}; +use sp_std::marker::PhantomData; + +/// Weight functions needed for pallet_fee_lock. +pub trait WeightInfo { + fn process_fee_lock() -> Weight; + fn get_swap_valuation_for_token() -> Weight; + fn update_fee_lock_metadata() -> Weight; + fn unlock_fee() -> Weight; +} + +/// Weights for pallet_fee_lock using the Mangata node and recommended hardware. +pub struct ModuleWeight(PhantomData); +impl pallet_fee_lock::WeightInfo for ModuleWeight { + // Storage: `FeeLock::FeeLockMetadata` (r:1 w:0) + // Proof: `FeeLock::FeeLockMetadata` (`max_values`: Some(1), `max_size`: Some(438), added: 933, mode: `MaxEncodedLen`) + // Storage: `FeeLock::AccountFeeLockData` (r:1 w:1) + // Proof: `FeeLock::AccountFeeLockData` (`max_values`: None, `max_size`: Some(48), added: 2523, mode: `MaxEncodedLen`) + // Storage: `Tokens::Accounts` (r:1 w:1) + // Proof: `Tokens::Accounts` (`max_values`: None, `max_size`: Some(96), added: 2571, mode: `MaxEncodedLen`) + // Storage: `FeeLock::FeeLockMetadataQeueuePosition` (r:1 w:1) + // Proof: `FeeLock::FeeLockMetadataQeueuePosition` (`max_values`: None, `max_size`: Some(44), added: 2519, mode: `MaxEncodedLen`) + // Storage: `FeeLock::UnlockQueue` (r:1 w:2) + // Proof: `FeeLock::UnlockQueue` (`max_values`: None, `max_size`: Some(44), added: 2519, mode: `MaxEncodedLen`) + // Storage: `FeeLock::UnlockQueueEnd` (r:1 w:1) + // Proof: `FeeLock::UnlockQueueEnd` (`max_values`: Some(1), `max_size`: Some(16), added: 511, mode: `MaxEncodedLen`) + fn process_fee_lock() -> Weight { + (Weight::from_parts(46_960_000, 0)) + .saturating_add(T::DbWeight::get().reads(6 as u64)) + .saturating_add(T::DbWeight::get().writes(6 as u64)) + } + // Storage: `Xyk::Pools` (r:2 w:0) + // Proof: `Xyk::Pools` (`max_values`: None, `max_size`: Some(56), added: 2531, mode: `MaxEncodedLen`) + fn get_swap_valuation_for_token() -> Weight { + (Weight::from_parts(14_920_000, 0)) + .saturating_add(T::DbWeight::get().reads(2 as u64)) + } + // Storage: `FeeLock::FeeLockMetadata` (r:1 w:1) + // Proof: `FeeLock::FeeLockMetadata` (`max_values`: Some(1), `max_size`: Some(438), added: 933, mode: `MaxEncodedLen`) + fn update_fee_lock_metadata() -> Weight { + (Weight::from_parts(22_310_000, 0)) + .saturating_add(T::DbWeight::get().reads(1 as u64)) + .saturating_add(T::DbWeight::get().writes(1 as u64)) + } + // Storage: `FeeLock::AccountFeeLockData` (r:1 w:1) + // Proof: `FeeLock::AccountFeeLockData` (`max_values`: None, `max_size`: Some(48), added: 2523, mode: `MaxEncodedLen`) + // Storage: `FeeLock::FeeLockMetadata` (r:1 w:0) + // Proof: `FeeLock::FeeLockMetadata` (`max_values`: Some(1), `max_size`: Some(438), added: 933, mode: `MaxEncodedLen`) + // Storage: `Tokens::Accounts` (r:1 w:1) + // Proof: `Tokens::Accounts` (`max_values`: None, `max_size`: Some(96), added: 2571, mode: `MaxEncodedLen`) + // Storage: `FeeLock::FeeLockMetadataQeueuePosition` (r:1 w:1) + // Proof: `FeeLock::FeeLockMetadataQeueuePosition` (`max_values`: None, `max_size`: Some(44), added: 2519, mode: `MaxEncodedLen`) + // Storage: `FeeLock::UnlockQueue` (r:1 w:1) + // Proof: `FeeLock::UnlockQueue` (`max_values`: None, `max_size`: Some(44), added: 2519, mode: `MaxEncodedLen`) + fn unlock_fee() -> Weight { + (Weight::from_parts(46_020_000, 0)) + .saturating_add(T::DbWeight::get().reads(5 as u64)) + .saturating_add(T::DbWeight::get().writes(4 as u64)) + } +} + +// For backwards compatibility and tests +impl WeightInfo for () { + // Storage: `FeeLock::FeeLockMetadata` (r:1 w:0) + // Proof: `FeeLock::FeeLockMetadata` (`max_values`: Some(1), `max_size`: Some(438), added: 933, mode: `MaxEncodedLen`) + // Storage: `FeeLock::AccountFeeLockData` (r:1 w:1) + // Proof: `FeeLock::AccountFeeLockData` (`max_values`: None, `max_size`: Some(48), added: 2523, mode: `MaxEncodedLen`) + // Storage: `Tokens::Accounts` (r:1 w:1) + // Proof: `Tokens::Accounts` (`max_values`: None, `max_size`: Some(96), added: 2571, mode: `MaxEncodedLen`) + // Storage: `FeeLock::FeeLockMetadataQeueuePosition` (r:1 w:1) + // Proof: `FeeLock::FeeLockMetadataQeueuePosition` (`max_values`: None, `max_size`: Some(44), added: 2519, mode: `MaxEncodedLen`) + // Storage: `FeeLock::UnlockQueue` (r:1 w:2) + // Proof: `FeeLock::UnlockQueue` (`max_values`: None, `max_size`: Some(44), added: 2519, mode: `MaxEncodedLen`) + // Storage: `FeeLock::UnlockQueueEnd` (r:1 w:1) + // Proof: `FeeLock::UnlockQueueEnd` (`max_values`: Some(1), `max_size`: Some(16), added: 511, mode: `MaxEncodedLen`) + fn process_fee_lock() -> Weight { + (Weight::from_parts(46_960_000, 0)) + .saturating_add(RocksDbWeight::get().reads(6 as u64)) + .saturating_add(RocksDbWeight::get().writes(6 as u64)) + } + // Storage: `Xyk::Pools` (r:2 w:0) + // Proof: `Xyk::Pools` (`max_values`: None, `max_size`: Some(56), added: 2531, mode: `MaxEncodedLen`) + fn get_swap_valuation_for_token() -> Weight { + (Weight::from_parts(14_920_000, 0)) + .saturating_add(RocksDbWeight::get().reads(2 as u64)) + } + // Storage: `FeeLock::FeeLockMetadata` (r:1 w:1) + // Proof: `FeeLock::FeeLockMetadata` (`max_values`: Some(1), `max_size`: Some(438), added: 933, mode: `MaxEncodedLen`) + fn update_fee_lock_metadata() -> Weight { + (Weight::from_parts(22_310_000, 0)) + .saturating_add(RocksDbWeight::get().reads(1 as u64)) + .saturating_add(RocksDbWeight::get().writes(1 as u64)) + } + // Storage: `FeeLock::AccountFeeLockData` (r:1 w:1) + // Proof: `FeeLock::AccountFeeLockData` (`max_values`: None, `max_size`: Some(48), added: 2523, mode: `MaxEncodedLen`) + // Storage: `FeeLock::FeeLockMetadata` (r:1 w:0) + // Proof: `FeeLock::FeeLockMetadata` (`max_values`: Some(1), `max_size`: Some(438), added: 933, mode: `MaxEncodedLen`) + // Storage: `Tokens::Accounts` (r:1 w:1) + // Proof: `Tokens::Accounts` (`max_values`: None, `max_size`: Some(96), added: 2571, mode: `MaxEncodedLen`) + // Storage: `FeeLock::FeeLockMetadataQeueuePosition` (r:1 w:1) + // Proof: `FeeLock::FeeLockMetadataQeueuePosition` (`max_values`: None, `max_size`: Some(44), added: 2519, mode: `MaxEncodedLen`) + // Storage: `FeeLock::UnlockQueue` (r:1 w:1) + // Proof: `FeeLock::UnlockQueue` (`max_values`: None, `max_size`: Some(44), added: 2519, mode: `MaxEncodedLen`) + fn unlock_fee() -> Weight { + (Weight::from_parts(46_020_000, 0)) + .saturating_add(RocksDbWeight::get().reads(5 as u64)) + .saturating_add(RocksDbWeight::get().writes(4 as u64)) + } +} diff --git a/gasp-node/rollup/runtime/src/weights/pallet_issuance.rs b/gasp-node/rollup/runtime/src/weights/pallet_issuance.rs new file mode 100644 index 000000000..72721f2e1 --- /dev/null +++ b/gasp-node/rollup/runtime/src/weights/pallet_issuance.rs @@ -0,0 +1,158 @@ +// This file is part of Mangata. + +// Copyright (C) 2020-2022 Mangata Foundation. +// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 + +// This program 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. + +// This program 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 this program. If not, see . + +//! Autogenerated weights for pallet_issuance +//! +//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 32.0.0 +//! DATE: 2025-01-15, STEPS: `50`, REPEAT: 20, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! EXECUTION: , WASM-EXECUTION: Compiled, CHAIN: Some("rollup-local"), DB CACHE: 1024 + +// Executed Command: +// target/release/rollup-node +// benchmark +// pallet +// -l=info,runtime::collective=warn,xyk=warn +// --chain +// rollup-local +// --wasm-execution +// compiled +// --pallet +// * +// --extrinsic +// * +// --steps +// 50 +// --repeat +// 20 +// --template +// ./templates/module-weight-template.hbs +// --output +// ./benchmarks/ + +#![cfg_attr(rustfmt, rustfmt_skip)] +#![allow(unused_parens)] +#![allow(unused_imports)] +#![allow(clippy::unnecessary_cast)] + +use frame_support::{traits::Get, weights::{Weight, constants::RocksDbWeight}}; +use sp_std::marker::PhantomData; + +/// Weight functions needed for pallet_issuance. +pub trait WeightInfo { + fn init_issuance_config() -> Weight; + fn finalize_tge() -> Weight; + fn execute_tge(x: u32, ) -> Weight; + fn set_issuance_config() -> Weight; +} + +/// Weights for pallet_issuance using the Mangata node and recommended hardware. +pub struct ModuleWeight(PhantomData); +impl pallet_issuance::WeightInfo for ModuleWeight { + // Storage: `Issuance::IssuanceConfigStore` (r:1 w:1) + // Proof: `Issuance::IssuanceConfigStore` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + // Storage: `Issuance::IsTGEFinalized` (r:1 w:0) + // Proof: `Issuance::IsTGEFinalized` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + // Storage: `Tokens::TotalIssuance` (r:1 w:0) + // Proof: `Tokens::TotalIssuance` (`max_values`: None, `max_size`: Some(28), added: 2503, mode: `MaxEncodedLen`) + fn init_issuance_config() -> Weight { + (Weight::from_parts(17_750_000, 0)) + .saturating_add(T::DbWeight::get().reads(3 as u64)) + .saturating_add(T::DbWeight::get().writes(1 as u64)) + } + // Storage: `Issuance::IsTGEFinalized` (r:1 w:1) + // Proof: `Issuance::IsTGEFinalized` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + fn finalize_tge() -> Weight { + (Weight::from_parts(9_809_000, 0)) + .saturating_add(T::DbWeight::get().reads(1 as u64)) + .saturating_add(T::DbWeight::get().writes(1 as u64)) + } + // Storage: `Issuance::IsTGEFinalized` (r:1 w:0) + // Proof: `Issuance::IsTGEFinalized` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + // Storage: `Tokens::Accounts` (r:100 w:100) + // Proof: `Tokens::Accounts` (`max_values`: None, `max_size`: Some(96), added: 2571, mode: `MaxEncodedLen`) + // Storage: `Tokens::TotalIssuance` (r:1 w:1) + // Proof: `Tokens::TotalIssuance` (`max_values`: None, `max_size`: Some(28), added: 2503, mode: `MaxEncodedLen`) + // Storage: `System::Account` (r:100 w:100) + // Proof: `System::Account` (`max_values`: None, `max_size`: Some(52), added: 2527, mode: `MaxEncodedLen`) + // Storage: `Issuance::TGETotal` (r:1 w:1) + // Proof: `Issuance::TGETotal` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + fn execute_tge(x: u32, ) -> Weight { + (Weight::from_parts(26_290_460, 0)) + // Standard Error: 10_945 + .saturating_add((Weight::from_parts(22_350_615, 0)).saturating_mul(x as u64)) + .saturating_add(T::DbWeight::get().reads(3 as u64)) + .saturating_add(T::DbWeight::get().reads((2 as u64).saturating_mul(x as u64))) + .saturating_add(T::DbWeight::get().writes(2 as u64)) + .saturating_add(T::DbWeight::get().writes((2 as u64).saturating_mul(x as u64))) + } + // Storage: `Issuance::IssuanceConfigStore` (r:1 w:1) + // Proof: `Issuance::IssuanceConfigStore` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + fn set_issuance_config() -> Weight { + (Weight::from_parts(11_770_000, 0)) + .saturating_add(T::DbWeight::get().reads(1 as u64)) + .saturating_add(T::DbWeight::get().writes(1 as u64)) + } +} + +// For backwards compatibility and tests +impl WeightInfo for () { + // Storage: `Issuance::IssuanceConfigStore` (r:1 w:1) + // Proof: `Issuance::IssuanceConfigStore` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + // Storage: `Issuance::IsTGEFinalized` (r:1 w:0) + // Proof: `Issuance::IsTGEFinalized` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + // Storage: `Tokens::TotalIssuance` (r:1 w:0) + // Proof: `Tokens::TotalIssuance` (`max_values`: None, `max_size`: Some(28), added: 2503, mode: `MaxEncodedLen`) + fn init_issuance_config() -> Weight { + (Weight::from_parts(17_750_000, 0)) + .saturating_add(RocksDbWeight::get().reads(3 as u64)) + .saturating_add(RocksDbWeight::get().writes(1 as u64)) + } + // Storage: `Issuance::IsTGEFinalized` (r:1 w:1) + // Proof: `Issuance::IsTGEFinalized` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + fn finalize_tge() -> Weight { + (Weight::from_parts(9_809_000, 0)) + .saturating_add(RocksDbWeight::get().reads(1 as u64)) + .saturating_add(RocksDbWeight::get().writes(1 as u64)) + } + // Storage: `Issuance::IsTGEFinalized` (r:1 w:0) + // Proof: `Issuance::IsTGEFinalized` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + // Storage: `Tokens::Accounts` (r:100 w:100) + // Proof: `Tokens::Accounts` (`max_values`: None, `max_size`: Some(96), added: 2571, mode: `MaxEncodedLen`) + // Storage: `Tokens::TotalIssuance` (r:1 w:1) + // Proof: `Tokens::TotalIssuance` (`max_values`: None, `max_size`: Some(28), added: 2503, mode: `MaxEncodedLen`) + // Storage: `System::Account` (r:100 w:100) + // Proof: `System::Account` (`max_values`: None, `max_size`: Some(52), added: 2527, mode: `MaxEncodedLen`) + // Storage: `Issuance::TGETotal` (r:1 w:1) + // Proof: `Issuance::TGETotal` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + fn execute_tge(x: u32, ) -> Weight { + (Weight::from_parts(26_290_460, 0)) + // Standard Error: 10_945 + .saturating_add((Weight::from_parts(22_350_615, 0)).saturating_mul(x as u64)) + .saturating_add(RocksDbWeight::get().reads(3 as u64)) + .saturating_add(RocksDbWeight::get().reads((2 as u64).saturating_mul(x as u64))) + .saturating_add(RocksDbWeight::get().writes(2 as u64)) + .saturating_add(RocksDbWeight::get().writes((2 as u64).saturating_mul(x as u64))) + } + // Storage: `Issuance::IssuanceConfigStore` (r:1 w:1) + // Proof: `Issuance::IssuanceConfigStore` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + fn set_issuance_config() -> Weight { + (Weight::from_parts(11_770_000, 0)) + .saturating_add(RocksDbWeight::get().reads(1 as u64)) + .saturating_add(RocksDbWeight::get().writes(1 as u64)) + } +} diff --git a/gasp-node/rollup/runtime/src/weights/pallet_market.rs b/gasp-node/rollup/runtime/src/weights/pallet_market.rs new file mode 100644 index 000000000..7ff9c5529 --- /dev/null +++ b/gasp-node/rollup/runtime/src/weights/pallet_market.rs @@ -0,0 +1,850 @@ +// This file is part of Mangata. + +// Copyright (C) 2020-2022 Mangata Foundation. +// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 + +// This program 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. + +// This program 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 this program. If not, see . + +//! Autogenerated weights for pallet_market +//! +//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 32.0.0 +//! DATE: 2024-11-29, STEPS: `50`, REPEAT: 20, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! EXECUTION: , WASM-EXECUTION: Compiled, CHAIN: Some("rollup-local"), DB CACHE: 1024 + +// Executed Command: +// target/release/rollup-node +// benchmark +// pallet +// -l=info,runtime::collective=warn,xyk=warn +// --chain +// rollup-local +// --wasm-execution +// compiled +// --pallet +// * +// --extrinsic +// * +// --steps +// 50 +// --repeat +// 20 +// --template +// ./templates/module-weight-template.hbs +// --output +// ./benchmarks/ + +#![cfg_attr(rustfmt, rustfmt_skip)] +#![allow(unused_parens)] +#![allow(unused_imports)] +#![allow(clippy::unnecessary_cast)] + +use frame_support::{traits::Get, weights::{Weight, constants::RocksDbWeight}}; +use sp_std::marker::PhantomData; + +/// Weight functions needed for pallet_market. +pub trait WeightInfo { + fn create_pool_xyk() -> Weight; + fn mint_liquidity_xyk() -> Weight; + fn mint_liquidity_fixed_amounts_xyk() -> Weight; + fn mint_liquidity_using_vesting_native_tokens_by_vesting_index_xyk() -> Weight; + fn mint_liquidity_using_vesting_native_tokens_xyk() -> Weight; + fn burn_liquidity_xyk() -> Weight; + fn multiswap_asset_xyk(y: u32, ) -> Weight; + fn multiswap_asset_buy_xyk(y: u32, ) -> Weight; + fn create_pool_sswap() -> Weight; + fn mint_liquidity_sswap() -> Weight; + fn mint_liquidity_fixed_amounts_sswap() -> Weight; + fn mint_liquidity_using_vesting_native_tokens_by_vesting_index_sswap() -> Weight; + fn mint_liquidity_using_vesting_native_tokens_sswap() -> Weight; + fn burn_liquidity_sswap() -> Weight; + fn multiswap_asset_sswap(y: u32, ) -> Weight; + fn multiswap_asset_buy_sswap(y: u32, ) -> Weight; +} + +/// Weights for pallet_market using the Mangata node and recommended hardware. +pub struct ModuleWeight(PhantomData); +impl pallet_market::WeightInfo for ModuleWeight { + // Storage: `AssetRegistry::Metadata` (r:3 w:1) + // Proof: `AssetRegistry::Metadata` (`max_values`: None, `max_size`: Some(153), added: 2628, mode: `MaxEncodedLen`) + // Storage: `Bootstrap::BootstrapSchedule` (r:1 w:0) + // Proof: `Bootstrap::BootstrapSchedule` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + // Storage: `Xyk::Pools` (r:2 w:1) + // Proof: `Xyk::Pools` (`max_values`: None, `max_size`: Some(56), added: 2531, mode: `MaxEncodedLen`) + // Storage: `Tokens::Accounts` (r:5 w:5) + // Proof: `Tokens::Accounts` (`max_values`: None, `max_size`: Some(96), added: 2571, mode: `MaxEncodedLen`) + // Storage: `System::Account` (r:1 w:1) + // Proof: `System::Account` (`max_values`: None, `max_size`: Some(52), added: 2527, mode: `MaxEncodedLen`) + // Storage: `Tokens::NextCurrencyId` (r:1 w:1) + // Proof: `Tokens::NextCurrencyId` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) + // Storage: `Tokens::TotalIssuance` (r:1 w:1) + // Proof: `Tokens::TotalIssuance` (`max_values`: None, `max_size`: Some(28), added: 2503, mode: `MaxEncodedLen`) + // Storage: `Xyk::LiquidityAssets` (r:0 w:1) + // Proof: `Xyk::LiquidityAssets` (`max_values`: None, `max_size`: Some(29), added: 2504, mode: `MaxEncodedLen`) + // Storage: `Xyk::LiquidityPools` (r:0 w:1) + // Proof: `Xyk::LiquidityPools` (`max_values`: None, `max_size`: Some(29), added: 2504, mode: `MaxEncodedLen`) + fn create_pool_xyk() -> Weight { + (Weight::from_parts(168_069_000, 0)) + .saturating_add(T::DbWeight::get().reads(14 as u64)) + .saturating_add(T::DbWeight::get().writes(12 as u64)) + } + // Storage: `Xyk::LiquidityPools` (r:1 w:0) + // Proof: `Xyk::LiquidityPools` (`max_values`: None, `max_size`: Some(29), added: 2504, mode: `MaxEncodedLen`) + // Storage: `AssetRegistry::Metadata` (r:2 w:0) + // Proof: `AssetRegistry::Metadata` (`max_values`: None, `max_size`: Some(153), added: 2628, mode: `MaxEncodedLen`) + // Storage: `Xyk::LiquidityAssets` (r:1 w:0) + // Proof: `Xyk::LiquidityAssets` (`max_values`: None, `max_size`: Some(29), added: 2504, mode: `MaxEncodedLen`) + // Storage: `Xyk::Pools` (r:1 w:1) + // Proof: `Xyk::Pools` (`max_values`: None, `max_size`: Some(56), added: 2531, mode: `MaxEncodedLen`) + // Storage: `Tokens::TotalIssuance` (r:1 w:1) + // Proof: `Tokens::TotalIssuance` (`max_values`: None, `max_size`: Some(28), added: 2503, mode: `MaxEncodedLen`) + // Storage: `Tokens::Accounts` (r:5 w:5) + // Proof: `Tokens::Accounts` (`max_values`: None, `max_size`: Some(96), added: 2571, mode: `MaxEncodedLen`) + // Storage: `Tokens::NextCurrencyId` (r:1 w:0) + // Proof: `Tokens::NextCurrencyId` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) + // Storage: `ProofOfStake::PromotedPoolRewards` (r:1 w:0) + // Proof: `ProofOfStake::PromotedPoolRewards` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + // Storage: `MultiPurposeLiquidity::ReserveStatus` (r:1 w:1) + // Proof: `MultiPurposeLiquidity::ReserveStatus` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`) + // Storage: `ProofOfStake::RewardsInfo` (r:1 w:1) + // Proof: `ProofOfStake::RewardsInfo` (`max_values`: None, `max_size`: None, mode: `Measured`) + // Storage: `ProofOfStake::TotalActivatedLiquidity` (r:1 w:1) + // Proof: `ProofOfStake::TotalActivatedLiquidity` (`max_values`: None, `max_size`: None, mode: `Measured`) + fn mint_liquidity_xyk() -> Weight { + (Weight::from_parts(219_140_000, 0)) + .saturating_add(T::DbWeight::get().reads(16 as u64)) + .saturating_add(T::DbWeight::get().writes(10 as u64)) + } + // Storage: `Xyk::LiquidityPools` (r:1 w:0) + // Proof: `Xyk::LiquidityPools` (`max_values`: None, `max_size`: Some(29), added: 2504, mode: `MaxEncodedLen`) + // Storage: `AssetRegistry::Metadata` (r:2 w:0) + // Proof: `AssetRegistry::Metadata` (`max_values`: None, `max_size`: Some(153), added: 2628, mode: `MaxEncodedLen`) + // Storage: `Xyk::LiquidityAssets` (r:2 w:0) + // Proof: `Xyk::LiquidityAssets` (`max_values`: None, `max_size`: Some(29), added: 2504, mode: `MaxEncodedLen`) + // Storage: `Tokens::TotalIssuance` (r:1 w:1) + // Proof: `Tokens::TotalIssuance` (`max_values`: None, `max_size`: Some(28), added: 2503, mode: `MaxEncodedLen`) + // Storage: `Xyk::Pools` (r:4 w:1) + // Proof: `Xyk::Pools` (`max_values`: None, `max_size`: Some(56), added: 2531, mode: `MaxEncodedLen`) + // Storage: `Tokens::Accounts` (r:7 w:7) + // Proof: `Tokens::Accounts` (`max_values`: None, `max_size`: Some(96), added: 2571, mode: `MaxEncodedLen`) + // Storage: `Maintenance::MaintenanceStatus` (r:1 w:0) + // Proof: `Maintenance::MaintenanceStatus` (`max_values`: Some(1), `max_size`: Some(2), added: 497, mode: `MaxEncodedLen`) + // Storage: `System::Account` (r:2 w:2) + // Proof: `System::Account` (`max_values`: None, `max_size`: Some(52), added: 2527, mode: `MaxEncodedLen`) + // Storage: `Tokens::NextCurrencyId` (r:1 w:0) + // Proof: `Tokens::NextCurrencyId` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) + // Storage: `ProofOfStake::PromotedPoolRewards` (r:1 w:0) + // Proof: `ProofOfStake::PromotedPoolRewards` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + // Storage: `MultiPurposeLiquidity::ReserveStatus` (r:1 w:1) + // Proof: `MultiPurposeLiquidity::ReserveStatus` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`) + // Storage: `ProofOfStake::RewardsInfo` (r:1 w:1) + // Proof: `ProofOfStake::RewardsInfo` (`max_values`: None, `max_size`: None, mode: `Measured`) + // Storage: `ProofOfStake::TotalActivatedLiquidity` (r:1 w:1) + // Proof: `ProofOfStake::TotalActivatedLiquidity` (`max_values`: None, `max_size`: None, mode: `Measured`) + fn mint_liquidity_fixed_amounts_xyk() -> Weight { + (Weight::from_parts(398_450_000, 0)) + .saturating_add(T::DbWeight::get().reads(25 as u64)) + .saturating_add(T::DbWeight::get().writes(14 as u64)) + } + // Storage: `Xyk::LiquidityPools` (r:1 w:0) + // Proof: `Xyk::LiquidityPools` (`max_values`: None, `max_size`: Some(29), added: 2504, mode: `MaxEncodedLen`) + // Storage: `AssetRegistry::Metadata` (r:2 w:0) + // Proof: `AssetRegistry::Metadata` (`max_values`: None, `max_size`: Some(153), added: 2628, mode: `MaxEncodedLen`) + // Storage: `ProofOfStake::PromotedPoolRewards` (r:1 w:0) + // Proof: `ProofOfStake::PromotedPoolRewards` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + // Storage: `Vesting::Vesting` (r:2 w:2) + // Proof: `Vesting::Vesting` (`max_values`: None, `max_size`: Some(1857), added: 4332, mode: `MaxEncodedLen`) + // Storage: `Tokens::Locks` (r:2 w:2) + // Proof: `Tokens::Locks` (`max_values`: None, `max_size`: Some(1249), added: 3724, mode: `MaxEncodedLen`) + // Storage: `Tokens::Accounts` (r:5 w:5) + // Proof: `Tokens::Accounts` (`max_values`: None, `max_size`: Some(96), added: 2571, mode: `MaxEncodedLen`) + // Storage: `Xyk::LiquidityAssets` (r:1 w:0) + // Proof: `Xyk::LiquidityAssets` (`max_values`: None, `max_size`: Some(29), added: 2504, mode: `MaxEncodedLen`) + // Storage: `Xyk::Pools` (r:1 w:1) + // Proof: `Xyk::Pools` (`max_values`: None, `max_size`: Some(56), added: 2531, mode: `MaxEncodedLen`) + // Storage: `Tokens::TotalIssuance` (r:1 w:1) + // Proof: `Tokens::TotalIssuance` (`max_values`: None, `max_size`: Some(28), added: 2503, mode: `MaxEncodedLen`) + // Storage: `Tokens::NextCurrencyId` (r:1 w:0) + // Proof: `Tokens::NextCurrencyId` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) + fn mint_liquidity_using_vesting_native_tokens_by_vesting_index_xyk() -> Weight { + (Weight::from_parts(236_580_000, 0)) + .saturating_add(T::DbWeight::get().reads(17 as u64)) + .saturating_add(T::DbWeight::get().writes(11 as u64)) + } + // Storage: `Xyk::LiquidityPools` (r:1 w:0) + // Proof: `Xyk::LiquidityPools` (`max_values`: None, `max_size`: Some(29), added: 2504, mode: `MaxEncodedLen`) + // Storage: `AssetRegistry::Metadata` (r:2 w:0) + // Proof: `AssetRegistry::Metadata` (`max_values`: None, `max_size`: Some(153), added: 2628, mode: `MaxEncodedLen`) + // Storage: `ProofOfStake::PromotedPoolRewards` (r:1 w:0) + // Proof: `ProofOfStake::PromotedPoolRewards` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + // Storage: `Vesting::Vesting` (r:2 w:2) + // Proof: `Vesting::Vesting` (`max_values`: None, `max_size`: Some(1857), added: 4332, mode: `MaxEncodedLen`) + // Storage: `Tokens::Locks` (r:2 w:2) + // Proof: `Tokens::Locks` (`max_values`: None, `max_size`: Some(1249), added: 3724, mode: `MaxEncodedLen`) + // Storage: `Tokens::Accounts` (r:5 w:5) + // Proof: `Tokens::Accounts` (`max_values`: None, `max_size`: Some(96), added: 2571, mode: `MaxEncodedLen`) + // Storage: `Xyk::LiquidityAssets` (r:1 w:0) + // Proof: `Xyk::LiquidityAssets` (`max_values`: None, `max_size`: Some(29), added: 2504, mode: `MaxEncodedLen`) + // Storage: `Xyk::Pools` (r:1 w:1) + // Proof: `Xyk::Pools` (`max_values`: None, `max_size`: Some(56), added: 2531, mode: `MaxEncodedLen`) + // Storage: `Tokens::TotalIssuance` (r:1 w:1) + // Proof: `Tokens::TotalIssuance` (`max_values`: None, `max_size`: Some(28), added: 2503, mode: `MaxEncodedLen`) + // Storage: `Tokens::NextCurrencyId` (r:1 w:0) + // Proof: `Tokens::NextCurrencyId` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) + fn mint_liquidity_using_vesting_native_tokens_xyk() -> Weight { + (Weight::from_parts(235_820_000, 0)) + .saturating_add(T::DbWeight::get().reads(17 as u64)) + .saturating_add(T::DbWeight::get().writes(11 as u64)) + } + // Storage: `Xyk::LiquidityPools` (r:1 w:0) + // Proof: `Xyk::LiquidityPools` (`max_values`: None, `max_size`: Some(29), added: 2504, mode: `MaxEncodedLen`) + // Storage: `AssetRegistry::Metadata` (r:2 w:0) + // Proof: `AssetRegistry::Metadata` (`max_values`: None, `max_size`: Some(153), added: 2628, mode: `MaxEncodedLen`) + // Storage: `Xyk::LiquidityAssets` (r:1 w:0) + // Proof: `Xyk::LiquidityAssets` (`max_values`: None, `max_size`: Some(29), added: 2504, mode: `MaxEncodedLen`) + // Storage: `MultiPurposeLiquidity::ReserveStatus` (r:1 w:0) + // Proof: `MultiPurposeLiquidity::ReserveStatus` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`) + // Storage: `Xyk::Pools` (r:1 w:1) + // Proof: `Xyk::Pools` (`max_values`: None, `max_size`: Some(56), added: 2531, mode: `MaxEncodedLen`) + // Storage: `Tokens::Accounts` (r:5 w:5) + // Proof: `Tokens::Accounts` (`max_values`: None, `max_size`: Some(96), added: 2571, mode: `MaxEncodedLen`) + // Storage: `Tokens::TotalIssuance` (r:1 w:1) + // Proof: `Tokens::TotalIssuance` (`max_values`: None, `max_size`: Some(28), added: 2503, mode: `MaxEncodedLen`) + fn burn_liquidity_xyk() -> Weight { + (Weight::from_parts(150_050_000, 0)) + .saturating_add(T::DbWeight::get().reads(12 as u64)) + .saturating_add(T::DbWeight::get().writes(7 as u64)) + } + // Storage: `Maintenance::MaintenanceStatus` (r:1 w:0) + // Proof: `Maintenance::MaintenanceStatus` (`max_values`: Some(1), `max_size`: Some(2), added: 497, mode: `MaxEncodedLen`) + // Storage: `Xyk::LiquidityPools` (r:99 w:0) + // Proof: `Xyk::LiquidityPools` (`max_values`: None, `max_size`: Some(29), added: 2504, mode: `MaxEncodedLen`) + // Storage: `AssetRegistry::Metadata` (r:100 w:0) + // Proof: `AssetRegistry::Metadata` (`max_values`: None, `max_size`: Some(153), added: 2628, mode: `MaxEncodedLen`) + // Storage: `Xyk::LiquidityAssets` (r:99 w:0) + // Proof: `Xyk::LiquidityAssets` (`max_values`: None, `max_size`: Some(29), added: 2504, mode: `MaxEncodedLen`) + // Storage: `Tokens::TotalIssuance` (r:100 w:1) + // Proof: `Tokens::TotalIssuance` (`max_values`: None, `max_size`: Some(28), added: 2503, mode: `MaxEncodedLen`) + // Storage: `Xyk::Pools` (r:297 w:198) + // Proof: `Xyk::Pools` (`max_values`: None, `max_size`: Some(56), added: 2531, mode: `MaxEncodedLen`) + // Storage: `Tokens::Accounts` (r:400 w:400) + // Proof: `Tokens::Accounts` (`max_values`: None, `max_size`: Some(96), added: 2571, mode: `MaxEncodedLen`) + // Storage: `System::Account` (r:2 w:2) + // Proof: `System::Account` (`max_values`: None, `max_size`: Some(52), added: 2527, mode: `MaxEncodedLen`) + fn multiswap_asset_xyk(y: u32, ) -> Weight { + (Weight::from_parts(301_140_000, 0)) + // Standard Error: 451_112 + .saturating_add((Weight::from_parts(244_981_331, 0)).saturating_mul(y as u64)) + .saturating_add(T::DbWeight::get().reads(20 as u64)) + .saturating_add(T::DbWeight::get().reads((11 as u64).saturating_mul(y as u64))) + .saturating_add(T::DbWeight::get().writes(1 as u64)) + .saturating_add(T::DbWeight::get().writes((6 as u64).saturating_mul(y as u64))) + } + // Storage: `Maintenance::MaintenanceStatus` (r:1 w:0) + // Proof: `Maintenance::MaintenanceStatus` (`max_values`: Some(1), `max_size`: Some(2), added: 497, mode: `MaxEncodedLen`) + // Storage: `Xyk::LiquidityPools` (r:99 w:0) + // Proof: `Xyk::LiquidityPools` (`max_values`: None, `max_size`: Some(29), added: 2504, mode: `MaxEncodedLen`) + // Storage: `AssetRegistry::Metadata` (r:100 w:0) + // Proof: `AssetRegistry::Metadata` (`max_values`: None, `max_size`: Some(153), added: 2628, mode: `MaxEncodedLen`) + // Storage: `Xyk::Pools` (r:297 w:198) + // Proof: `Xyk::Pools` (`max_values`: None, `max_size`: Some(56), added: 2531, mode: `MaxEncodedLen`) + // Storage: `Xyk::LiquidityAssets` (r:99 w:0) + // Proof: `Xyk::LiquidityAssets` (`max_values`: None, `max_size`: Some(29), added: 2504, mode: `MaxEncodedLen`) + // Storage: `Tokens::TotalIssuance` (r:100 w:1) + // Proof: `Tokens::TotalIssuance` (`max_values`: None, `max_size`: Some(28), added: 2503, mode: `MaxEncodedLen`) + // Storage: `Tokens::Accounts` (r:400 w:400) + // Proof: `Tokens::Accounts` (`max_values`: None, `max_size`: Some(96), added: 2571, mode: `MaxEncodedLen`) + // Storage: `System::Account` (r:2 w:2) + // Proof: `System::Account` (`max_values`: None, `max_size`: Some(52), added: 2527, mode: `MaxEncodedLen`) + fn multiswap_asset_buy_xyk(y: u32, ) -> Weight { + (Weight::from_parts(324_330_000, 0)) + // Standard Error: 375_075 + .saturating_add((Weight::from_parts(255_932_380, 0)).saturating_mul(y as u64)) + .saturating_add(T::DbWeight::get().reads(20 as u64)) + .saturating_add(T::DbWeight::get().reads((11 as u64).saturating_mul(y as u64))) + .saturating_add(T::DbWeight::get().writes(1 as u64)) + .saturating_add(T::DbWeight::get().writes((6 as u64).saturating_mul(y as u64))) + } + // Storage: `AssetRegistry::Metadata` (r:3 w:1) + // Proof: `AssetRegistry::Metadata` (`max_values`: None, `max_size`: Some(153), added: 2628, mode: `MaxEncodedLen`) + // Storage: `Bootstrap::BootstrapSchedule` (r:1 w:0) + // Proof: `Bootstrap::BootstrapSchedule` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + // Storage: `Tokens::NextCurrencyId` (r:1 w:1) + // Proof: `Tokens::NextCurrencyId` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) + // Storage: `Tokens::Accounts` (r:5 w:5) + // Proof: `Tokens::Accounts` (`max_values`: None, `max_size`: Some(96), added: 2571, mode: `MaxEncodedLen`) + // Storage: `System::Account` (r:1 w:1) + // Proof: `System::Account` (`max_values`: None, `max_size`: Some(52), added: 2527, mode: `MaxEncodedLen`) + // Storage: `Tokens::TotalIssuance` (r:1 w:1) + // Proof: `Tokens::TotalIssuance` (`max_values`: None, `max_size`: Some(28), added: 2503, mode: `MaxEncodedLen`) + // Storage: `StableSwap::Pools` (r:0 w:1) + // Proof: `StableSwap::Pools` (`max_values`: None, `max_size`: Some(66), added: 2541, mode: `MaxEncodedLen`) + fn create_pool_sswap() -> Weight { + (Weight::from_parts(168_240_000, 0)) + .saturating_add(T::DbWeight::get().reads(12 as u64)) + .saturating_add(T::DbWeight::get().writes(10 as u64)) + } + // Storage: `Xyk::LiquidityPools` (r:1 w:0) + // Proof: `Xyk::LiquidityPools` (`max_values`: None, `max_size`: Some(29), added: 2504, mode: `MaxEncodedLen`) + // Storage: `StableSwap::Pools` (r:1 w:0) + // Proof: `StableSwap::Pools` (`max_values`: None, `max_size`: Some(66), added: 2541, mode: `MaxEncodedLen`) + // Storage: `AssetRegistry::Metadata` (r:2 w:0) + // Proof: `AssetRegistry::Metadata` (`max_values`: None, `max_size`: Some(153), added: 2628, mode: `MaxEncodedLen`) + // Storage: `Tokens::TotalIssuance` (r:1 w:1) + // Proof: `Tokens::TotalIssuance` (`max_values`: None, `max_size`: Some(28), added: 2503, mode: `MaxEncodedLen`) + // Storage: `Tokens::Accounts` (r:5 w:5) + // Proof: `Tokens::Accounts` (`max_values`: None, `max_size`: Some(96), added: 2571, mode: `MaxEncodedLen`) + // Storage: `Tokens::NextCurrencyId` (r:1 w:0) + // Proof: `Tokens::NextCurrencyId` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) + // Storage: `ProofOfStake::PromotedPoolRewards` (r:1 w:0) + // Proof: `ProofOfStake::PromotedPoolRewards` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + // Storage: `MultiPurposeLiquidity::ReserveStatus` (r:1 w:1) + // Proof: `MultiPurposeLiquidity::ReserveStatus` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`) + // Storage: `ProofOfStake::RewardsInfo` (r:1 w:1) + // Proof: `ProofOfStake::RewardsInfo` (`max_values`: None, `max_size`: None, mode: `Measured`) + // Storage: `ProofOfStake::TotalActivatedLiquidity` (r:1 w:1) + // Proof: `ProofOfStake::TotalActivatedLiquidity` (`max_values`: None, `max_size`: None, mode: `Measured`) + fn mint_liquidity_sswap() -> Weight { + (Weight::from_parts(211_870_000, 0)) + .saturating_add(T::DbWeight::get().reads(15 as u64)) + .saturating_add(T::DbWeight::get().writes(9 as u64)) + } + // Storage: `Xyk::LiquidityPools` (r:1 w:0) + // Proof: `Xyk::LiquidityPools` (`max_values`: None, `max_size`: Some(29), added: 2504, mode: `MaxEncodedLen`) + // Storage: `StableSwap::Pools` (r:1 w:0) + // Proof: `StableSwap::Pools` (`max_values`: None, `max_size`: Some(66), added: 2541, mode: `MaxEncodedLen`) + // Storage: `AssetRegistry::Metadata` (r:2 w:0) + // Proof: `AssetRegistry::Metadata` (`max_values`: None, `max_size`: Some(153), added: 2628, mode: `MaxEncodedLen`) + // Storage: `Tokens::TotalIssuance` (r:1 w:1) + // Proof: `Tokens::TotalIssuance` (`max_values`: None, `max_size`: Some(28), added: 2503, mode: `MaxEncodedLen`) + // Storage: `Tokens::Accounts` (r:6 w:6) + // Proof: `Tokens::Accounts` (`max_values`: None, `max_size`: Some(96), added: 2571, mode: `MaxEncodedLen`) + // Storage: `System::Account` (r:2 w:1) + // Proof: `System::Account` (`max_values`: None, `max_size`: Some(52), added: 2527, mode: `MaxEncodedLen`) + // Storage: `Tokens::NextCurrencyId` (r:1 w:0) + // Proof: `Tokens::NextCurrencyId` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) + // Storage: `ProofOfStake::PromotedPoolRewards` (r:1 w:0) + // Proof: `ProofOfStake::PromotedPoolRewards` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + // Storage: `MultiPurposeLiquidity::ReserveStatus` (r:1 w:1) + // Proof: `MultiPurposeLiquidity::ReserveStatus` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`) + // Storage: `ProofOfStake::RewardsInfo` (r:1 w:1) + // Proof: `ProofOfStake::RewardsInfo` (`max_values`: None, `max_size`: None, mode: `Measured`) + // Storage: `ProofOfStake::TotalActivatedLiquidity` (r:1 w:1) + // Proof: `ProofOfStake::TotalActivatedLiquidity` (`max_values`: None, `max_size`: None, mode: `Measured`) + fn mint_liquidity_fixed_amounts_sswap() -> Weight { + (Weight::from_parts(257_160_000, 0)) + .saturating_add(T::DbWeight::get().reads(18 as u64)) + .saturating_add(T::DbWeight::get().writes(11 as u64)) + } + // Storage: `Xyk::LiquidityPools` (r:1 w:0) + // Proof: `Xyk::LiquidityPools` (`max_values`: None, `max_size`: Some(29), added: 2504, mode: `MaxEncodedLen`) + // Storage: `StableSwap::Pools` (r:1 w:0) + // Proof: `StableSwap::Pools` (`max_values`: None, `max_size`: Some(66), added: 2541, mode: `MaxEncodedLen`) + // Storage: `AssetRegistry::Metadata` (r:2 w:0) + // Proof: `AssetRegistry::Metadata` (`max_values`: None, `max_size`: Some(153), added: 2628, mode: `MaxEncodedLen`) + // Storage: `ProofOfStake::PromotedPoolRewards` (r:1 w:0) + // Proof: `ProofOfStake::PromotedPoolRewards` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + // Storage: `Vesting::Vesting` (r:2 w:2) + // Proof: `Vesting::Vesting` (`max_values`: None, `max_size`: Some(1857), added: 4332, mode: `MaxEncodedLen`) + // Storage: `Tokens::Locks` (r:2 w:2) + // Proof: `Tokens::Locks` (`max_values`: None, `max_size`: Some(1249), added: 3724, mode: `MaxEncodedLen`) + // Storage: `Tokens::Accounts` (r:5 w:5) + // Proof: `Tokens::Accounts` (`max_values`: None, `max_size`: Some(96), added: 2571, mode: `MaxEncodedLen`) + // Storage: `Tokens::TotalIssuance` (r:1 w:1) + // Proof: `Tokens::TotalIssuance` (`max_values`: None, `max_size`: Some(28), added: 2503, mode: `MaxEncodedLen`) + // Storage: `Tokens::NextCurrencyId` (r:1 w:0) + // Proof: `Tokens::NextCurrencyId` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) + fn mint_liquidity_using_vesting_native_tokens_by_vesting_index_sswap() -> Weight { + (Weight::from_parts(231_400_000, 0)) + .saturating_add(T::DbWeight::get().reads(16 as u64)) + .saturating_add(T::DbWeight::get().writes(10 as u64)) + } + // Storage: `Xyk::LiquidityPools` (r:1 w:0) + // Proof: `Xyk::LiquidityPools` (`max_values`: None, `max_size`: Some(29), added: 2504, mode: `MaxEncodedLen`) + // Storage: `StableSwap::Pools` (r:1 w:0) + // Proof: `StableSwap::Pools` (`max_values`: None, `max_size`: Some(66), added: 2541, mode: `MaxEncodedLen`) + // Storage: `AssetRegistry::Metadata` (r:2 w:0) + // Proof: `AssetRegistry::Metadata` (`max_values`: None, `max_size`: Some(153), added: 2628, mode: `MaxEncodedLen`) + // Storage: `ProofOfStake::PromotedPoolRewards` (r:1 w:0) + // Proof: `ProofOfStake::PromotedPoolRewards` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + // Storage: `Vesting::Vesting` (r:2 w:2) + // Proof: `Vesting::Vesting` (`max_values`: None, `max_size`: Some(1857), added: 4332, mode: `MaxEncodedLen`) + // Storage: `Tokens::Locks` (r:2 w:2) + // Proof: `Tokens::Locks` (`max_values`: None, `max_size`: Some(1249), added: 3724, mode: `MaxEncodedLen`) + // Storage: `Tokens::Accounts` (r:5 w:5) + // Proof: `Tokens::Accounts` (`max_values`: None, `max_size`: Some(96), added: 2571, mode: `MaxEncodedLen`) + // Storage: `Tokens::TotalIssuance` (r:1 w:1) + // Proof: `Tokens::TotalIssuance` (`max_values`: None, `max_size`: Some(28), added: 2503, mode: `MaxEncodedLen`) + // Storage: `Tokens::NextCurrencyId` (r:1 w:0) + // Proof: `Tokens::NextCurrencyId` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) + fn mint_liquidity_using_vesting_native_tokens_sswap() -> Weight { + (Weight::from_parts(230_020_000, 0)) + .saturating_add(T::DbWeight::get().reads(16 as u64)) + .saturating_add(T::DbWeight::get().writes(10 as u64)) + } + // Storage: `Xyk::LiquidityPools` (r:1 w:0) + // Proof: `Xyk::LiquidityPools` (`max_values`: None, `max_size`: Some(29), added: 2504, mode: `MaxEncodedLen`) + // Storage: `StableSwap::Pools` (r:1 w:0) + // Proof: `StableSwap::Pools` (`max_values`: None, `max_size`: Some(66), added: 2541, mode: `MaxEncodedLen`) + // Storage: `AssetRegistry::Metadata` (r:2 w:0) + // Proof: `AssetRegistry::Metadata` (`max_values`: None, `max_size`: Some(153), added: 2628, mode: `MaxEncodedLen`) + // Storage: `Tokens::Accounts` (r:5 w:5) + // Proof: `Tokens::Accounts` (`max_values`: None, `max_size`: Some(96), added: 2571, mode: `MaxEncodedLen`) + // Storage: `Tokens::TotalIssuance` (r:1 w:1) + // Proof: `Tokens::TotalIssuance` (`max_values`: None, `max_size`: Some(28), added: 2503, mode: `MaxEncodedLen`) + // Storage: `System::Account` (r:1 w:0) + // Proof: `System::Account` (`max_values`: None, `max_size`: Some(52), added: 2527, mode: `MaxEncodedLen`) + fn burn_liquidity_sswap() -> Weight { + (Weight::from_parts(127_700_000, 0)) + .saturating_add(T::DbWeight::get().reads(11 as u64)) + .saturating_add(T::DbWeight::get().writes(6 as u64)) + } + // Storage: `Maintenance::MaintenanceStatus` (r:1 w:0) + // Proof: `Maintenance::MaintenanceStatus` (`max_values`: Some(1), `max_size`: Some(2), added: 497, mode: `MaxEncodedLen`) + // Storage: `Xyk::LiquidityPools` (r:99 w:0) + // Proof: `Xyk::LiquidityPools` (`max_values`: None, `max_size`: Some(29), added: 2504, mode: `MaxEncodedLen`) + // Storage: `StableSwap::Pools` (r:99 w:0) + // Proof: `StableSwap::Pools` (`max_values`: None, `max_size`: Some(66), added: 2541, mode: `MaxEncodedLen`) + // Storage: `AssetRegistry::Metadata` (r:100 w:0) + // Proof: `AssetRegistry::Metadata` (`max_values`: None, `max_size`: Some(153), added: 2628, mode: `MaxEncodedLen`) + // Storage: `Tokens::Accounts` (r:597 w:597) + // Proof: `Tokens::Accounts` (`max_values`: None, `max_size`: Some(96), added: 2571, mode: `MaxEncodedLen`) + // Storage: `System::Account` (r:101 w:2) + // Proof: `System::Account` (`max_values`: None, `max_size`: Some(52), added: 2527, mode: `MaxEncodedLen`) + // Storage: `Xyk::Pools` (r:198 w:99) + // Proof: `Xyk::Pools` (`max_values`: None, `max_size`: Some(56), added: 2531, mode: `MaxEncodedLen`) + // Storage: `Tokens::TotalIssuance` (r:1 w:1) + // Proof: `Tokens::TotalIssuance` (`max_values`: None, `max_size`: Some(28), added: 2503, mode: `MaxEncodedLen`) + fn multiswap_asset_sswap(y: u32, ) -> Weight { + (Weight::from_parts(285_870_000, 0)) + // Standard Error: 235_063 + .saturating_add((Weight::from_parts(219_219_563, 0)).saturating_mul(y as u64)) + .saturating_add(T::DbWeight::get().reads(20 as u64)) + .saturating_add(T::DbWeight::get().reads((12 as u64).saturating_mul(y as u64))) + .saturating_add(T::DbWeight::get().writes(13 as u64)) + .saturating_add(T::DbWeight::get().writes((7 as u64).saturating_mul(y as u64))) + } + // Storage: `Maintenance::MaintenanceStatus` (r:1 w:0) + // Proof: `Maintenance::MaintenanceStatus` (`max_values`: Some(1), `max_size`: Some(2), added: 497, mode: `MaxEncodedLen`) + // Storage: `Xyk::LiquidityPools` (r:99 w:0) + // Proof: `Xyk::LiquidityPools` (`max_values`: None, `max_size`: Some(29), added: 2504, mode: `MaxEncodedLen`) + // Storage: `StableSwap::Pools` (r:99 w:0) + // Proof: `StableSwap::Pools` (`max_values`: None, `max_size`: Some(66), added: 2541, mode: `MaxEncodedLen`) + // Storage: `AssetRegistry::Metadata` (r:100 w:0) + // Proof: `AssetRegistry::Metadata` (`max_values`: None, `max_size`: Some(153), added: 2628, mode: `MaxEncodedLen`) + // Storage: `Tokens::Accounts` (r:597 w:597) + // Proof: `Tokens::Accounts` (`max_values`: None, `max_size`: Some(96), added: 2571, mode: `MaxEncodedLen`) + // Storage: `System::Account` (r:101 w:2) + // Proof: `System::Account` (`max_values`: None, `max_size`: Some(52), added: 2527, mode: `MaxEncodedLen`) + // Storage: `Xyk::Pools` (r:198 w:99) + // Proof: `Xyk::Pools` (`max_values`: None, `max_size`: Some(56), added: 2531, mode: `MaxEncodedLen`) + // Storage: `Tokens::TotalIssuance` (r:1 w:1) + // Proof: `Tokens::TotalIssuance` (`max_values`: None, `max_size`: Some(28), added: 2503, mode: `MaxEncodedLen`) + fn multiswap_asset_buy_sswap(y: u32, ) -> Weight { + (Weight::from_parts(326_140_000, 0)) + // Standard Error: 277_369 + .saturating_add((Weight::from_parts(254_697_373, 0)).saturating_mul(y as u64)) + .saturating_add(T::DbWeight::get().reads(20 as u64)) + .saturating_add(T::DbWeight::get().reads((12 as u64).saturating_mul(y as u64))) + .saturating_add(T::DbWeight::get().writes(13 as u64)) + .saturating_add(T::DbWeight::get().writes((7 as u64).saturating_mul(y as u64))) + } +} + +// For backwards compatibility and tests +impl WeightInfo for () { + // Storage: `AssetRegistry::Metadata` (r:3 w:1) + // Proof: `AssetRegistry::Metadata` (`max_values`: None, `max_size`: Some(153), added: 2628, mode: `MaxEncodedLen`) + // Storage: `Bootstrap::BootstrapSchedule` (r:1 w:0) + // Proof: `Bootstrap::BootstrapSchedule` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + // Storage: `Xyk::Pools` (r:2 w:1) + // Proof: `Xyk::Pools` (`max_values`: None, `max_size`: Some(56), added: 2531, mode: `MaxEncodedLen`) + // Storage: `Tokens::Accounts` (r:5 w:5) + // Proof: `Tokens::Accounts` (`max_values`: None, `max_size`: Some(96), added: 2571, mode: `MaxEncodedLen`) + // Storage: `System::Account` (r:1 w:1) + // Proof: `System::Account` (`max_values`: None, `max_size`: Some(52), added: 2527, mode: `MaxEncodedLen`) + // Storage: `Tokens::NextCurrencyId` (r:1 w:1) + // Proof: `Tokens::NextCurrencyId` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) + // Storage: `Tokens::TotalIssuance` (r:1 w:1) + // Proof: `Tokens::TotalIssuance` (`max_values`: None, `max_size`: Some(28), added: 2503, mode: `MaxEncodedLen`) + // Storage: `Xyk::LiquidityAssets` (r:0 w:1) + // Proof: `Xyk::LiquidityAssets` (`max_values`: None, `max_size`: Some(29), added: 2504, mode: `MaxEncodedLen`) + // Storage: `Xyk::LiquidityPools` (r:0 w:1) + // Proof: `Xyk::LiquidityPools` (`max_values`: None, `max_size`: Some(29), added: 2504, mode: `MaxEncodedLen`) + fn create_pool_xyk() -> Weight { + (Weight::from_parts(168_069_000, 0)) + .saturating_add(RocksDbWeight::get().reads(14 as u64)) + .saturating_add(RocksDbWeight::get().writes(12 as u64)) + } + // Storage: `Xyk::LiquidityPools` (r:1 w:0) + // Proof: `Xyk::LiquidityPools` (`max_values`: None, `max_size`: Some(29), added: 2504, mode: `MaxEncodedLen`) + // Storage: `AssetRegistry::Metadata` (r:2 w:0) + // Proof: `AssetRegistry::Metadata` (`max_values`: None, `max_size`: Some(153), added: 2628, mode: `MaxEncodedLen`) + // Storage: `Xyk::LiquidityAssets` (r:1 w:0) + // Proof: `Xyk::LiquidityAssets` (`max_values`: None, `max_size`: Some(29), added: 2504, mode: `MaxEncodedLen`) + // Storage: `Xyk::Pools` (r:1 w:1) + // Proof: `Xyk::Pools` (`max_values`: None, `max_size`: Some(56), added: 2531, mode: `MaxEncodedLen`) + // Storage: `Tokens::TotalIssuance` (r:1 w:1) + // Proof: `Tokens::TotalIssuance` (`max_values`: None, `max_size`: Some(28), added: 2503, mode: `MaxEncodedLen`) + // Storage: `Tokens::Accounts` (r:5 w:5) + // Proof: `Tokens::Accounts` (`max_values`: None, `max_size`: Some(96), added: 2571, mode: `MaxEncodedLen`) + // Storage: `Tokens::NextCurrencyId` (r:1 w:0) + // Proof: `Tokens::NextCurrencyId` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) + // Storage: `ProofOfStake::PromotedPoolRewards` (r:1 w:0) + // Proof: `ProofOfStake::PromotedPoolRewards` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + // Storage: `MultiPurposeLiquidity::ReserveStatus` (r:1 w:1) + // Proof: `MultiPurposeLiquidity::ReserveStatus` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`) + // Storage: `ProofOfStake::RewardsInfo` (r:1 w:1) + // Proof: `ProofOfStake::RewardsInfo` (`max_values`: None, `max_size`: None, mode: `Measured`) + // Storage: `ProofOfStake::TotalActivatedLiquidity` (r:1 w:1) + // Proof: `ProofOfStake::TotalActivatedLiquidity` (`max_values`: None, `max_size`: None, mode: `Measured`) + fn mint_liquidity_xyk() -> Weight { + (Weight::from_parts(219_140_000, 0)) + .saturating_add(RocksDbWeight::get().reads(16 as u64)) + .saturating_add(RocksDbWeight::get().writes(10 as u64)) + } + // Storage: `Xyk::LiquidityPools` (r:1 w:0) + // Proof: `Xyk::LiquidityPools` (`max_values`: None, `max_size`: Some(29), added: 2504, mode: `MaxEncodedLen`) + // Storage: `AssetRegistry::Metadata` (r:2 w:0) + // Proof: `AssetRegistry::Metadata` (`max_values`: None, `max_size`: Some(153), added: 2628, mode: `MaxEncodedLen`) + // Storage: `Xyk::LiquidityAssets` (r:2 w:0) + // Proof: `Xyk::LiquidityAssets` (`max_values`: None, `max_size`: Some(29), added: 2504, mode: `MaxEncodedLen`) + // Storage: `Tokens::TotalIssuance` (r:1 w:1) + // Proof: `Tokens::TotalIssuance` (`max_values`: None, `max_size`: Some(28), added: 2503, mode: `MaxEncodedLen`) + // Storage: `Xyk::Pools` (r:4 w:1) + // Proof: `Xyk::Pools` (`max_values`: None, `max_size`: Some(56), added: 2531, mode: `MaxEncodedLen`) + // Storage: `Tokens::Accounts` (r:7 w:7) + // Proof: `Tokens::Accounts` (`max_values`: None, `max_size`: Some(96), added: 2571, mode: `MaxEncodedLen`) + // Storage: `Maintenance::MaintenanceStatus` (r:1 w:0) + // Proof: `Maintenance::MaintenanceStatus` (`max_values`: Some(1), `max_size`: Some(2), added: 497, mode: `MaxEncodedLen`) + // Storage: `System::Account` (r:2 w:2) + // Proof: `System::Account` (`max_values`: None, `max_size`: Some(52), added: 2527, mode: `MaxEncodedLen`) + // Storage: `Tokens::NextCurrencyId` (r:1 w:0) + // Proof: `Tokens::NextCurrencyId` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) + // Storage: `ProofOfStake::PromotedPoolRewards` (r:1 w:0) + // Proof: `ProofOfStake::PromotedPoolRewards` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + // Storage: `MultiPurposeLiquidity::ReserveStatus` (r:1 w:1) + // Proof: `MultiPurposeLiquidity::ReserveStatus` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`) + // Storage: `ProofOfStake::RewardsInfo` (r:1 w:1) + // Proof: `ProofOfStake::RewardsInfo` (`max_values`: None, `max_size`: None, mode: `Measured`) + // Storage: `ProofOfStake::TotalActivatedLiquidity` (r:1 w:1) + // Proof: `ProofOfStake::TotalActivatedLiquidity` (`max_values`: None, `max_size`: None, mode: `Measured`) + fn mint_liquidity_fixed_amounts_xyk() -> Weight { + (Weight::from_parts(398_450_000, 0)) + .saturating_add(RocksDbWeight::get().reads(25 as u64)) + .saturating_add(RocksDbWeight::get().writes(14 as u64)) + } + // Storage: `Xyk::LiquidityPools` (r:1 w:0) + // Proof: `Xyk::LiquidityPools` (`max_values`: None, `max_size`: Some(29), added: 2504, mode: `MaxEncodedLen`) + // Storage: `AssetRegistry::Metadata` (r:2 w:0) + // Proof: `AssetRegistry::Metadata` (`max_values`: None, `max_size`: Some(153), added: 2628, mode: `MaxEncodedLen`) + // Storage: `ProofOfStake::PromotedPoolRewards` (r:1 w:0) + // Proof: `ProofOfStake::PromotedPoolRewards` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + // Storage: `Vesting::Vesting` (r:2 w:2) + // Proof: `Vesting::Vesting` (`max_values`: None, `max_size`: Some(1857), added: 4332, mode: `MaxEncodedLen`) + // Storage: `Tokens::Locks` (r:2 w:2) + // Proof: `Tokens::Locks` (`max_values`: None, `max_size`: Some(1249), added: 3724, mode: `MaxEncodedLen`) + // Storage: `Tokens::Accounts` (r:5 w:5) + // Proof: `Tokens::Accounts` (`max_values`: None, `max_size`: Some(96), added: 2571, mode: `MaxEncodedLen`) + // Storage: `Xyk::LiquidityAssets` (r:1 w:0) + // Proof: `Xyk::LiquidityAssets` (`max_values`: None, `max_size`: Some(29), added: 2504, mode: `MaxEncodedLen`) + // Storage: `Xyk::Pools` (r:1 w:1) + // Proof: `Xyk::Pools` (`max_values`: None, `max_size`: Some(56), added: 2531, mode: `MaxEncodedLen`) + // Storage: `Tokens::TotalIssuance` (r:1 w:1) + // Proof: `Tokens::TotalIssuance` (`max_values`: None, `max_size`: Some(28), added: 2503, mode: `MaxEncodedLen`) + // Storage: `Tokens::NextCurrencyId` (r:1 w:0) + // Proof: `Tokens::NextCurrencyId` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) + fn mint_liquidity_using_vesting_native_tokens_by_vesting_index_xyk() -> Weight { + (Weight::from_parts(236_580_000, 0)) + .saturating_add(RocksDbWeight::get().reads(17 as u64)) + .saturating_add(RocksDbWeight::get().writes(11 as u64)) + } + // Storage: `Xyk::LiquidityPools` (r:1 w:0) + // Proof: `Xyk::LiquidityPools` (`max_values`: None, `max_size`: Some(29), added: 2504, mode: `MaxEncodedLen`) + // Storage: `AssetRegistry::Metadata` (r:2 w:0) + // Proof: `AssetRegistry::Metadata` (`max_values`: None, `max_size`: Some(153), added: 2628, mode: `MaxEncodedLen`) + // Storage: `ProofOfStake::PromotedPoolRewards` (r:1 w:0) + // Proof: `ProofOfStake::PromotedPoolRewards` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + // Storage: `Vesting::Vesting` (r:2 w:2) + // Proof: `Vesting::Vesting` (`max_values`: None, `max_size`: Some(1857), added: 4332, mode: `MaxEncodedLen`) + // Storage: `Tokens::Locks` (r:2 w:2) + // Proof: `Tokens::Locks` (`max_values`: None, `max_size`: Some(1249), added: 3724, mode: `MaxEncodedLen`) + // Storage: `Tokens::Accounts` (r:5 w:5) + // Proof: `Tokens::Accounts` (`max_values`: None, `max_size`: Some(96), added: 2571, mode: `MaxEncodedLen`) + // Storage: `Xyk::LiquidityAssets` (r:1 w:0) + // Proof: `Xyk::LiquidityAssets` (`max_values`: None, `max_size`: Some(29), added: 2504, mode: `MaxEncodedLen`) + // Storage: `Xyk::Pools` (r:1 w:1) + // Proof: `Xyk::Pools` (`max_values`: None, `max_size`: Some(56), added: 2531, mode: `MaxEncodedLen`) + // Storage: `Tokens::TotalIssuance` (r:1 w:1) + // Proof: `Tokens::TotalIssuance` (`max_values`: None, `max_size`: Some(28), added: 2503, mode: `MaxEncodedLen`) + // Storage: `Tokens::NextCurrencyId` (r:1 w:0) + // Proof: `Tokens::NextCurrencyId` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) + fn mint_liquidity_using_vesting_native_tokens_xyk() -> Weight { + (Weight::from_parts(235_820_000, 0)) + .saturating_add(RocksDbWeight::get().reads(17 as u64)) + .saturating_add(RocksDbWeight::get().writes(11 as u64)) + } + // Storage: `Xyk::LiquidityPools` (r:1 w:0) + // Proof: `Xyk::LiquidityPools` (`max_values`: None, `max_size`: Some(29), added: 2504, mode: `MaxEncodedLen`) + // Storage: `AssetRegistry::Metadata` (r:2 w:0) + // Proof: `AssetRegistry::Metadata` (`max_values`: None, `max_size`: Some(153), added: 2628, mode: `MaxEncodedLen`) + // Storage: `Xyk::LiquidityAssets` (r:1 w:0) + // Proof: `Xyk::LiquidityAssets` (`max_values`: None, `max_size`: Some(29), added: 2504, mode: `MaxEncodedLen`) + // Storage: `MultiPurposeLiquidity::ReserveStatus` (r:1 w:0) + // Proof: `MultiPurposeLiquidity::ReserveStatus` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`) + // Storage: `Xyk::Pools` (r:1 w:1) + // Proof: `Xyk::Pools` (`max_values`: None, `max_size`: Some(56), added: 2531, mode: `MaxEncodedLen`) + // Storage: `Tokens::Accounts` (r:5 w:5) + // Proof: `Tokens::Accounts` (`max_values`: None, `max_size`: Some(96), added: 2571, mode: `MaxEncodedLen`) + // Storage: `Tokens::TotalIssuance` (r:1 w:1) + // Proof: `Tokens::TotalIssuance` (`max_values`: None, `max_size`: Some(28), added: 2503, mode: `MaxEncodedLen`) + fn burn_liquidity_xyk() -> Weight { + (Weight::from_parts(150_050_000, 0)) + .saturating_add(RocksDbWeight::get().reads(12 as u64)) + .saturating_add(RocksDbWeight::get().writes(7 as u64)) + } + // Storage: `Maintenance::MaintenanceStatus` (r:1 w:0) + // Proof: `Maintenance::MaintenanceStatus` (`max_values`: Some(1), `max_size`: Some(2), added: 497, mode: `MaxEncodedLen`) + // Storage: `Xyk::LiquidityPools` (r:99 w:0) + // Proof: `Xyk::LiquidityPools` (`max_values`: None, `max_size`: Some(29), added: 2504, mode: `MaxEncodedLen`) + // Storage: `AssetRegistry::Metadata` (r:100 w:0) + // Proof: `AssetRegistry::Metadata` (`max_values`: None, `max_size`: Some(153), added: 2628, mode: `MaxEncodedLen`) + // Storage: `Xyk::LiquidityAssets` (r:99 w:0) + // Proof: `Xyk::LiquidityAssets` (`max_values`: None, `max_size`: Some(29), added: 2504, mode: `MaxEncodedLen`) + // Storage: `Tokens::TotalIssuance` (r:100 w:1) + // Proof: `Tokens::TotalIssuance` (`max_values`: None, `max_size`: Some(28), added: 2503, mode: `MaxEncodedLen`) + // Storage: `Xyk::Pools` (r:297 w:198) + // Proof: `Xyk::Pools` (`max_values`: None, `max_size`: Some(56), added: 2531, mode: `MaxEncodedLen`) + // Storage: `Tokens::Accounts` (r:400 w:400) + // Proof: `Tokens::Accounts` (`max_values`: None, `max_size`: Some(96), added: 2571, mode: `MaxEncodedLen`) + // Storage: `System::Account` (r:2 w:2) + // Proof: `System::Account` (`max_values`: None, `max_size`: Some(52), added: 2527, mode: `MaxEncodedLen`) + fn multiswap_asset_xyk(y: u32, ) -> Weight { + (Weight::from_parts(301_140_000, 0)) + // Standard Error: 451_112 + .saturating_add((Weight::from_parts(244_981_331, 0)).saturating_mul(y as u64)) + .saturating_add(RocksDbWeight::get().reads(20 as u64)) + .saturating_add(RocksDbWeight::get().reads((11 as u64).saturating_mul(y as u64))) + .saturating_add(RocksDbWeight::get().writes(1 as u64)) + .saturating_add(RocksDbWeight::get().writes((6 as u64).saturating_mul(y as u64))) + } + // Storage: `Maintenance::MaintenanceStatus` (r:1 w:0) + // Proof: `Maintenance::MaintenanceStatus` (`max_values`: Some(1), `max_size`: Some(2), added: 497, mode: `MaxEncodedLen`) + // Storage: `Xyk::LiquidityPools` (r:99 w:0) + // Proof: `Xyk::LiquidityPools` (`max_values`: None, `max_size`: Some(29), added: 2504, mode: `MaxEncodedLen`) + // Storage: `AssetRegistry::Metadata` (r:100 w:0) + // Proof: `AssetRegistry::Metadata` (`max_values`: None, `max_size`: Some(153), added: 2628, mode: `MaxEncodedLen`) + // Storage: `Xyk::Pools` (r:297 w:198) + // Proof: `Xyk::Pools` (`max_values`: None, `max_size`: Some(56), added: 2531, mode: `MaxEncodedLen`) + // Storage: `Xyk::LiquidityAssets` (r:99 w:0) + // Proof: `Xyk::LiquidityAssets` (`max_values`: None, `max_size`: Some(29), added: 2504, mode: `MaxEncodedLen`) + // Storage: `Tokens::TotalIssuance` (r:100 w:1) + // Proof: `Tokens::TotalIssuance` (`max_values`: None, `max_size`: Some(28), added: 2503, mode: `MaxEncodedLen`) + // Storage: `Tokens::Accounts` (r:400 w:400) + // Proof: `Tokens::Accounts` (`max_values`: None, `max_size`: Some(96), added: 2571, mode: `MaxEncodedLen`) + // Storage: `System::Account` (r:2 w:2) + // Proof: `System::Account` (`max_values`: None, `max_size`: Some(52), added: 2527, mode: `MaxEncodedLen`) + fn multiswap_asset_buy_xyk(y: u32, ) -> Weight { + (Weight::from_parts(324_330_000, 0)) + // Standard Error: 375_075 + .saturating_add((Weight::from_parts(255_932_380, 0)).saturating_mul(y as u64)) + .saturating_add(RocksDbWeight::get().reads(20 as u64)) + .saturating_add(RocksDbWeight::get().reads((11 as u64).saturating_mul(y as u64))) + .saturating_add(RocksDbWeight::get().writes(1 as u64)) + .saturating_add(RocksDbWeight::get().writes((6 as u64).saturating_mul(y as u64))) + } + // Storage: `AssetRegistry::Metadata` (r:3 w:1) + // Proof: `AssetRegistry::Metadata` (`max_values`: None, `max_size`: Some(153), added: 2628, mode: `MaxEncodedLen`) + // Storage: `Bootstrap::BootstrapSchedule` (r:1 w:0) + // Proof: `Bootstrap::BootstrapSchedule` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + // Storage: `Tokens::NextCurrencyId` (r:1 w:1) + // Proof: `Tokens::NextCurrencyId` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) + // Storage: `Tokens::Accounts` (r:5 w:5) + // Proof: `Tokens::Accounts` (`max_values`: None, `max_size`: Some(96), added: 2571, mode: `MaxEncodedLen`) + // Storage: `System::Account` (r:1 w:1) + // Proof: `System::Account` (`max_values`: None, `max_size`: Some(52), added: 2527, mode: `MaxEncodedLen`) + // Storage: `Tokens::TotalIssuance` (r:1 w:1) + // Proof: `Tokens::TotalIssuance` (`max_values`: None, `max_size`: Some(28), added: 2503, mode: `MaxEncodedLen`) + // Storage: `StableSwap::Pools` (r:0 w:1) + // Proof: `StableSwap::Pools` (`max_values`: None, `max_size`: Some(66), added: 2541, mode: `MaxEncodedLen`) + fn create_pool_sswap() -> Weight { + (Weight::from_parts(168_240_000, 0)) + .saturating_add(RocksDbWeight::get().reads(12 as u64)) + .saturating_add(RocksDbWeight::get().writes(10 as u64)) + } + // Storage: `Xyk::LiquidityPools` (r:1 w:0) + // Proof: `Xyk::LiquidityPools` (`max_values`: None, `max_size`: Some(29), added: 2504, mode: `MaxEncodedLen`) + // Storage: `StableSwap::Pools` (r:1 w:0) + // Proof: `StableSwap::Pools` (`max_values`: None, `max_size`: Some(66), added: 2541, mode: `MaxEncodedLen`) + // Storage: `AssetRegistry::Metadata` (r:2 w:0) + // Proof: `AssetRegistry::Metadata` (`max_values`: None, `max_size`: Some(153), added: 2628, mode: `MaxEncodedLen`) + // Storage: `Tokens::TotalIssuance` (r:1 w:1) + // Proof: `Tokens::TotalIssuance` (`max_values`: None, `max_size`: Some(28), added: 2503, mode: `MaxEncodedLen`) + // Storage: `Tokens::Accounts` (r:5 w:5) + // Proof: `Tokens::Accounts` (`max_values`: None, `max_size`: Some(96), added: 2571, mode: `MaxEncodedLen`) + // Storage: `Tokens::NextCurrencyId` (r:1 w:0) + // Proof: `Tokens::NextCurrencyId` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) + // Storage: `ProofOfStake::PromotedPoolRewards` (r:1 w:0) + // Proof: `ProofOfStake::PromotedPoolRewards` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + // Storage: `MultiPurposeLiquidity::ReserveStatus` (r:1 w:1) + // Proof: `MultiPurposeLiquidity::ReserveStatus` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`) + // Storage: `ProofOfStake::RewardsInfo` (r:1 w:1) + // Proof: `ProofOfStake::RewardsInfo` (`max_values`: None, `max_size`: None, mode: `Measured`) + // Storage: `ProofOfStake::TotalActivatedLiquidity` (r:1 w:1) + // Proof: `ProofOfStake::TotalActivatedLiquidity` (`max_values`: None, `max_size`: None, mode: `Measured`) + fn mint_liquidity_sswap() -> Weight { + (Weight::from_parts(211_870_000, 0)) + .saturating_add(RocksDbWeight::get().reads(15 as u64)) + .saturating_add(RocksDbWeight::get().writes(9 as u64)) + } + // Storage: `Xyk::LiquidityPools` (r:1 w:0) + // Proof: `Xyk::LiquidityPools` (`max_values`: None, `max_size`: Some(29), added: 2504, mode: `MaxEncodedLen`) + // Storage: `StableSwap::Pools` (r:1 w:0) + // Proof: `StableSwap::Pools` (`max_values`: None, `max_size`: Some(66), added: 2541, mode: `MaxEncodedLen`) + // Storage: `AssetRegistry::Metadata` (r:2 w:0) + // Proof: `AssetRegistry::Metadata` (`max_values`: None, `max_size`: Some(153), added: 2628, mode: `MaxEncodedLen`) + // Storage: `Tokens::TotalIssuance` (r:1 w:1) + // Proof: `Tokens::TotalIssuance` (`max_values`: None, `max_size`: Some(28), added: 2503, mode: `MaxEncodedLen`) + // Storage: `Tokens::Accounts` (r:6 w:6) + // Proof: `Tokens::Accounts` (`max_values`: None, `max_size`: Some(96), added: 2571, mode: `MaxEncodedLen`) + // Storage: `System::Account` (r:2 w:1) + // Proof: `System::Account` (`max_values`: None, `max_size`: Some(52), added: 2527, mode: `MaxEncodedLen`) + // Storage: `Tokens::NextCurrencyId` (r:1 w:0) + // Proof: `Tokens::NextCurrencyId` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) + // Storage: `ProofOfStake::PromotedPoolRewards` (r:1 w:0) + // Proof: `ProofOfStake::PromotedPoolRewards` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + // Storage: `MultiPurposeLiquidity::ReserveStatus` (r:1 w:1) + // Proof: `MultiPurposeLiquidity::ReserveStatus` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`) + // Storage: `ProofOfStake::RewardsInfo` (r:1 w:1) + // Proof: `ProofOfStake::RewardsInfo` (`max_values`: None, `max_size`: None, mode: `Measured`) + // Storage: `ProofOfStake::TotalActivatedLiquidity` (r:1 w:1) + // Proof: `ProofOfStake::TotalActivatedLiquidity` (`max_values`: None, `max_size`: None, mode: `Measured`) + fn mint_liquidity_fixed_amounts_sswap() -> Weight { + (Weight::from_parts(257_160_000, 0)) + .saturating_add(RocksDbWeight::get().reads(18 as u64)) + .saturating_add(RocksDbWeight::get().writes(11 as u64)) + } + // Storage: `Xyk::LiquidityPools` (r:1 w:0) + // Proof: `Xyk::LiquidityPools` (`max_values`: None, `max_size`: Some(29), added: 2504, mode: `MaxEncodedLen`) + // Storage: `StableSwap::Pools` (r:1 w:0) + // Proof: `StableSwap::Pools` (`max_values`: None, `max_size`: Some(66), added: 2541, mode: `MaxEncodedLen`) + // Storage: `AssetRegistry::Metadata` (r:2 w:0) + // Proof: `AssetRegistry::Metadata` (`max_values`: None, `max_size`: Some(153), added: 2628, mode: `MaxEncodedLen`) + // Storage: `ProofOfStake::PromotedPoolRewards` (r:1 w:0) + // Proof: `ProofOfStake::PromotedPoolRewards` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + // Storage: `Vesting::Vesting` (r:2 w:2) + // Proof: `Vesting::Vesting` (`max_values`: None, `max_size`: Some(1857), added: 4332, mode: `MaxEncodedLen`) + // Storage: `Tokens::Locks` (r:2 w:2) + // Proof: `Tokens::Locks` (`max_values`: None, `max_size`: Some(1249), added: 3724, mode: `MaxEncodedLen`) + // Storage: `Tokens::Accounts` (r:5 w:5) + // Proof: `Tokens::Accounts` (`max_values`: None, `max_size`: Some(96), added: 2571, mode: `MaxEncodedLen`) + // Storage: `Tokens::TotalIssuance` (r:1 w:1) + // Proof: `Tokens::TotalIssuance` (`max_values`: None, `max_size`: Some(28), added: 2503, mode: `MaxEncodedLen`) + // Storage: `Tokens::NextCurrencyId` (r:1 w:0) + // Proof: `Tokens::NextCurrencyId` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) + fn mint_liquidity_using_vesting_native_tokens_by_vesting_index_sswap() -> Weight { + (Weight::from_parts(231_400_000, 0)) + .saturating_add(RocksDbWeight::get().reads(16 as u64)) + .saturating_add(RocksDbWeight::get().writes(10 as u64)) + } + // Storage: `Xyk::LiquidityPools` (r:1 w:0) + // Proof: `Xyk::LiquidityPools` (`max_values`: None, `max_size`: Some(29), added: 2504, mode: `MaxEncodedLen`) + // Storage: `StableSwap::Pools` (r:1 w:0) + // Proof: `StableSwap::Pools` (`max_values`: None, `max_size`: Some(66), added: 2541, mode: `MaxEncodedLen`) + // Storage: `AssetRegistry::Metadata` (r:2 w:0) + // Proof: `AssetRegistry::Metadata` (`max_values`: None, `max_size`: Some(153), added: 2628, mode: `MaxEncodedLen`) + // Storage: `ProofOfStake::PromotedPoolRewards` (r:1 w:0) + // Proof: `ProofOfStake::PromotedPoolRewards` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + // Storage: `Vesting::Vesting` (r:2 w:2) + // Proof: `Vesting::Vesting` (`max_values`: None, `max_size`: Some(1857), added: 4332, mode: `MaxEncodedLen`) + // Storage: `Tokens::Locks` (r:2 w:2) + // Proof: `Tokens::Locks` (`max_values`: None, `max_size`: Some(1249), added: 3724, mode: `MaxEncodedLen`) + // Storage: `Tokens::Accounts` (r:5 w:5) + // Proof: `Tokens::Accounts` (`max_values`: None, `max_size`: Some(96), added: 2571, mode: `MaxEncodedLen`) + // Storage: `Tokens::TotalIssuance` (r:1 w:1) + // Proof: `Tokens::TotalIssuance` (`max_values`: None, `max_size`: Some(28), added: 2503, mode: `MaxEncodedLen`) + // Storage: `Tokens::NextCurrencyId` (r:1 w:0) + // Proof: `Tokens::NextCurrencyId` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) + fn mint_liquidity_using_vesting_native_tokens_sswap() -> Weight { + (Weight::from_parts(230_020_000, 0)) + .saturating_add(RocksDbWeight::get().reads(16 as u64)) + .saturating_add(RocksDbWeight::get().writes(10 as u64)) + } + // Storage: `Xyk::LiquidityPools` (r:1 w:0) + // Proof: `Xyk::LiquidityPools` (`max_values`: None, `max_size`: Some(29), added: 2504, mode: `MaxEncodedLen`) + // Storage: `StableSwap::Pools` (r:1 w:0) + // Proof: `StableSwap::Pools` (`max_values`: None, `max_size`: Some(66), added: 2541, mode: `MaxEncodedLen`) + // Storage: `AssetRegistry::Metadata` (r:2 w:0) + // Proof: `AssetRegistry::Metadata` (`max_values`: None, `max_size`: Some(153), added: 2628, mode: `MaxEncodedLen`) + // Storage: `Tokens::Accounts` (r:5 w:5) + // Proof: `Tokens::Accounts` (`max_values`: None, `max_size`: Some(96), added: 2571, mode: `MaxEncodedLen`) + // Storage: `Tokens::TotalIssuance` (r:1 w:1) + // Proof: `Tokens::TotalIssuance` (`max_values`: None, `max_size`: Some(28), added: 2503, mode: `MaxEncodedLen`) + // Storage: `System::Account` (r:1 w:0) + // Proof: `System::Account` (`max_values`: None, `max_size`: Some(52), added: 2527, mode: `MaxEncodedLen`) + fn burn_liquidity_sswap() -> Weight { + (Weight::from_parts(127_700_000, 0)) + .saturating_add(RocksDbWeight::get().reads(11 as u64)) + .saturating_add(RocksDbWeight::get().writes(6 as u64)) + } + // Storage: `Maintenance::MaintenanceStatus` (r:1 w:0) + // Proof: `Maintenance::MaintenanceStatus` (`max_values`: Some(1), `max_size`: Some(2), added: 497, mode: `MaxEncodedLen`) + // Storage: `Xyk::LiquidityPools` (r:99 w:0) + // Proof: `Xyk::LiquidityPools` (`max_values`: None, `max_size`: Some(29), added: 2504, mode: `MaxEncodedLen`) + // Storage: `StableSwap::Pools` (r:99 w:0) + // Proof: `StableSwap::Pools` (`max_values`: None, `max_size`: Some(66), added: 2541, mode: `MaxEncodedLen`) + // Storage: `AssetRegistry::Metadata` (r:100 w:0) + // Proof: `AssetRegistry::Metadata` (`max_values`: None, `max_size`: Some(153), added: 2628, mode: `MaxEncodedLen`) + // Storage: `Tokens::Accounts` (r:597 w:597) + // Proof: `Tokens::Accounts` (`max_values`: None, `max_size`: Some(96), added: 2571, mode: `MaxEncodedLen`) + // Storage: `System::Account` (r:101 w:2) + // Proof: `System::Account` (`max_values`: None, `max_size`: Some(52), added: 2527, mode: `MaxEncodedLen`) + // Storage: `Xyk::Pools` (r:198 w:99) + // Proof: `Xyk::Pools` (`max_values`: None, `max_size`: Some(56), added: 2531, mode: `MaxEncodedLen`) + // Storage: `Tokens::TotalIssuance` (r:1 w:1) + // Proof: `Tokens::TotalIssuance` (`max_values`: None, `max_size`: Some(28), added: 2503, mode: `MaxEncodedLen`) + fn multiswap_asset_sswap(y: u32, ) -> Weight { + (Weight::from_parts(285_870_000, 0)) + // Standard Error: 235_063 + .saturating_add((Weight::from_parts(219_219_563, 0)).saturating_mul(y as u64)) + .saturating_add(RocksDbWeight::get().reads(20 as u64)) + .saturating_add(RocksDbWeight::get().reads((12 as u64).saturating_mul(y as u64))) + .saturating_add(RocksDbWeight::get().writes(13 as u64)) + .saturating_add(RocksDbWeight::get().writes((7 as u64).saturating_mul(y as u64))) + } + // Storage: `Maintenance::MaintenanceStatus` (r:1 w:0) + // Proof: `Maintenance::MaintenanceStatus` (`max_values`: Some(1), `max_size`: Some(2), added: 497, mode: `MaxEncodedLen`) + // Storage: `Xyk::LiquidityPools` (r:99 w:0) + // Proof: `Xyk::LiquidityPools` (`max_values`: None, `max_size`: Some(29), added: 2504, mode: `MaxEncodedLen`) + // Storage: `StableSwap::Pools` (r:99 w:0) + // Proof: `StableSwap::Pools` (`max_values`: None, `max_size`: Some(66), added: 2541, mode: `MaxEncodedLen`) + // Storage: `AssetRegistry::Metadata` (r:100 w:0) + // Proof: `AssetRegistry::Metadata` (`max_values`: None, `max_size`: Some(153), added: 2628, mode: `MaxEncodedLen`) + // Storage: `Tokens::Accounts` (r:597 w:597) + // Proof: `Tokens::Accounts` (`max_values`: None, `max_size`: Some(96), added: 2571, mode: `MaxEncodedLen`) + // Storage: `System::Account` (r:101 w:2) + // Proof: `System::Account` (`max_values`: None, `max_size`: Some(52), added: 2527, mode: `MaxEncodedLen`) + // Storage: `Xyk::Pools` (r:198 w:99) + // Proof: `Xyk::Pools` (`max_values`: None, `max_size`: Some(56), added: 2531, mode: `MaxEncodedLen`) + // Storage: `Tokens::TotalIssuance` (r:1 w:1) + // Proof: `Tokens::TotalIssuance` (`max_values`: None, `max_size`: Some(28), added: 2503, mode: `MaxEncodedLen`) + fn multiswap_asset_buy_sswap(y: u32, ) -> Weight { + (Weight::from_parts(326_140_000, 0)) + // Standard Error: 277_369 + .saturating_add((Weight::from_parts(254_697_373, 0)).saturating_mul(y as u64)) + .saturating_add(RocksDbWeight::get().reads(20 as u64)) + .saturating_add(RocksDbWeight::get().reads((12 as u64).saturating_mul(y as u64))) + .saturating_add(RocksDbWeight::get().writes(13 as u64)) + .saturating_add(RocksDbWeight::get().writes((7 as u64).saturating_mul(y as u64))) + } +} diff --git a/gasp-node/rollup/runtime/src/weights/pallet_multipurpose_liquidity.rs b/gasp-node/rollup/runtime/src/weights/pallet_multipurpose_liquidity.rs new file mode 100644 index 000000000..a249e89ef --- /dev/null +++ b/gasp-node/rollup/runtime/src/weights/pallet_multipurpose_liquidity.rs @@ -0,0 +1,132 @@ +// This file is part of Mangata. + +// Copyright (C) 2020-2022 Mangata Foundation. +// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 + +// This program 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. + +// This program 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 this program. If not, see . + +//! Autogenerated weights for pallet_multipurpose_liquidity +//! +//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 32.0.0 +//! DATE: 2024-11-29, STEPS: `50`, REPEAT: 20, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! EXECUTION: , WASM-EXECUTION: Compiled, CHAIN: Some("rollup-local"), DB CACHE: 1024 + +// Executed Command: +// target/release/rollup-node +// benchmark +// pallet +// -l=info,runtime::collective=warn,xyk=warn +// --chain +// rollup-local +// --wasm-execution +// compiled +// --pallet +// * +// --extrinsic +// * +// --steps +// 50 +// --repeat +// 20 +// --template +// ./templates/module-weight-template.hbs +// --output +// ./benchmarks/ + +#![cfg_attr(rustfmt, rustfmt_skip)] +#![allow(unused_parens)] +#![allow(unused_imports)] +#![allow(clippy::unnecessary_cast)] + +use frame_support::{traits::Get, weights::{Weight, constants::RocksDbWeight}}; +use sp_std::marker::PhantomData; + +/// Weight functions needed for pallet_multipurpose_liquidity. +pub trait WeightInfo { + fn reserve_vesting_liquidity_tokens() -> Weight; + fn unreserve_and_relock_instance() -> Weight; +} + +/// Weights for pallet_multipurpose_liquidity using the Mangata node and recommended hardware. +pub struct ModuleWeight(PhantomData); +impl pallet_multipurpose_liquidity::WeightInfo for ModuleWeight { + // Storage: `Xyk::LiquidityPools` (r:1 w:0) + // Proof: `Xyk::LiquidityPools` (`max_values`: None, `max_size`: Some(29), added: 2504, mode: `MaxEncodedLen`) + // Storage: `Vesting::Vesting` (r:1 w:1) + // Proof: `Vesting::Vesting` (`max_values`: None, `max_size`: Some(1857), added: 4332, mode: `MaxEncodedLen`) + // Storage: `Tokens::Locks` (r:1 w:1) + // Proof: `Tokens::Locks` (`max_values`: None, `max_size`: Some(1249), added: 3724, mode: `MaxEncodedLen`) + // Storage: `Tokens::Accounts` (r:1 w:1) + // Proof: `Tokens::Accounts` (`max_values`: None, `max_size`: Some(96), added: 2571, mode: `MaxEncodedLen`) + // Storage: `MultiPurposeLiquidity::ReserveStatus` (r:1 w:1) + // Proof: `MultiPurposeLiquidity::ReserveStatus` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`) + // Storage: `MultiPurposeLiquidity::RelockStatus` (r:1 w:1) + // Proof: `MultiPurposeLiquidity::RelockStatus` (`max_values`: None, `max_size`: Some(1849), added: 4324, mode: `MaxEncodedLen`) + fn reserve_vesting_liquidity_tokens() -> Weight { + (Weight::from_parts(105_780_000, 0)) + .saturating_add(T::DbWeight::get().reads(6 as u64)) + .saturating_add(T::DbWeight::get().writes(5 as u64)) + } + // Storage: `MultiPurposeLiquidity::RelockStatus` (r:1 w:1) + // Proof: `MultiPurposeLiquidity::RelockStatus` (`max_values`: None, `max_size`: Some(1849), added: 4324, mode: `MaxEncodedLen`) + // Storage: `MultiPurposeLiquidity::ReserveStatus` (r:1 w:1) + // Proof: `MultiPurposeLiquidity::ReserveStatus` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`) + // Storage: `Tokens::Accounts` (r:1 w:1) + // Proof: `Tokens::Accounts` (`max_values`: None, `max_size`: Some(96), added: 2571, mode: `MaxEncodedLen`) + // Storage: `Vesting::Vesting` (r:1 w:1) + // Proof: `Vesting::Vesting` (`max_values`: None, `max_size`: Some(1857), added: 4332, mode: `MaxEncodedLen`) + // Storage: `Tokens::Locks` (r:1 w:1) + // Proof: `Tokens::Locks` (`max_values`: None, `max_size`: Some(1249), added: 3724, mode: `MaxEncodedLen`) + fn unreserve_and_relock_instance() -> Weight { + (Weight::from_parts(100_320_000, 0)) + .saturating_add(T::DbWeight::get().reads(5 as u64)) + .saturating_add(T::DbWeight::get().writes(5 as u64)) + } +} + +// For backwards compatibility and tests +impl WeightInfo for () { + // Storage: `Xyk::LiquidityPools` (r:1 w:0) + // Proof: `Xyk::LiquidityPools` (`max_values`: None, `max_size`: Some(29), added: 2504, mode: `MaxEncodedLen`) + // Storage: `Vesting::Vesting` (r:1 w:1) + // Proof: `Vesting::Vesting` (`max_values`: None, `max_size`: Some(1857), added: 4332, mode: `MaxEncodedLen`) + // Storage: `Tokens::Locks` (r:1 w:1) + // Proof: `Tokens::Locks` (`max_values`: None, `max_size`: Some(1249), added: 3724, mode: `MaxEncodedLen`) + // Storage: `Tokens::Accounts` (r:1 w:1) + // Proof: `Tokens::Accounts` (`max_values`: None, `max_size`: Some(96), added: 2571, mode: `MaxEncodedLen`) + // Storage: `MultiPurposeLiquidity::ReserveStatus` (r:1 w:1) + // Proof: `MultiPurposeLiquidity::ReserveStatus` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`) + // Storage: `MultiPurposeLiquidity::RelockStatus` (r:1 w:1) + // Proof: `MultiPurposeLiquidity::RelockStatus` (`max_values`: None, `max_size`: Some(1849), added: 4324, mode: `MaxEncodedLen`) + fn reserve_vesting_liquidity_tokens() -> Weight { + (Weight::from_parts(105_780_000, 0)) + .saturating_add(RocksDbWeight::get().reads(6 as u64)) + .saturating_add(RocksDbWeight::get().writes(5 as u64)) + } + // Storage: `MultiPurposeLiquidity::RelockStatus` (r:1 w:1) + // Proof: `MultiPurposeLiquidity::RelockStatus` (`max_values`: None, `max_size`: Some(1849), added: 4324, mode: `MaxEncodedLen`) + // Storage: `MultiPurposeLiquidity::ReserveStatus` (r:1 w:1) + // Proof: `MultiPurposeLiquidity::ReserveStatus` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`) + // Storage: `Tokens::Accounts` (r:1 w:1) + // Proof: `Tokens::Accounts` (`max_values`: None, `max_size`: Some(96), added: 2571, mode: `MaxEncodedLen`) + // Storage: `Vesting::Vesting` (r:1 w:1) + // Proof: `Vesting::Vesting` (`max_values`: None, `max_size`: Some(1857), added: 4332, mode: `MaxEncodedLen`) + // Storage: `Tokens::Locks` (r:1 w:1) + // Proof: `Tokens::Locks` (`max_values`: None, `max_size`: Some(1249), added: 3724, mode: `MaxEncodedLen`) + fn unreserve_and_relock_instance() -> Weight { + (Weight::from_parts(100_320_000, 0)) + .saturating_add(RocksDbWeight::get().reads(5 as u64)) + .saturating_add(RocksDbWeight::get().writes(5 as u64)) + } +} diff --git a/gasp-node/rollup/runtime/src/weights/pallet_proof_of_stake.rs b/gasp-node/rollup/runtime/src/weights/pallet_proof_of_stake.rs new file mode 100644 index 000000000..da3580711 --- /dev/null +++ b/gasp-node/rollup/runtime/src/weights/pallet_proof_of_stake.rs @@ -0,0 +1,346 @@ +// This file is part of Mangata. + +// Copyright (C) 2020-2022 Mangata Foundation. +// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 + +// This program 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. + +// This program 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 this program. If not, see . + +//! Autogenerated weights for pallet_proof_of_stake +//! +//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 32.0.0 +//! DATE: 2024-11-29, STEPS: `50`, REPEAT: 20, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! EXECUTION: , WASM-EXECUTION: Compiled, CHAIN: Some("rollup-local"), DB CACHE: 1024 + +// Executed Command: +// target/release/rollup-node +// benchmark +// pallet +// -l=info,runtime::collective=warn,xyk=warn +// --chain +// rollup-local +// --wasm-execution +// compiled +// --pallet +// * +// --extrinsic +// * +// --steps +// 50 +// --repeat +// 20 +// --template +// ./templates/module-weight-template.hbs +// --output +// ./benchmarks/ + +#![cfg_attr(rustfmt, rustfmt_skip)] +#![allow(unused_parens)] +#![allow(unused_imports)] +#![allow(clippy::unnecessary_cast)] + +use frame_support::{traits::Get, weights::{Weight, constants::RocksDbWeight}}; +use sp_std::marker::PhantomData; + +/// Weight functions needed for pallet_proof_of_stake. +pub trait WeightInfo { + fn claim_native_rewards() -> Weight; + fn update_pool_promotion() -> Weight; + fn activate_liquidity_for_native_rewards() -> Weight; + fn deactivate_liquidity_for_native_rewards() -> Weight; + fn reward_pool() -> Weight; + fn activate_liquidity_for_3rdparty_rewards() -> Weight; + fn deactivate_liquidity_for_3rdparty_rewards() -> Weight; + fn claim_3rdparty_rewards() -> Weight; +} + +/// Weights for pallet_proof_of_stake using the Mangata node and recommended hardware. +pub struct ModuleWeight(PhantomData); +impl pallet_proof_of_stake::WeightInfo for ModuleWeight { + // Storage: `ProofOfStake::PromotedPoolRewards` (r:1 w:0) + // Proof: `ProofOfStake::PromotedPoolRewards` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + // Storage: `ProofOfStake::RewardsInfo` (r:1 w:1) + // Proof: `ProofOfStake::RewardsInfo` (`max_values`: None, `max_size`: None, mode: `Measured`) + // Storage: `Tokens::Accounts` (r:2 w:2) + // Proof: `Tokens::Accounts` (`max_values`: None, `max_size`: Some(96), added: 2571, mode: `MaxEncodedLen`) + fn claim_native_rewards() -> Weight { + (Weight::from_parts(80_830_000, 0)) + .saturating_add(T::DbWeight::get().reads(4 as u64)) + .saturating_add(T::DbWeight::get().writes(3 as u64)) + } + // Storage: `Xyk::LiquidityPools` (r:1 w:0) + // Proof: `Xyk::LiquidityPools` (`max_values`: None, `max_size`: Some(29), added: 2504, mode: `MaxEncodedLen`) + // Storage: `ProofOfStake::PromotedPoolRewards` (r:1 w:1) + // Proof: `ProofOfStake::PromotedPoolRewards` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + fn update_pool_promotion() -> Weight { + (Weight::from_parts(20_890_000, 0)) + .saturating_add(T::DbWeight::get().reads(2 as u64)) + .saturating_add(T::DbWeight::get().writes(1 as u64)) + } + // Storage: `ProofOfStake::PromotedPoolRewards` (r:1 w:0) + // Proof: `ProofOfStake::PromotedPoolRewards` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + // Storage: `MultiPurposeLiquidity::ReserveStatus` (r:1 w:1) + // Proof: `MultiPurposeLiquidity::ReserveStatus` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`) + // Storage: `Tokens::Accounts` (r:1 w:1) + // Proof: `Tokens::Accounts` (`max_values`: None, `max_size`: Some(96), added: 2571, mode: `MaxEncodedLen`) + // Storage: `ProofOfStake::RewardsInfo` (r:1 w:1) + // Proof: `ProofOfStake::RewardsInfo` (`max_values`: None, `max_size`: None, mode: `Measured`) + // Storage: `ProofOfStake::TotalActivatedLiquidity` (r:1 w:1) + // Proof: `ProofOfStake::TotalActivatedLiquidity` (`max_values`: None, `max_size`: None, mode: `Measured`) + fn activate_liquidity_for_native_rewards() -> Weight { + (Weight::from_parts(90_249_000, 0)) + .saturating_add(T::DbWeight::get().reads(5 as u64)) + .saturating_add(T::DbWeight::get().writes(4 as u64)) + } + // Storage: `ProofOfStake::PromotedPoolRewards` (r:1 w:0) + // Proof: `ProofOfStake::PromotedPoolRewards` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + // Storage: `ProofOfStake::RewardsInfo` (r:1 w:1) + // Proof: `ProofOfStake::RewardsInfo` (`max_values`: None, `max_size`: None, mode: `Measured`) + // Storage: `ProofOfStake::ActivatedNativeRewardsLiq` (r:1 w:0) + // Proof: `ProofOfStake::ActivatedNativeRewardsLiq` (`max_values`: None, `max_size`: None, mode: `Measured`) + // Storage: `ProofOfStake::TotalActivatedLiquidity` (r:1 w:1) + // Proof: `ProofOfStake::TotalActivatedLiquidity` (`max_values`: None, `max_size`: None, mode: `Measured`) + // Storage: `MultiPurposeLiquidity::ReserveStatus` (r:1 w:1) + // Proof: `MultiPurposeLiquidity::ReserveStatus` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`) + // Storage: `Tokens::Accounts` (r:1 w:1) + // Proof: `Tokens::Accounts` (`max_values`: None, `max_size`: Some(96), added: 2571, mode: `MaxEncodedLen`) + fn deactivate_liquidity_for_native_rewards() -> Weight { + (Weight::from_parts(80_270_000, 0)) + .saturating_add(T::DbWeight::get().reads(6 as u64)) + .saturating_add(T::DbWeight::get().writes(4 as u64)) + } + // Storage: `Xyk::LiquidityAssets` (r:1 w:0) + // Proof: `Xyk::LiquidityAssets` (`max_values`: None, `max_size`: Some(29), added: 2504, mode: `MaxEncodedLen`) + // Storage: `Xyk::LiquidityPools` (r:1 w:0) + // Proof: `Xyk::LiquidityPools` (`max_values`: None, `max_size`: Some(29), added: 2504, mode: `MaxEncodedLen`) + // Storage: `Xyk::Pools` (r:1 w:0) + // Proof: `Xyk::Pools` (`max_values`: None, `max_size`: Some(56), added: 2531, mode: `MaxEncodedLen`) + // Storage: `Tokens::TotalIssuance` (r:1 w:0) + // Proof: `Tokens::TotalIssuance` (`max_values`: None, `max_size`: Some(28), added: 2503, mode: `MaxEncodedLen`) + // Storage: `Tokens::Accounts` (r:2 w:2) + // Proof: `Tokens::Accounts` (`max_values`: None, `max_size`: Some(96), added: 2571, mode: `MaxEncodedLen`) + // Storage: `System::Account` (r:1 w:1) + // Proof: `System::Account` (`max_values`: None, `max_size`: Some(52), added: 2527, mode: `MaxEncodedLen`) + // Storage: `ProofOfStake::SchedulesListMetadata` (r:1 w:1) + // Proof: `ProofOfStake::SchedulesListMetadata` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + // Storage: `ProofOfStake::RewardsSchedulesList` (r:1 w:2) + // Proof: `ProofOfStake::RewardsSchedulesList` (`max_values`: None, `max_size`: None, mode: `Measured`) + // Storage: `ProofOfStake::RewardTokensPerPool` (r:0 w:1) + // Proof: `ProofOfStake::RewardTokensPerPool` (`max_values`: None, `max_size`: None, mode: `Measured`) + fn reward_pool() -> Weight { + (Weight::from_parts(149_310_000, 0)) + .saturating_add(T::DbWeight::get().reads(9 as u64)) + .saturating_add(T::DbWeight::get().writes(7 as u64)) + } + // Storage: `ProofOfStake::RewardTokensPerPool` (r:2 w:0) + // Proof: `ProofOfStake::RewardTokensPerPool` (`max_values`: None, `max_size`: None, mode: `Measured`) + // Storage: `MultiPurposeLiquidity::ReserveStatus` (r:1 w:1) + // Proof: `MultiPurposeLiquidity::ReserveStatus` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`) + // Storage: `Tokens::Accounts` (r:1 w:1) + // Proof: `Tokens::Accounts` (`max_values`: None, `max_size`: Some(96), added: 2571, mode: `MaxEncodedLen`) + // Storage: `ProofOfStake::ActivatedLockedLiquidityForSchedules` (r:1 w:1) + // Proof: `ProofOfStake::ActivatedLockedLiquidityForSchedules` (`max_values`: None, `max_size`: None, mode: `Measured`) + // Storage: `ProofOfStake::ScheduleRewardsPerLiquidity` (r:1 w:0) + // Proof: `ProofOfStake::ScheduleRewardsPerLiquidity` (`max_values`: None, `max_size`: None, mode: `Measured`) + // Storage: `ProofOfStake::RewardsInfoForScheduleRewards` (r:1 w:1) + // Proof: `ProofOfStake::RewardsInfoForScheduleRewards` (`max_values`: None, `max_size`: None, mode: `Measured`) + // Storage: `ProofOfStake::ActivatedLiquidityForSchedules` (r:1 w:1) + // Proof: `ProofOfStake::ActivatedLiquidityForSchedules` (`max_values`: None, `max_size`: None, mode: `Measured`) + // Storage: `ProofOfStake::TotalActivatedLiquidityForSchedules` (r:1 w:1) + // Proof: `ProofOfStake::TotalActivatedLiquidityForSchedules` (`max_values`: None, `max_size`: None, mode: `Measured`) + fn activate_liquidity_for_3rdparty_rewards() -> Weight { + (Weight::from_parts(110_431_000, 0)) + .saturating_add(T::DbWeight::get().reads(9 as u64)) + .saturating_add(T::DbWeight::get().writes(6 as u64)) + } + // Storage: `ProofOfStake::RewardTokensPerPool` (r:2 w:0) + // Proof: `ProofOfStake::RewardTokensPerPool` (`max_values`: None, `max_size`: None, mode: `Measured`) + // Storage: `ProofOfStake::ScheduleRewardsPerLiquidity` (r:1 w:0) + // Proof: `ProofOfStake::ScheduleRewardsPerLiquidity` (`max_values`: None, `max_size`: None, mode: `Measured`) + // Storage: `ProofOfStake::RewardsInfoForScheduleRewards` (r:1 w:1) + // Proof: `ProofOfStake::RewardsInfoForScheduleRewards` (`max_values`: None, `max_size`: None, mode: `Measured`) + // Storage: `ProofOfStake::TotalActivatedLiquidityForSchedules` (r:1 w:1) + // Proof: `ProofOfStake::TotalActivatedLiquidityForSchedules` (`max_values`: None, `max_size`: None, mode: `Measured`) + // Storage: `ProofOfStake::ActivatedLiquidityForSchedules` (r:2 w:1) + // Proof: `ProofOfStake::ActivatedLiquidityForSchedules` (`max_values`: None, `max_size`: None, mode: `Measured`) + // Storage: `ProofOfStake::ActivatedLockedLiquidityForSchedules` (r:1 w:1) + // Proof: `ProofOfStake::ActivatedLockedLiquidityForSchedules` (`max_values`: None, `max_size`: None, mode: `Measured`) + // Storage: `MultiPurposeLiquidity::ReserveStatus` (r:1 w:1) + // Proof: `MultiPurposeLiquidity::ReserveStatus` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`) + // Storage: `Tokens::Accounts` (r:1 w:1) + // Proof: `Tokens::Accounts` (`max_values`: None, `max_size`: Some(96), added: 2571, mode: `MaxEncodedLen`) + // Storage: `ProofOfStake::ActivatedNativeRewardsLiq` (r:1 w:1) + // Proof: `ProofOfStake::ActivatedNativeRewardsLiq` (`max_values`: None, `max_size`: None, mode: `Measured`) + fn deactivate_liquidity_for_3rdparty_rewards() -> Weight { + (Weight::from_parts(118_440_000, 0)) + .saturating_add(T::DbWeight::get().reads(11 as u64)) + .saturating_add(T::DbWeight::get().writes(7 as u64)) + } + // Storage: `ProofOfStake::ScheduleRewardsPerLiquidity` (r:1 w:1) + // Proof: `ProofOfStake::ScheduleRewardsPerLiquidity` (`max_values`: None, `max_size`: None, mode: `Measured`) + // Storage: `ProofOfStake::TotalActivatedLiquidityForSchedules` (r:1 w:0) + // Proof: `ProofOfStake::TotalActivatedLiquidityForSchedules` (`max_values`: None, `max_size`: None, mode: `Measured`) + // Storage: `ProofOfStake::ScheduleRewardsTotal` (r:1 w:1) + // Proof: `ProofOfStake::ScheduleRewardsTotal` (`max_values`: None, `max_size`: None, mode: `Measured`) + // Storage: `ProofOfStake::RewardTokensPerPool` (r:2 w:0) + // Proof: `ProofOfStake::RewardTokensPerPool` (`max_values`: None, `max_size`: None, mode: `Measured`) + // Storage: `ProofOfStake::RewardsInfoForScheduleRewards` (r:1 w:1) + // Proof: `ProofOfStake::RewardsInfoForScheduleRewards` (`max_values`: None, `max_size`: None, mode: `Measured`) + // Storage: `Tokens::Accounts` (r:2 w:2) + // Proof: `Tokens::Accounts` (`max_values`: None, `max_size`: Some(96), added: 2571, mode: `MaxEncodedLen`) + fn claim_3rdparty_rewards() -> Weight { + (Weight::from_parts(132_200_000, 0)) + .saturating_add(T::DbWeight::get().reads(8 as u64)) + .saturating_add(T::DbWeight::get().writes(5 as u64)) + } +} + +// For backwards compatibility and tests +impl WeightInfo for () { + // Storage: `ProofOfStake::PromotedPoolRewards` (r:1 w:0) + // Proof: `ProofOfStake::PromotedPoolRewards` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + // Storage: `ProofOfStake::RewardsInfo` (r:1 w:1) + // Proof: `ProofOfStake::RewardsInfo` (`max_values`: None, `max_size`: None, mode: `Measured`) + // Storage: `Tokens::Accounts` (r:2 w:2) + // Proof: `Tokens::Accounts` (`max_values`: None, `max_size`: Some(96), added: 2571, mode: `MaxEncodedLen`) + fn claim_native_rewards() -> Weight { + (Weight::from_parts(80_830_000, 0)) + .saturating_add(RocksDbWeight::get().reads(4 as u64)) + .saturating_add(RocksDbWeight::get().writes(3 as u64)) + } + // Storage: `Xyk::LiquidityPools` (r:1 w:0) + // Proof: `Xyk::LiquidityPools` (`max_values`: None, `max_size`: Some(29), added: 2504, mode: `MaxEncodedLen`) + // Storage: `ProofOfStake::PromotedPoolRewards` (r:1 w:1) + // Proof: `ProofOfStake::PromotedPoolRewards` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + fn update_pool_promotion() -> Weight { + (Weight::from_parts(20_890_000, 0)) + .saturating_add(RocksDbWeight::get().reads(2 as u64)) + .saturating_add(RocksDbWeight::get().writes(1 as u64)) + } + // Storage: `ProofOfStake::PromotedPoolRewards` (r:1 w:0) + // Proof: `ProofOfStake::PromotedPoolRewards` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + // Storage: `MultiPurposeLiquidity::ReserveStatus` (r:1 w:1) + // Proof: `MultiPurposeLiquidity::ReserveStatus` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`) + // Storage: `Tokens::Accounts` (r:1 w:1) + // Proof: `Tokens::Accounts` (`max_values`: None, `max_size`: Some(96), added: 2571, mode: `MaxEncodedLen`) + // Storage: `ProofOfStake::RewardsInfo` (r:1 w:1) + // Proof: `ProofOfStake::RewardsInfo` (`max_values`: None, `max_size`: None, mode: `Measured`) + // Storage: `ProofOfStake::TotalActivatedLiquidity` (r:1 w:1) + // Proof: `ProofOfStake::TotalActivatedLiquidity` (`max_values`: None, `max_size`: None, mode: `Measured`) + fn activate_liquidity_for_native_rewards() -> Weight { + (Weight::from_parts(90_249_000, 0)) + .saturating_add(RocksDbWeight::get().reads(5 as u64)) + .saturating_add(RocksDbWeight::get().writes(4 as u64)) + } + // Storage: `ProofOfStake::PromotedPoolRewards` (r:1 w:0) + // Proof: `ProofOfStake::PromotedPoolRewards` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + // Storage: `ProofOfStake::RewardsInfo` (r:1 w:1) + // Proof: `ProofOfStake::RewardsInfo` (`max_values`: None, `max_size`: None, mode: `Measured`) + // Storage: `ProofOfStake::ActivatedNativeRewardsLiq` (r:1 w:0) + // Proof: `ProofOfStake::ActivatedNativeRewardsLiq` (`max_values`: None, `max_size`: None, mode: `Measured`) + // Storage: `ProofOfStake::TotalActivatedLiquidity` (r:1 w:1) + // Proof: `ProofOfStake::TotalActivatedLiquidity` (`max_values`: None, `max_size`: None, mode: `Measured`) + // Storage: `MultiPurposeLiquidity::ReserveStatus` (r:1 w:1) + // Proof: `MultiPurposeLiquidity::ReserveStatus` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`) + // Storage: `Tokens::Accounts` (r:1 w:1) + // Proof: `Tokens::Accounts` (`max_values`: None, `max_size`: Some(96), added: 2571, mode: `MaxEncodedLen`) + fn deactivate_liquidity_for_native_rewards() -> Weight { + (Weight::from_parts(80_270_000, 0)) + .saturating_add(RocksDbWeight::get().reads(6 as u64)) + .saturating_add(RocksDbWeight::get().writes(4 as u64)) + } + // Storage: `Xyk::LiquidityAssets` (r:1 w:0) + // Proof: `Xyk::LiquidityAssets` (`max_values`: None, `max_size`: Some(29), added: 2504, mode: `MaxEncodedLen`) + // Storage: `Xyk::LiquidityPools` (r:1 w:0) + // Proof: `Xyk::LiquidityPools` (`max_values`: None, `max_size`: Some(29), added: 2504, mode: `MaxEncodedLen`) + // Storage: `Xyk::Pools` (r:1 w:0) + // Proof: `Xyk::Pools` (`max_values`: None, `max_size`: Some(56), added: 2531, mode: `MaxEncodedLen`) + // Storage: `Tokens::TotalIssuance` (r:1 w:0) + // Proof: `Tokens::TotalIssuance` (`max_values`: None, `max_size`: Some(28), added: 2503, mode: `MaxEncodedLen`) + // Storage: `Tokens::Accounts` (r:2 w:2) + // Proof: `Tokens::Accounts` (`max_values`: None, `max_size`: Some(96), added: 2571, mode: `MaxEncodedLen`) + // Storage: `System::Account` (r:1 w:1) + // Proof: `System::Account` (`max_values`: None, `max_size`: Some(52), added: 2527, mode: `MaxEncodedLen`) + // Storage: `ProofOfStake::SchedulesListMetadata` (r:1 w:1) + // Proof: `ProofOfStake::SchedulesListMetadata` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + // Storage: `ProofOfStake::RewardsSchedulesList` (r:1 w:2) + // Proof: `ProofOfStake::RewardsSchedulesList` (`max_values`: None, `max_size`: None, mode: `Measured`) + // Storage: `ProofOfStake::RewardTokensPerPool` (r:0 w:1) + // Proof: `ProofOfStake::RewardTokensPerPool` (`max_values`: None, `max_size`: None, mode: `Measured`) + fn reward_pool() -> Weight { + (Weight::from_parts(149_310_000, 0)) + .saturating_add(RocksDbWeight::get().reads(9 as u64)) + .saturating_add(RocksDbWeight::get().writes(7 as u64)) + } + // Storage: `ProofOfStake::RewardTokensPerPool` (r:2 w:0) + // Proof: `ProofOfStake::RewardTokensPerPool` (`max_values`: None, `max_size`: None, mode: `Measured`) + // Storage: `MultiPurposeLiquidity::ReserveStatus` (r:1 w:1) + // Proof: `MultiPurposeLiquidity::ReserveStatus` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`) + // Storage: `Tokens::Accounts` (r:1 w:1) + // Proof: `Tokens::Accounts` (`max_values`: None, `max_size`: Some(96), added: 2571, mode: `MaxEncodedLen`) + // Storage: `ProofOfStake::ActivatedLockedLiquidityForSchedules` (r:1 w:1) + // Proof: `ProofOfStake::ActivatedLockedLiquidityForSchedules` (`max_values`: None, `max_size`: None, mode: `Measured`) + // Storage: `ProofOfStake::ScheduleRewardsPerLiquidity` (r:1 w:0) + // Proof: `ProofOfStake::ScheduleRewardsPerLiquidity` (`max_values`: None, `max_size`: None, mode: `Measured`) + // Storage: `ProofOfStake::RewardsInfoForScheduleRewards` (r:1 w:1) + // Proof: `ProofOfStake::RewardsInfoForScheduleRewards` (`max_values`: None, `max_size`: None, mode: `Measured`) + // Storage: `ProofOfStake::ActivatedLiquidityForSchedules` (r:1 w:1) + // Proof: `ProofOfStake::ActivatedLiquidityForSchedules` (`max_values`: None, `max_size`: None, mode: `Measured`) + // Storage: `ProofOfStake::TotalActivatedLiquidityForSchedules` (r:1 w:1) + // Proof: `ProofOfStake::TotalActivatedLiquidityForSchedules` (`max_values`: None, `max_size`: None, mode: `Measured`) + fn activate_liquidity_for_3rdparty_rewards() -> Weight { + (Weight::from_parts(110_431_000, 0)) + .saturating_add(RocksDbWeight::get().reads(9 as u64)) + .saturating_add(RocksDbWeight::get().writes(6 as u64)) + } + // Storage: `ProofOfStake::RewardTokensPerPool` (r:2 w:0) + // Proof: `ProofOfStake::RewardTokensPerPool` (`max_values`: None, `max_size`: None, mode: `Measured`) + // Storage: `ProofOfStake::ScheduleRewardsPerLiquidity` (r:1 w:0) + // Proof: `ProofOfStake::ScheduleRewardsPerLiquidity` (`max_values`: None, `max_size`: None, mode: `Measured`) + // Storage: `ProofOfStake::RewardsInfoForScheduleRewards` (r:1 w:1) + // Proof: `ProofOfStake::RewardsInfoForScheduleRewards` (`max_values`: None, `max_size`: None, mode: `Measured`) + // Storage: `ProofOfStake::TotalActivatedLiquidityForSchedules` (r:1 w:1) + // Proof: `ProofOfStake::TotalActivatedLiquidityForSchedules` (`max_values`: None, `max_size`: None, mode: `Measured`) + // Storage: `ProofOfStake::ActivatedLiquidityForSchedules` (r:2 w:1) + // Proof: `ProofOfStake::ActivatedLiquidityForSchedules` (`max_values`: None, `max_size`: None, mode: `Measured`) + // Storage: `ProofOfStake::ActivatedLockedLiquidityForSchedules` (r:1 w:1) + // Proof: `ProofOfStake::ActivatedLockedLiquidityForSchedules` (`max_values`: None, `max_size`: None, mode: `Measured`) + // Storage: `MultiPurposeLiquidity::ReserveStatus` (r:1 w:1) + // Proof: `MultiPurposeLiquidity::ReserveStatus` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`) + // Storage: `Tokens::Accounts` (r:1 w:1) + // Proof: `Tokens::Accounts` (`max_values`: None, `max_size`: Some(96), added: 2571, mode: `MaxEncodedLen`) + // Storage: `ProofOfStake::ActivatedNativeRewardsLiq` (r:1 w:1) + // Proof: `ProofOfStake::ActivatedNativeRewardsLiq` (`max_values`: None, `max_size`: None, mode: `Measured`) + fn deactivate_liquidity_for_3rdparty_rewards() -> Weight { + (Weight::from_parts(118_440_000, 0)) + .saturating_add(RocksDbWeight::get().reads(11 as u64)) + .saturating_add(RocksDbWeight::get().writes(7 as u64)) + } + // Storage: `ProofOfStake::ScheduleRewardsPerLiquidity` (r:1 w:1) + // Proof: `ProofOfStake::ScheduleRewardsPerLiquidity` (`max_values`: None, `max_size`: None, mode: `Measured`) + // Storage: `ProofOfStake::TotalActivatedLiquidityForSchedules` (r:1 w:0) + // Proof: `ProofOfStake::TotalActivatedLiquidityForSchedules` (`max_values`: None, `max_size`: None, mode: `Measured`) + // Storage: `ProofOfStake::ScheduleRewardsTotal` (r:1 w:1) + // Proof: `ProofOfStake::ScheduleRewardsTotal` (`max_values`: None, `max_size`: None, mode: `Measured`) + // Storage: `ProofOfStake::RewardTokensPerPool` (r:2 w:0) + // Proof: `ProofOfStake::RewardTokensPerPool` (`max_values`: None, `max_size`: None, mode: `Measured`) + // Storage: `ProofOfStake::RewardsInfoForScheduleRewards` (r:1 w:1) + // Proof: `ProofOfStake::RewardsInfoForScheduleRewards` (`max_values`: None, `max_size`: None, mode: `Measured`) + // Storage: `Tokens::Accounts` (r:2 w:2) + // Proof: `Tokens::Accounts` (`max_values`: None, `max_size`: Some(96), added: 2571, mode: `MaxEncodedLen`) + fn claim_3rdparty_rewards() -> Weight { + (Weight::from_parts(132_200_000, 0)) + .saturating_add(RocksDbWeight::get().reads(8 as u64)) + .saturating_add(RocksDbWeight::get().writes(5 as u64)) + } +} diff --git a/gasp-node/rollup/runtime/src/weights/pallet_rolldown.rs b/gasp-node/rollup/runtime/src/weights/pallet_rolldown.rs new file mode 100644 index 000000000..d7c13383d --- /dev/null +++ b/gasp-node/rollup/runtime/src/weights/pallet_rolldown.rs @@ -0,0 +1,742 @@ +// This file is part of Mangata. + +// Copyright (C) 2020-2022 Mangata Foundation. +// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 + +// This program 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. + +// This program 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 this program. If not, see . + +//! Autogenerated weights for pallet_rolldown +//! +//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 32.0.0 +//! DATE: 2024-11-29, STEPS: `50`, REPEAT: 20, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! EXECUTION: , WASM-EXECUTION: Compiled, CHAIN: Some("rollup-local"), DB CACHE: 1024 + +// Executed Command: +// target/release/rollup-node +// benchmark +// pallet +// -l=info,runtime::collective=warn,xyk=warn +// --chain +// rollup-local +// --wasm-execution +// compiled +// --pallet +// * +// --extrinsic +// * +// --steps +// 50 +// --repeat +// 20 +// --template +// ./templates/module-weight-template.hbs +// --output +// ./benchmarks/ + +#![cfg_attr(rustfmt, rustfmt_skip)] +#![allow(unused_parens)] +#![allow(unused_imports)] +#![allow(clippy::unnecessary_cast)] + +use frame_support::{traits::Get, weights::{Weight, constants::RocksDbWeight}}; +use sp_std::marker::PhantomData; + +/// Weight functions needed for pallet_rolldown. +pub trait WeightInfo { + fn set_manual_batch_extra_fee() -> Weight; + fn create_batch() -> Weight; + fn force_create_batch() -> Weight; + fn update_l2_from_l1(x: u32, ) -> Weight; + fn update_l2_from_l1_unsafe(x: u32, ) -> Weight; + fn force_update_l2_from_l1(x: u32, ) -> Weight; + fn cancel_requests_from_l1() -> Weight; + fn force_cancel_requests_from_l1() -> Weight; + fn withdraw() -> Weight; + fn refund_failed_deposit() -> Weight; + fn ferry_deposit() -> Weight; + fn ferry_deposit_unsafe() -> Weight; + fn process_deposit() -> Weight; + fn process_cancel_resolution() -> Weight; + fn load_next_update_from_execution_queue() -> Weight; + fn schedule_request_for_execution_if_dispute_period_has_passsed() -> Weight; + fn maybe_create_batch() -> Weight; + fn execute_requests_from_execute_queue() -> Weight; +} + +/// Weights for pallet_rolldown using the Mangata node and recommended hardware. +pub struct ModuleWeight(PhantomData); +impl pallet_rolldown::WeightInfo for ModuleWeight { + // Storage: `Rolldown::ManualBatchExtraFee` (r:0 w:1) + // Proof: `Rolldown::ManualBatchExtraFee` (`max_values`: Some(1), `max_size`: Some(16), added: 511, mode: `MaxEncodedLen`) + fn set_manual_batch_extra_fee() -> Weight { + (Weight::from_parts(8_090_000, 0)) + .saturating_add(T::DbWeight::get().writes(1 as u64)) + } + // Storage: `Maintenance::MaintenanceStatus` (r:1 w:0) + // Proof: `Maintenance::MaintenanceStatus` (`max_values`: Some(1), `max_size`: Some(2), added: 497, mode: `MaxEncodedLen`) + // Storage: `SequencerStaking::AliasAccount` (r:1 w:0) + // Proof: `SequencerStaking::AliasAccount` (`max_values`: None, `max_size`: Some(57), added: 2532, mode: `MaxEncodedLen`) + // Storage: `Rolldown::ManualBatchExtraFee` (r:1 w:0) + // Proof: `Rolldown::ManualBatchExtraFee` (`max_values`: Some(1), `max_size`: Some(16), added: 511, mode: `MaxEncodedLen`) + // Storage: `Tokens::Accounts` (r:2 w:2) + // Proof: `Tokens::Accounts` (`max_values`: None, `max_size`: Some(96), added: 2571, mode: `MaxEncodedLen`) + // Storage: `Rolldown::L2RequestsBatchLast` (r:1 w:1) + // Proof: `Rolldown::L2RequestsBatchLast` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + // Storage: `Rolldown::L2OriginRequestId` (r:1 w:0) + // Proof: `Rolldown::L2OriginRequestId` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + // Storage: `Rolldown::L2Requests` (r:1 w:0) + // Proof: `Rolldown::L2Requests` (`max_values`: None, `max_size`: None, mode: `Measured`) + // Storage: `Rolldown::L2RequestsBatch` (r:0 w:1) + // Proof: `Rolldown::L2RequestsBatch` (`max_values`: None, `max_size`: Some(89), added: 2564, mode: `MaxEncodedLen`) + fn create_batch() -> Weight { + (Weight::from_parts(66_250_000, 0)) + .saturating_add(T::DbWeight::get().reads(8 as u64)) + .saturating_add(T::DbWeight::get().writes(4 as u64)) + } + // Storage: `Maintenance::MaintenanceStatus` (r:1 w:0) + // Proof: `Maintenance::MaintenanceStatus` (`max_values`: Some(1), `max_size`: Some(2), added: 497, mode: `MaxEncodedLen`) + // Storage: `Rolldown::L2Requests` (r:1 w:0) + // Proof: `Rolldown::L2Requests` (`max_values`: None, `max_size`: None, mode: `Measured`) + // Storage: `Rolldown::L2RequestsBatchLast` (r:1 w:1) + // Proof: `Rolldown::L2RequestsBatchLast` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + // Storage: `Rolldown::L2RequestsBatch` (r:0 w:1) + // Proof: `Rolldown::L2RequestsBatch` (`max_values`: None, `max_size`: Some(89), added: 2564, mode: `MaxEncodedLen`) + fn force_create_batch() -> Weight { + (Weight::from_parts(29_170_000, 0)) + .saturating_add(T::DbWeight::get().reads(3 as u64)) + .saturating_add(T::DbWeight::get().writes(2 as u64)) + } + // Storage: `Maintenance::MaintenanceStatus` (r:1 w:0) + // Proof: `Maintenance::MaintenanceStatus` (`max_values`: Some(1), `max_size`: Some(2), added: 497, mode: `MaxEncodedLen`) + // Storage: `SequencerStaking::SelectedSequencer` (r:1 w:0) + // Proof: `SequencerStaking::SelectedSequencer` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + // Storage: `Rolldown::LastProcessedRequestOnL2` (r:1 w:0) + // Proof: `Rolldown::LastProcessedRequestOnL2` (`max_values`: None, `max_size`: Some(33), added: 2508, mode: `MaxEncodedLen`) + // Storage: `Rolldown::DisputePeriod` (r:1 w:0) + // Proof: `Rolldown::DisputePeriod` (`max_values`: None, `max_size`: Some(33), added: 2508, mode: `MaxEncodedLen`) + // Storage: `Rolldown::SequencersRights` (r:1 w:1) + // Proof: `Rolldown::SequencersRights` (`max_values`: None, `max_size`: None, mode: `Measured`) + // Storage: `Rolldown::PendingSequencerUpdates` (r:1 w:1) + // Proof: `Rolldown::PendingSequencerUpdates` (`max_values`: None, `max_size`: None, mode: `Measured`) + // Storage: `SequencerStaking::CurrentRound` (r:1 w:0) + // Proof: `SequencerStaking::CurrentRound` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) + // Storage: `SequencerStaking::AwardedPts` (r:1 w:1) + // Proof: `SequencerStaking::AwardedPts` (`max_values`: None, `max_size`: Some(44), added: 2519, mode: `MaxEncodedLen`) + // Storage: `SequencerStaking::Points` (r:1 w:1) + // Proof: `SequencerStaking::Points` (`max_values`: None, `max_size`: Some(16), added: 2491, mode: `MaxEncodedLen`) + // Storage: `Rolldown::LastUpdateBySequencer` (r:0 w:1) + // Proof: `Rolldown::LastUpdateBySequencer` (`max_values`: None, `max_size`: Some(53), added: 2528, mode: `MaxEncodedLen`) + // Storage: `Rolldown::PendingSequencerUpdateContent` (r:0 w:1) + // Proof: `Rolldown::PendingSequencerUpdateContent` (`max_values`: None, `max_size`: None, mode: `Measured`) + fn update_l2_from_l1(x: u32, ) -> Weight { + (Weight::from_parts(78_571_518, 0)) + // Standard Error: 3_017 + .saturating_add((Weight::from_parts(2_261_755, 0)).saturating_mul(x as u64)) + .saturating_add(T::DbWeight::get().reads(9 as u64)) + .saturating_add(T::DbWeight::get().writes(6 as u64)) + } + // Storage: `Maintenance::MaintenanceStatus` (r:1 w:0) + // Proof: `Maintenance::MaintenanceStatus` (`max_values`: Some(1), `max_size`: Some(2), added: 497, mode: `MaxEncodedLen`) + // Storage: `SequencerStaking::SelectedSequencer` (r:1 w:0) + // Proof: `SequencerStaking::SelectedSequencer` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + // Storage: `Rolldown::LastProcessedRequestOnL2` (r:1 w:0) + // Proof: `Rolldown::LastProcessedRequestOnL2` (`max_values`: None, `max_size`: Some(33), added: 2508, mode: `MaxEncodedLen`) + // Storage: `Rolldown::DisputePeriod` (r:1 w:0) + // Proof: `Rolldown::DisputePeriod` (`max_values`: None, `max_size`: Some(33), added: 2508, mode: `MaxEncodedLen`) + // Storage: `Rolldown::SequencersRights` (r:1 w:1) + // Proof: `Rolldown::SequencersRights` (`max_values`: None, `max_size`: None, mode: `Measured`) + // Storage: `Rolldown::PendingSequencerUpdates` (r:1 w:1) + // Proof: `Rolldown::PendingSequencerUpdates` (`max_values`: None, `max_size`: None, mode: `Measured`) + // Storage: `SequencerStaking::CurrentRound` (r:1 w:0) + // Proof: `SequencerStaking::CurrentRound` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) + // Storage: `SequencerStaking::AwardedPts` (r:1 w:1) + // Proof: `SequencerStaking::AwardedPts` (`max_values`: None, `max_size`: Some(44), added: 2519, mode: `MaxEncodedLen`) + // Storage: `SequencerStaking::Points` (r:1 w:1) + // Proof: `SequencerStaking::Points` (`max_values`: None, `max_size`: Some(16), added: 2491, mode: `MaxEncodedLen`) + // Storage: `Rolldown::LastUpdateBySequencer` (r:0 w:1) + // Proof: `Rolldown::LastUpdateBySequencer` (`max_values`: None, `max_size`: Some(53), added: 2528, mode: `MaxEncodedLen`) + // Storage: `Rolldown::PendingSequencerUpdateContent` (r:0 w:1) + // Proof: `Rolldown::PendingSequencerUpdateContent` (`max_values`: None, `max_size`: None, mode: `Measured`) + fn update_l2_from_l1_unsafe(x: u32, ) -> Weight { + (Weight::from_parts(74_102_411, 0)) + // Standard Error: 3_454 + .saturating_add((Weight::from_parts(1_390_756, 0)).saturating_mul(x as u64)) + .saturating_add(T::DbWeight::get().reads(9 as u64)) + .saturating_add(T::DbWeight::get().writes(6 as u64)) + } + // Storage: `Maintenance::MaintenanceStatus` (r:1 w:0) + // Proof: `Maintenance::MaintenanceStatus` (`max_values`: Some(1), `max_size`: Some(2), added: 497, mode: `MaxEncodedLen`) + // Storage: `Rolldown::LastProcessedRequestOnL2` (r:1 w:0) + // Proof: `Rolldown::LastProcessedRequestOnL2` (`max_values`: None, `max_size`: Some(33), added: 2508, mode: `MaxEncodedLen`) + // Storage: `Rolldown::MaxAcceptedRequestIdOnl2` (r:1 w:1) + // Proof: `Rolldown::MaxAcceptedRequestIdOnl2` (`max_values`: None, `max_size`: Some(33), added: 2508, mode: `MaxEncodedLen`) + // Storage: `Rolldown::LastScheduledUpdateIdInExecutionQueue` (r:1 w:1) + // Proof: `Rolldown::LastScheduledUpdateIdInExecutionQueue` (`max_values`: Some(1), `max_size`: Some(16), added: 511, mode: `MaxEncodedLen`) + // Storage: `Rolldown::UpdatesExecutionQueue` (r:0 w:1) + // Proof: `Rolldown::UpdatesExecutionQueue` (`max_values`: None, `max_size`: None, mode: `Measured`) + // Storage: `Rolldown::PendingSequencerUpdateContent` (r:0 w:1) + // Proof: `Rolldown::PendingSequencerUpdateContent` (`max_values`: None, `max_size`: None, mode: `Measured`) + fn force_update_l2_from_l1(x: u32, ) -> Weight { + (Weight::from_parts(30_029_416, 0)) + // Standard Error: 3_059 + .saturating_add((Weight::from_parts(1_346_861, 0)).saturating_mul(x as u64)) + .saturating_add(T::DbWeight::get().reads(4 as u64)) + .saturating_add(T::DbWeight::get().writes(4 as u64)) + } + // Storage: `Maintenance::MaintenanceStatus` (r:1 w:0) + // Proof: `Maintenance::MaintenanceStatus` (`max_values`: Some(1), `max_size`: Some(2), added: 497, mode: `MaxEncodedLen`) + // Storage: `Rolldown::SequencersRights` (r:1 w:1) + // Proof: `Rolldown::SequencersRights` (`max_values`: None, `max_size`: None, mode: `Measured`) + // Storage: `Rolldown::PendingSequencerUpdates` (r:1 w:1) + // Proof: `Rolldown::PendingSequencerUpdates` (`max_values`: None, `max_size`: None, mode: `Measured`) + // Storage: `Rolldown::PendingSequencerUpdateContent` (r:1 w:1) + // Proof: `Rolldown::PendingSequencerUpdateContent` (`max_values`: None, `max_size`: None, mode: `Measured`) + // Storage: `Rolldown::L2OriginRequestId` (r:1 w:1) + // Proof: `Rolldown::L2OriginRequestId` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + // Storage: `Rolldown::AwaitingCancelResolution` (r:1 w:1) + // Proof: `Rolldown::AwaitingCancelResolution` (`max_values`: None, `max_size`: None, mode: `Measured`) + // Storage: `Rolldown::L2Requests` (r:0 w:1) + // Proof: `Rolldown::L2Requests` (`max_values`: None, `max_size`: None, mode: `Measured`) + fn cancel_requests_from_l1() -> Weight { + (Weight::from_parts(54_269_000, 0)) + .saturating_add(T::DbWeight::get().reads(6 as u64)) + .saturating_add(T::DbWeight::get().writes(6 as u64)) + } + // Storage: `Maintenance::MaintenanceStatus` (r:1 w:0) + // Proof: `Maintenance::MaintenanceStatus` (`max_values`: Some(1), `max_size`: Some(2), added: 497, mode: `MaxEncodedLen`) + // Storage: `Rolldown::PendingSequencerUpdates` (r:1 w:1) + // Proof: `Rolldown::PendingSequencerUpdates` (`max_values`: None, `max_size`: None, mode: `Measured`) + // Storage: `SequencerStaking::ActiveSequencers` (r:1 w:0) + // Proof: `SequencerStaking::ActiveSequencers` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + // Storage: `Rolldown::SequencersRights` (r:1 w:1) + // Proof: `Rolldown::SequencersRights` (`max_values`: None, `max_size`: None, mode: `Measured`) + fn force_cancel_requests_from_l1() -> Weight { + (Weight::from_parts(30_610_000, 0)) + .saturating_add(T::DbWeight::get().reads(4 as u64)) + .saturating_add(T::DbWeight::get().writes(2 as u64)) + } + // Storage: `Maintenance::MaintenanceStatus` (r:1 w:0) + // Proof: `Maintenance::MaintenanceStatus` (`max_values`: Some(1), `max_size`: Some(2), added: 497, mode: `MaxEncodedLen`) + // Storage: `AssetRegistry::L1AssetToId` (r:1 w:0) + // Proof: `AssetRegistry::L1AssetToId` (`max_values`: None, `max_size`: Some(41), added: 2516, mode: `MaxEncodedLen`) + // Storage: `Tokens::Accounts` (r:3 w:3) + // Proof: `Tokens::Accounts` (`max_values`: None, `max_size`: Some(96), added: 2571, mode: `MaxEncodedLen`) + // Storage: `System::Account` (r:1 w:1) + // Proof: `System::Account` (`max_values`: None, `max_size`: Some(52), added: 2527, mode: `MaxEncodedLen`) + // Storage: `Tokens::TotalIssuance` (r:1 w:1) + // Proof: `Tokens::TotalIssuance` (`max_values`: None, `max_size`: Some(28), added: 2503, mode: `MaxEncodedLen`) + // Storage: `Rolldown::L2OriginRequestId` (r:1 w:1) + // Proof: `Rolldown::L2OriginRequestId` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + // Storage: `Rolldown::TotalNumberOfWithdrawals` (r:1 w:1) + // Proof: `Rolldown::TotalNumberOfWithdrawals` (`max_values`: Some(1), `max_size`: Some(16), added: 511, mode: `MaxEncodedLen`) + // Storage: `Rolldown::L2Requests` (r:0 w:1) + // Proof: `Rolldown::L2Requests` (`max_values`: None, `max_size`: None, mode: `Measured`) + fn withdraw() -> Weight { + (Weight::from_parts(95_260_000, 0)) + .saturating_add(T::DbWeight::get().reads(9 as u64)) + .saturating_add(T::DbWeight::get().writes(8 as u64)) + } + // Storage: `Rolldown::FailedL1Deposits` (r:1 w:1) + // Proof: `Rolldown::FailedL1Deposits` (`max_values`: None, `max_size`: Some(85), added: 2560, mode: `MaxEncodedLen`) + // Storage: `Rolldown::FerriedDeposits` (r:1 w:0) + // Proof: `Rolldown::FerriedDeposits` (`max_values`: None, `max_size`: Some(69), added: 2544, mode: `MaxEncodedLen`) + // Storage: `Rolldown::L2OriginRequestId` (r:1 w:1) + // Proof: `Rolldown::L2OriginRequestId` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + // Storage: `Rolldown::L2Requests` (r:0 w:1) + // Proof: `Rolldown::L2Requests` (`max_values`: None, `max_size`: None, mode: `Measured`) + fn refund_failed_deposit() -> Weight { + (Weight::from_parts(28_730_000, 0)) + .saturating_add(T::DbWeight::get().reads(3 as u64)) + .saturating_add(T::DbWeight::get().writes(3 as u64)) + } + // Storage: `Rolldown::LastProcessedRequestOnL2` (r:1 w:0) + // Proof: `Rolldown::LastProcessedRequestOnL2` (`max_values`: None, `max_size`: Some(33), added: 2508, mode: `MaxEncodedLen`) + // Storage: `AssetRegistry::L1AssetToId` (r:1 w:0) + // Proof: `AssetRegistry::L1AssetToId` (`max_values`: None, `max_size`: Some(41), added: 2516, mode: `MaxEncodedLen`) + // Storage: `Tokens::Accounts` (r:2 w:2) + // Proof: `Tokens::Accounts` (`max_values`: None, `max_size`: Some(96), added: 2571, mode: `MaxEncodedLen`) + // Storage: `System::Account` (r:1 w:1) + // Proof: `System::Account` (`max_values`: None, `max_size`: Some(52), added: 2527, mode: `MaxEncodedLen`) + // Storage: `Rolldown::FerriedDeposits` (r:0 w:1) + // Proof: `Rolldown::FerriedDeposits` (`max_values`: None, `max_size`: Some(69), added: 2544, mode: `MaxEncodedLen`) + fn ferry_deposit() -> Weight { + (Weight::from_parts(62_060_000, 0)) + .saturating_add(T::DbWeight::get().reads(5 as u64)) + .saturating_add(T::DbWeight::get().writes(4 as u64)) + } + // Storage: `Rolldown::LastProcessedRequestOnL2` (r:1 w:0) + // Proof: `Rolldown::LastProcessedRequestOnL2` (`max_values`: None, `max_size`: Some(33), added: 2508, mode: `MaxEncodedLen`) + // Storage: `AssetRegistry::L1AssetToId` (r:1 w:0) + // Proof: `AssetRegistry::L1AssetToId` (`max_values`: None, `max_size`: Some(41), added: 2516, mode: `MaxEncodedLen`) + // Storage: `Tokens::Accounts` (r:2 w:2) + // Proof: `Tokens::Accounts` (`max_values`: None, `max_size`: Some(96), added: 2571, mode: `MaxEncodedLen`) + // Storage: `System::Account` (r:1 w:1) + // Proof: `System::Account` (`max_values`: None, `max_size`: Some(52), added: 2527, mode: `MaxEncodedLen`) + // Storage: `Rolldown::FerriedDeposits` (r:0 w:1) + // Proof: `Rolldown::FerriedDeposits` (`max_values`: None, `max_size`: Some(69), added: 2544, mode: `MaxEncodedLen`) + fn ferry_deposit_unsafe() -> Weight { + (Weight::from_parts(60_500_000, 0)) + .saturating_add(T::DbWeight::get().reads(5 as u64)) + .saturating_add(T::DbWeight::get().writes(4 as u64)) + } + // Storage: `AssetRegistry::L1AssetToId` (r:1 w:0) + // Proof: `AssetRegistry::L1AssetToId` (`max_values`: None, `max_size`: Some(41), added: 2516, mode: `MaxEncodedLen`) + // Storage: `Rolldown::FerriedDeposits` (r:1 w:0) + // Proof: `Rolldown::FerriedDeposits` (`max_values`: None, `max_size`: Some(69), added: 2544, mode: `MaxEncodedLen`) + // Storage: `Tokens::NextCurrencyId` (r:1 w:0) + // Proof: `Tokens::NextCurrencyId` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) + // Storage: `Tokens::Accounts` (r:1 w:1) + // Proof: `Tokens::Accounts` (`max_values`: None, `max_size`: Some(96), added: 2571, mode: `MaxEncodedLen`) + // Storage: `Tokens::TotalIssuance` (r:1 w:1) + // Proof: `Tokens::TotalIssuance` (`max_values`: None, `max_size`: Some(28), added: 2503, mode: `MaxEncodedLen`) + // Storage: `System::Account` (r:1 w:1) + // Proof: `System::Account` (`max_values`: None, `max_size`: Some(52), added: 2527, mode: `MaxEncodedLen`) + fn process_deposit() -> Weight { + (Weight::from_parts(47_830_000, 0)) + .saturating_add(T::DbWeight::get().reads(6 as u64)) + .saturating_add(T::DbWeight::get().writes(3 as u64)) + } + // Storage: `Rolldown::L2Requests` (r:1 w:0) + // Proof: `Rolldown::L2Requests` (`max_values`: None, `max_size`: None, mode: `Measured`) + // Storage: `SequencerStaking::ActiveSequencers` (r:1 w:1) + // Proof: `SequencerStaking::ActiveSequencers` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + // Storage: `Rolldown::SequencersRights` (r:1 w:1) + // Proof: `Rolldown::SequencersRights` (`max_values`: None, `max_size`: None, mode: `Measured`) + // Storage: `Rolldown::AwaitingCancelResolution` (r:1 w:1) + // Proof: `Rolldown::AwaitingCancelResolution` (`max_values`: None, `max_size`: None, mode: `Measured`) + // Storage: `SequencerStaking::SequencerStake` (r:1 w:1) + // Proof: `SequencerStaking::SequencerStake` (`max_values`: None, `max_size`: Some(53), added: 2528, mode: `MaxEncodedLen`) + // Storage: `SequencerStaking::SlashFineAmount` (r:1 w:0) + // Proof: `SequencerStaking::SlashFineAmount` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + // Storage: `Tokens::Accounts` (r:2 w:2) + // Proof: `Tokens::Accounts` (`max_values`: None, `max_size`: Some(96), added: 2571, mode: `MaxEncodedLen`) + // Storage: `Tokens::TotalIssuance` (r:1 w:1) + // Proof: `Tokens::TotalIssuance` (`max_values`: None, `max_size`: Some(28), added: 2503, mode: `MaxEncodedLen`) + // Storage: `SequencerStaking::MinimalStakeAmount` (r:1 w:0) + // Proof: `SequencerStaking::MinimalStakeAmount` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + // Storage: `SequencerStaking::NextSequencerIndex` (r:1 w:1) + // Proof: `SequencerStaking::NextSequencerIndex` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + // Storage: `SequencerStaking::SelectedSequencer` (r:1 w:1) + // Proof: `SequencerStaking::SelectedSequencer` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + fn process_cancel_resolution() -> Weight { + (Weight::from_parts(124_309_000, 0)) + .saturating_add(T::DbWeight::get().reads(12 as u64)) + .saturating_add(T::DbWeight::get().writes(9 as u64)) + } + // Storage: `Rolldown::UpdatesExecutionQueueNextId` (r:1 w:1) + // Proof: `Rolldown::UpdatesExecutionQueueNextId` (`max_values`: Some(1), `max_size`: Some(16), added: 511, mode: `MaxEncodedLen`) + // Storage: `Rolldown::UpdatesExecutionQueue` (r:2 w:0) + // Proof: `Rolldown::UpdatesExecutionQueue` (`max_values`: None, `max_size`: None, mode: `Measured`) + fn load_next_update_from_execution_queue() -> Weight { + (Weight::from_parts(11_780_000, 0)) + .saturating_add(T::DbWeight::get().reads(3 as u64)) + .saturating_add(T::DbWeight::get().writes(1 as u64)) + } + // Storage: `Rolldown::PendingSequencerUpdates` (r:2 w:1) + // Proof: `Rolldown::PendingSequencerUpdates` (`max_values`: None, `max_size`: None, mode: `Measured`) + // Storage: `Rolldown::DisputePeriod` (r:1 w:0) + // Proof: `Rolldown::DisputePeriod` (`max_values`: None, `max_size`: Some(33), added: 2508, mode: `MaxEncodedLen`) + // Storage: `SequencerStaking::ActiveSequencers` (r:1 w:0) + // Proof: `SequencerStaking::ActiveSequencers` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + // Storage: `Rolldown::LastMaintananceMode` (r:1 w:0) + // Proof: `Rolldown::LastMaintananceMode` (`max_values`: Some(1), `max_size`: Some(16), added: 511, mode: `MaxEncodedLen`) + // Storage: `Rolldown::MaxAcceptedRequestIdOnl2` (r:1 w:1) + // Proof: `Rolldown::MaxAcceptedRequestIdOnl2` (`max_values`: None, `max_size`: Some(33), added: 2508, mode: `MaxEncodedLen`) + // Storage: `Rolldown::LastScheduledUpdateIdInExecutionQueue` (r:1 w:1) + // Proof: `Rolldown::LastScheduledUpdateIdInExecutionQueue` (`max_values`: Some(1), `max_size`: Some(16), added: 511, mode: `MaxEncodedLen`) + // Storage: `Rolldown::UpdatesExecutionQueue` (r:0 w:1) + // Proof: `Rolldown::UpdatesExecutionQueue` (`max_values`: None, `max_size`: None, mode: `Measured`) + fn schedule_request_for_execution_if_dispute_period_has_passsed() -> Weight { + (Weight::from_parts(50_720_000, 0)) + .saturating_add(T::DbWeight::get().reads(7 as u64)) + .saturating_add(T::DbWeight::get().writes(4 as u64)) + } + // Storage: `Maintenance::MaintenanceStatus` (r:1 w:0) + // Proof: `Maintenance::MaintenanceStatus` (`max_values`: Some(1), `max_size`: Some(2), added: 497, mode: `MaxEncodedLen`) + // Storage: `Rolldown::L2OriginRequestId` (r:1 w:0) + // Proof: `Rolldown::L2OriginRequestId` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + // Storage: `Rolldown::L2RequestsBatchLast` (r:1 w:1) + // Proof: `Rolldown::L2RequestsBatchLast` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + // Storage: `SequencerStaking::SelectedSequencer` (r:1 w:0) + // Proof: `SequencerStaking::SelectedSequencer` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + // Storage: `Rolldown::L2RequestsBatch` (r:0 w:1) + // Proof: `Rolldown::L2RequestsBatch` (`max_values`: None, `max_size`: Some(89), added: 2564, mode: `MaxEncodedLen`) + fn maybe_create_batch() -> Weight { + (Weight::from_parts(27_320_000, 0)) + .saturating_add(T::DbWeight::get().reads(4 as u64)) + .saturating_add(T::DbWeight::get().writes(2 as u64)) + } + // Storage: `Rolldown::UpdatesExecutionQueueNextId` (r:1 w:0) + // Proof: `Rolldown::UpdatesExecutionQueueNextId` (`max_values`: Some(1), `max_size`: Some(16), added: 511, mode: `MaxEncodedLen`) + // Storage: `Rolldown::UpdatesExecutionQueue` (r:1 w:0) + // Proof: `Rolldown::UpdatesExecutionQueue` (`max_values`: None, `max_size`: None, mode: `Measured`) + // Storage: `Rolldown::LastMaintananceMode` (r:1 w:0) + // Proof: `Rolldown::LastMaintananceMode` (`max_values`: Some(1), `max_size`: Some(16), added: 511, mode: `MaxEncodedLen`) + // Storage: `Rolldown::PendingSequencerUpdateContent` (r:1 w:0) + // Proof: `Rolldown::PendingSequencerUpdateContent` (`max_values`: None, `max_size`: None, mode: `Measured`) + // Storage: `Rolldown::LastProcessedRequestOnL2` (r:1 w:1) + // Proof: `Rolldown::LastProcessedRequestOnL2` (`max_values`: None, `max_size`: Some(33), added: 2508, mode: `MaxEncodedLen`) + // Storage: `AssetRegistry::L1AssetToId` (r:1 w:1) + // Proof: `AssetRegistry::L1AssetToId` (`max_values`: None, `max_size`: Some(41), added: 2516, mode: `MaxEncodedLen`) + // Storage: `Tokens::NextCurrencyId` (r:1 w:1) + // Proof: `Tokens::NextCurrencyId` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) + // Storage: `Tokens::Accounts` (r:2 w:0) + // Proof: `Tokens::Accounts` (`max_values`: None, `max_size`: Some(96), added: 2571, mode: `MaxEncodedLen`) + // Storage: `AssetRegistry::Metadata` (r:1 w:1) + // Proof: `AssetRegistry::Metadata` (`max_values`: None, `max_size`: Some(153), added: 2628, mode: `MaxEncodedLen`) + // Storage: `Rolldown::FerriedDeposits` (r:50 w:0) + // Proof: `Rolldown::FerriedDeposits` (`max_values`: None, `max_size`: Some(69), added: 2544, mode: `MaxEncodedLen`) + // Storage: `Rolldown::TotalNumberOfDeposits` (r:1 w:1) + // Proof: `Rolldown::TotalNumberOfDeposits` (`max_values`: Some(1), `max_size`: Some(16), added: 511, mode: `MaxEncodedLen`) + // Storage: `AssetRegistry::IdToL1Asset` (r:0 w:1) + // Proof: `AssetRegistry::IdToL1Asset` (`max_values`: None, `max_size`: Some(33), added: 2508, mode: `MaxEncodedLen`) + fn execute_requests_from_execute_queue() -> Weight { + (Weight::from_parts(4_252_650_000, 0)) + .saturating_add(T::DbWeight::get().reads(61 as u64)) + .saturating_add(T::DbWeight::get().writes(6 as u64)) + } +} + +// For backwards compatibility and tests +impl WeightInfo for () { + // Storage: `Rolldown::ManualBatchExtraFee` (r:0 w:1) + // Proof: `Rolldown::ManualBatchExtraFee` (`max_values`: Some(1), `max_size`: Some(16), added: 511, mode: `MaxEncodedLen`) + fn set_manual_batch_extra_fee() -> Weight { + (Weight::from_parts(8_090_000, 0)) + .saturating_add(RocksDbWeight::get().writes(1 as u64)) + } + // Storage: `Maintenance::MaintenanceStatus` (r:1 w:0) + // Proof: `Maintenance::MaintenanceStatus` (`max_values`: Some(1), `max_size`: Some(2), added: 497, mode: `MaxEncodedLen`) + // Storage: `SequencerStaking::AliasAccount` (r:1 w:0) + // Proof: `SequencerStaking::AliasAccount` (`max_values`: None, `max_size`: Some(57), added: 2532, mode: `MaxEncodedLen`) + // Storage: `Rolldown::ManualBatchExtraFee` (r:1 w:0) + // Proof: `Rolldown::ManualBatchExtraFee` (`max_values`: Some(1), `max_size`: Some(16), added: 511, mode: `MaxEncodedLen`) + // Storage: `Tokens::Accounts` (r:2 w:2) + // Proof: `Tokens::Accounts` (`max_values`: None, `max_size`: Some(96), added: 2571, mode: `MaxEncodedLen`) + // Storage: `Rolldown::L2RequestsBatchLast` (r:1 w:1) + // Proof: `Rolldown::L2RequestsBatchLast` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + // Storage: `Rolldown::L2OriginRequestId` (r:1 w:0) + // Proof: `Rolldown::L2OriginRequestId` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + // Storage: `Rolldown::L2Requests` (r:1 w:0) + // Proof: `Rolldown::L2Requests` (`max_values`: None, `max_size`: None, mode: `Measured`) + // Storage: `Rolldown::L2RequestsBatch` (r:0 w:1) + // Proof: `Rolldown::L2RequestsBatch` (`max_values`: None, `max_size`: Some(89), added: 2564, mode: `MaxEncodedLen`) + fn create_batch() -> Weight { + (Weight::from_parts(66_250_000, 0)) + .saturating_add(RocksDbWeight::get().reads(8 as u64)) + .saturating_add(RocksDbWeight::get().writes(4 as u64)) + } + // Storage: `Maintenance::MaintenanceStatus` (r:1 w:0) + // Proof: `Maintenance::MaintenanceStatus` (`max_values`: Some(1), `max_size`: Some(2), added: 497, mode: `MaxEncodedLen`) + // Storage: `Rolldown::L2Requests` (r:1 w:0) + // Proof: `Rolldown::L2Requests` (`max_values`: None, `max_size`: None, mode: `Measured`) + // Storage: `Rolldown::L2RequestsBatchLast` (r:1 w:1) + // Proof: `Rolldown::L2RequestsBatchLast` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + // Storage: `Rolldown::L2RequestsBatch` (r:0 w:1) + // Proof: `Rolldown::L2RequestsBatch` (`max_values`: None, `max_size`: Some(89), added: 2564, mode: `MaxEncodedLen`) + fn force_create_batch() -> Weight { + (Weight::from_parts(29_170_000, 0)) + .saturating_add(RocksDbWeight::get().reads(3 as u64)) + .saturating_add(RocksDbWeight::get().writes(2 as u64)) + } + // Storage: `Maintenance::MaintenanceStatus` (r:1 w:0) + // Proof: `Maintenance::MaintenanceStatus` (`max_values`: Some(1), `max_size`: Some(2), added: 497, mode: `MaxEncodedLen`) + // Storage: `SequencerStaking::SelectedSequencer` (r:1 w:0) + // Proof: `SequencerStaking::SelectedSequencer` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + // Storage: `Rolldown::LastProcessedRequestOnL2` (r:1 w:0) + // Proof: `Rolldown::LastProcessedRequestOnL2` (`max_values`: None, `max_size`: Some(33), added: 2508, mode: `MaxEncodedLen`) + // Storage: `Rolldown::DisputePeriod` (r:1 w:0) + // Proof: `Rolldown::DisputePeriod` (`max_values`: None, `max_size`: Some(33), added: 2508, mode: `MaxEncodedLen`) + // Storage: `Rolldown::SequencersRights` (r:1 w:1) + // Proof: `Rolldown::SequencersRights` (`max_values`: None, `max_size`: None, mode: `Measured`) + // Storage: `Rolldown::PendingSequencerUpdates` (r:1 w:1) + // Proof: `Rolldown::PendingSequencerUpdates` (`max_values`: None, `max_size`: None, mode: `Measured`) + // Storage: `SequencerStaking::CurrentRound` (r:1 w:0) + // Proof: `SequencerStaking::CurrentRound` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) + // Storage: `SequencerStaking::AwardedPts` (r:1 w:1) + // Proof: `SequencerStaking::AwardedPts` (`max_values`: None, `max_size`: Some(44), added: 2519, mode: `MaxEncodedLen`) + // Storage: `SequencerStaking::Points` (r:1 w:1) + // Proof: `SequencerStaking::Points` (`max_values`: None, `max_size`: Some(16), added: 2491, mode: `MaxEncodedLen`) + // Storage: `Rolldown::LastUpdateBySequencer` (r:0 w:1) + // Proof: `Rolldown::LastUpdateBySequencer` (`max_values`: None, `max_size`: Some(53), added: 2528, mode: `MaxEncodedLen`) + // Storage: `Rolldown::PendingSequencerUpdateContent` (r:0 w:1) + // Proof: `Rolldown::PendingSequencerUpdateContent` (`max_values`: None, `max_size`: None, mode: `Measured`) + fn update_l2_from_l1(x: u32, ) -> Weight { + (Weight::from_parts(78_571_518, 0)) + // Standard Error: 3_017 + .saturating_add((Weight::from_parts(2_261_755, 0)).saturating_mul(x as u64)) + .saturating_add(RocksDbWeight::get().reads(9 as u64)) + .saturating_add(RocksDbWeight::get().writes(6 as u64)) + } + // Storage: `Maintenance::MaintenanceStatus` (r:1 w:0) + // Proof: `Maintenance::MaintenanceStatus` (`max_values`: Some(1), `max_size`: Some(2), added: 497, mode: `MaxEncodedLen`) + // Storage: `SequencerStaking::SelectedSequencer` (r:1 w:0) + // Proof: `SequencerStaking::SelectedSequencer` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + // Storage: `Rolldown::LastProcessedRequestOnL2` (r:1 w:0) + // Proof: `Rolldown::LastProcessedRequestOnL2` (`max_values`: None, `max_size`: Some(33), added: 2508, mode: `MaxEncodedLen`) + // Storage: `Rolldown::DisputePeriod` (r:1 w:0) + // Proof: `Rolldown::DisputePeriod` (`max_values`: None, `max_size`: Some(33), added: 2508, mode: `MaxEncodedLen`) + // Storage: `Rolldown::SequencersRights` (r:1 w:1) + // Proof: `Rolldown::SequencersRights` (`max_values`: None, `max_size`: None, mode: `Measured`) + // Storage: `Rolldown::PendingSequencerUpdates` (r:1 w:1) + // Proof: `Rolldown::PendingSequencerUpdates` (`max_values`: None, `max_size`: None, mode: `Measured`) + // Storage: `SequencerStaking::CurrentRound` (r:1 w:0) + // Proof: `SequencerStaking::CurrentRound` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) + // Storage: `SequencerStaking::AwardedPts` (r:1 w:1) + // Proof: `SequencerStaking::AwardedPts` (`max_values`: None, `max_size`: Some(44), added: 2519, mode: `MaxEncodedLen`) + // Storage: `SequencerStaking::Points` (r:1 w:1) + // Proof: `SequencerStaking::Points` (`max_values`: None, `max_size`: Some(16), added: 2491, mode: `MaxEncodedLen`) + // Storage: `Rolldown::LastUpdateBySequencer` (r:0 w:1) + // Proof: `Rolldown::LastUpdateBySequencer` (`max_values`: None, `max_size`: Some(53), added: 2528, mode: `MaxEncodedLen`) + // Storage: `Rolldown::PendingSequencerUpdateContent` (r:0 w:1) + // Proof: `Rolldown::PendingSequencerUpdateContent` (`max_values`: None, `max_size`: None, mode: `Measured`) + fn update_l2_from_l1_unsafe(x: u32, ) -> Weight { + (Weight::from_parts(74_102_411, 0)) + // Standard Error: 3_454 + .saturating_add((Weight::from_parts(1_390_756, 0)).saturating_mul(x as u64)) + .saturating_add(RocksDbWeight::get().reads(9 as u64)) + .saturating_add(RocksDbWeight::get().writes(6 as u64)) + } + // Storage: `Maintenance::MaintenanceStatus` (r:1 w:0) + // Proof: `Maintenance::MaintenanceStatus` (`max_values`: Some(1), `max_size`: Some(2), added: 497, mode: `MaxEncodedLen`) + // Storage: `Rolldown::LastProcessedRequestOnL2` (r:1 w:0) + // Proof: `Rolldown::LastProcessedRequestOnL2` (`max_values`: None, `max_size`: Some(33), added: 2508, mode: `MaxEncodedLen`) + // Storage: `Rolldown::MaxAcceptedRequestIdOnl2` (r:1 w:1) + // Proof: `Rolldown::MaxAcceptedRequestIdOnl2` (`max_values`: None, `max_size`: Some(33), added: 2508, mode: `MaxEncodedLen`) + // Storage: `Rolldown::LastScheduledUpdateIdInExecutionQueue` (r:1 w:1) + // Proof: `Rolldown::LastScheduledUpdateIdInExecutionQueue` (`max_values`: Some(1), `max_size`: Some(16), added: 511, mode: `MaxEncodedLen`) + // Storage: `Rolldown::UpdatesExecutionQueue` (r:0 w:1) + // Proof: `Rolldown::UpdatesExecutionQueue` (`max_values`: None, `max_size`: None, mode: `Measured`) + // Storage: `Rolldown::PendingSequencerUpdateContent` (r:0 w:1) + // Proof: `Rolldown::PendingSequencerUpdateContent` (`max_values`: None, `max_size`: None, mode: `Measured`) + fn force_update_l2_from_l1(x: u32, ) -> Weight { + (Weight::from_parts(30_029_416, 0)) + // Standard Error: 3_059 + .saturating_add((Weight::from_parts(1_346_861, 0)).saturating_mul(x as u64)) + .saturating_add(RocksDbWeight::get().reads(4 as u64)) + .saturating_add(RocksDbWeight::get().writes(4 as u64)) + } + // Storage: `Maintenance::MaintenanceStatus` (r:1 w:0) + // Proof: `Maintenance::MaintenanceStatus` (`max_values`: Some(1), `max_size`: Some(2), added: 497, mode: `MaxEncodedLen`) + // Storage: `Rolldown::SequencersRights` (r:1 w:1) + // Proof: `Rolldown::SequencersRights` (`max_values`: None, `max_size`: None, mode: `Measured`) + // Storage: `Rolldown::PendingSequencerUpdates` (r:1 w:1) + // Proof: `Rolldown::PendingSequencerUpdates` (`max_values`: None, `max_size`: None, mode: `Measured`) + // Storage: `Rolldown::PendingSequencerUpdateContent` (r:1 w:1) + // Proof: `Rolldown::PendingSequencerUpdateContent` (`max_values`: None, `max_size`: None, mode: `Measured`) + // Storage: `Rolldown::L2OriginRequestId` (r:1 w:1) + // Proof: `Rolldown::L2OriginRequestId` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + // Storage: `Rolldown::AwaitingCancelResolution` (r:1 w:1) + // Proof: `Rolldown::AwaitingCancelResolution` (`max_values`: None, `max_size`: None, mode: `Measured`) + // Storage: `Rolldown::L2Requests` (r:0 w:1) + // Proof: `Rolldown::L2Requests` (`max_values`: None, `max_size`: None, mode: `Measured`) + fn cancel_requests_from_l1() -> Weight { + (Weight::from_parts(54_269_000, 0)) + .saturating_add(RocksDbWeight::get().reads(6 as u64)) + .saturating_add(RocksDbWeight::get().writes(6 as u64)) + } + // Storage: `Maintenance::MaintenanceStatus` (r:1 w:0) + // Proof: `Maintenance::MaintenanceStatus` (`max_values`: Some(1), `max_size`: Some(2), added: 497, mode: `MaxEncodedLen`) + // Storage: `Rolldown::PendingSequencerUpdates` (r:1 w:1) + // Proof: `Rolldown::PendingSequencerUpdates` (`max_values`: None, `max_size`: None, mode: `Measured`) + // Storage: `SequencerStaking::ActiveSequencers` (r:1 w:0) + // Proof: `SequencerStaking::ActiveSequencers` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + // Storage: `Rolldown::SequencersRights` (r:1 w:1) + // Proof: `Rolldown::SequencersRights` (`max_values`: None, `max_size`: None, mode: `Measured`) + fn force_cancel_requests_from_l1() -> Weight { + (Weight::from_parts(30_610_000, 0)) + .saturating_add(RocksDbWeight::get().reads(4 as u64)) + .saturating_add(RocksDbWeight::get().writes(2 as u64)) + } + // Storage: `Maintenance::MaintenanceStatus` (r:1 w:0) + // Proof: `Maintenance::MaintenanceStatus` (`max_values`: Some(1), `max_size`: Some(2), added: 497, mode: `MaxEncodedLen`) + // Storage: `AssetRegistry::L1AssetToId` (r:1 w:0) + // Proof: `AssetRegistry::L1AssetToId` (`max_values`: None, `max_size`: Some(41), added: 2516, mode: `MaxEncodedLen`) + // Storage: `Tokens::Accounts` (r:3 w:3) + // Proof: `Tokens::Accounts` (`max_values`: None, `max_size`: Some(96), added: 2571, mode: `MaxEncodedLen`) + // Storage: `System::Account` (r:1 w:1) + // Proof: `System::Account` (`max_values`: None, `max_size`: Some(52), added: 2527, mode: `MaxEncodedLen`) + // Storage: `Tokens::TotalIssuance` (r:1 w:1) + // Proof: `Tokens::TotalIssuance` (`max_values`: None, `max_size`: Some(28), added: 2503, mode: `MaxEncodedLen`) + // Storage: `Rolldown::L2OriginRequestId` (r:1 w:1) + // Proof: `Rolldown::L2OriginRequestId` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + // Storage: `Rolldown::TotalNumberOfWithdrawals` (r:1 w:1) + // Proof: `Rolldown::TotalNumberOfWithdrawals` (`max_values`: Some(1), `max_size`: Some(16), added: 511, mode: `MaxEncodedLen`) + // Storage: `Rolldown::L2Requests` (r:0 w:1) + // Proof: `Rolldown::L2Requests` (`max_values`: None, `max_size`: None, mode: `Measured`) + fn withdraw() -> Weight { + (Weight::from_parts(95_260_000, 0)) + .saturating_add(RocksDbWeight::get().reads(9 as u64)) + .saturating_add(RocksDbWeight::get().writes(8 as u64)) + } + // Storage: `Rolldown::FailedL1Deposits` (r:1 w:1) + // Proof: `Rolldown::FailedL1Deposits` (`max_values`: None, `max_size`: Some(85), added: 2560, mode: `MaxEncodedLen`) + // Storage: `Rolldown::FerriedDeposits` (r:1 w:0) + // Proof: `Rolldown::FerriedDeposits` (`max_values`: None, `max_size`: Some(69), added: 2544, mode: `MaxEncodedLen`) + // Storage: `Rolldown::L2OriginRequestId` (r:1 w:1) + // Proof: `Rolldown::L2OriginRequestId` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + // Storage: `Rolldown::L2Requests` (r:0 w:1) + // Proof: `Rolldown::L2Requests` (`max_values`: None, `max_size`: None, mode: `Measured`) + fn refund_failed_deposit() -> Weight { + (Weight::from_parts(28_730_000, 0)) + .saturating_add(RocksDbWeight::get().reads(3 as u64)) + .saturating_add(RocksDbWeight::get().writes(3 as u64)) + } + // Storage: `Rolldown::LastProcessedRequestOnL2` (r:1 w:0) + // Proof: `Rolldown::LastProcessedRequestOnL2` (`max_values`: None, `max_size`: Some(33), added: 2508, mode: `MaxEncodedLen`) + // Storage: `AssetRegistry::L1AssetToId` (r:1 w:0) + // Proof: `AssetRegistry::L1AssetToId` (`max_values`: None, `max_size`: Some(41), added: 2516, mode: `MaxEncodedLen`) + // Storage: `Tokens::Accounts` (r:2 w:2) + // Proof: `Tokens::Accounts` (`max_values`: None, `max_size`: Some(96), added: 2571, mode: `MaxEncodedLen`) + // Storage: `System::Account` (r:1 w:1) + // Proof: `System::Account` (`max_values`: None, `max_size`: Some(52), added: 2527, mode: `MaxEncodedLen`) + // Storage: `Rolldown::FerriedDeposits` (r:0 w:1) + // Proof: `Rolldown::FerriedDeposits` (`max_values`: None, `max_size`: Some(69), added: 2544, mode: `MaxEncodedLen`) + fn ferry_deposit() -> Weight { + (Weight::from_parts(62_060_000, 0)) + .saturating_add(RocksDbWeight::get().reads(5 as u64)) + .saturating_add(RocksDbWeight::get().writes(4 as u64)) + } + // Storage: `Rolldown::LastProcessedRequestOnL2` (r:1 w:0) + // Proof: `Rolldown::LastProcessedRequestOnL2` (`max_values`: None, `max_size`: Some(33), added: 2508, mode: `MaxEncodedLen`) + // Storage: `AssetRegistry::L1AssetToId` (r:1 w:0) + // Proof: `AssetRegistry::L1AssetToId` (`max_values`: None, `max_size`: Some(41), added: 2516, mode: `MaxEncodedLen`) + // Storage: `Tokens::Accounts` (r:2 w:2) + // Proof: `Tokens::Accounts` (`max_values`: None, `max_size`: Some(96), added: 2571, mode: `MaxEncodedLen`) + // Storage: `System::Account` (r:1 w:1) + // Proof: `System::Account` (`max_values`: None, `max_size`: Some(52), added: 2527, mode: `MaxEncodedLen`) + // Storage: `Rolldown::FerriedDeposits` (r:0 w:1) + // Proof: `Rolldown::FerriedDeposits` (`max_values`: None, `max_size`: Some(69), added: 2544, mode: `MaxEncodedLen`) + fn ferry_deposit_unsafe() -> Weight { + (Weight::from_parts(60_500_000, 0)) + .saturating_add(RocksDbWeight::get().reads(5 as u64)) + .saturating_add(RocksDbWeight::get().writes(4 as u64)) + } + // Storage: `AssetRegistry::L1AssetToId` (r:1 w:0) + // Proof: `AssetRegistry::L1AssetToId` (`max_values`: None, `max_size`: Some(41), added: 2516, mode: `MaxEncodedLen`) + // Storage: `Rolldown::FerriedDeposits` (r:1 w:0) + // Proof: `Rolldown::FerriedDeposits` (`max_values`: None, `max_size`: Some(69), added: 2544, mode: `MaxEncodedLen`) + // Storage: `Tokens::NextCurrencyId` (r:1 w:0) + // Proof: `Tokens::NextCurrencyId` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) + // Storage: `Tokens::Accounts` (r:1 w:1) + // Proof: `Tokens::Accounts` (`max_values`: None, `max_size`: Some(96), added: 2571, mode: `MaxEncodedLen`) + // Storage: `Tokens::TotalIssuance` (r:1 w:1) + // Proof: `Tokens::TotalIssuance` (`max_values`: None, `max_size`: Some(28), added: 2503, mode: `MaxEncodedLen`) + // Storage: `System::Account` (r:1 w:1) + // Proof: `System::Account` (`max_values`: None, `max_size`: Some(52), added: 2527, mode: `MaxEncodedLen`) + fn process_deposit() -> Weight { + (Weight::from_parts(47_830_000, 0)) + .saturating_add(RocksDbWeight::get().reads(6 as u64)) + .saturating_add(RocksDbWeight::get().writes(3 as u64)) + } + // Storage: `Rolldown::L2Requests` (r:1 w:0) + // Proof: `Rolldown::L2Requests` (`max_values`: None, `max_size`: None, mode: `Measured`) + // Storage: `SequencerStaking::ActiveSequencers` (r:1 w:1) + // Proof: `SequencerStaking::ActiveSequencers` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + // Storage: `Rolldown::SequencersRights` (r:1 w:1) + // Proof: `Rolldown::SequencersRights` (`max_values`: None, `max_size`: None, mode: `Measured`) + // Storage: `Rolldown::AwaitingCancelResolution` (r:1 w:1) + // Proof: `Rolldown::AwaitingCancelResolution` (`max_values`: None, `max_size`: None, mode: `Measured`) + // Storage: `SequencerStaking::SequencerStake` (r:1 w:1) + // Proof: `SequencerStaking::SequencerStake` (`max_values`: None, `max_size`: Some(53), added: 2528, mode: `MaxEncodedLen`) + // Storage: `SequencerStaking::SlashFineAmount` (r:1 w:0) + // Proof: `SequencerStaking::SlashFineAmount` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + // Storage: `Tokens::Accounts` (r:2 w:2) + // Proof: `Tokens::Accounts` (`max_values`: None, `max_size`: Some(96), added: 2571, mode: `MaxEncodedLen`) + // Storage: `Tokens::TotalIssuance` (r:1 w:1) + // Proof: `Tokens::TotalIssuance` (`max_values`: None, `max_size`: Some(28), added: 2503, mode: `MaxEncodedLen`) + // Storage: `SequencerStaking::MinimalStakeAmount` (r:1 w:0) + // Proof: `SequencerStaking::MinimalStakeAmount` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + // Storage: `SequencerStaking::NextSequencerIndex` (r:1 w:1) + // Proof: `SequencerStaking::NextSequencerIndex` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + // Storage: `SequencerStaking::SelectedSequencer` (r:1 w:1) + // Proof: `SequencerStaking::SelectedSequencer` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + fn process_cancel_resolution() -> Weight { + (Weight::from_parts(124_309_000, 0)) + .saturating_add(RocksDbWeight::get().reads(12 as u64)) + .saturating_add(RocksDbWeight::get().writes(9 as u64)) + } + // Storage: `Rolldown::UpdatesExecutionQueueNextId` (r:1 w:1) + // Proof: `Rolldown::UpdatesExecutionQueueNextId` (`max_values`: Some(1), `max_size`: Some(16), added: 511, mode: `MaxEncodedLen`) + // Storage: `Rolldown::UpdatesExecutionQueue` (r:2 w:0) + // Proof: `Rolldown::UpdatesExecutionQueue` (`max_values`: None, `max_size`: None, mode: `Measured`) + fn load_next_update_from_execution_queue() -> Weight { + (Weight::from_parts(11_780_000, 0)) + .saturating_add(RocksDbWeight::get().reads(3 as u64)) + .saturating_add(RocksDbWeight::get().writes(1 as u64)) + } + // Storage: `Rolldown::PendingSequencerUpdates` (r:2 w:1) + // Proof: `Rolldown::PendingSequencerUpdates` (`max_values`: None, `max_size`: None, mode: `Measured`) + // Storage: `Rolldown::DisputePeriod` (r:1 w:0) + // Proof: `Rolldown::DisputePeriod` (`max_values`: None, `max_size`: Some(33), added: 2508, mode: `MaxEncodedLen`) + // Storage: `SequencerStaking::ActiveSequencers` (r:1 w:0) + // Proof: `SequencerStaking::ActiveSequencers` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + // Storage: `Rolldown::LastMaintananceMode` (r:1 w:0) + // Proof: `Rolldown::LastMaintananceMode` (`max_values`: Some(1), `max_size`: Some(16), added: 511, mode: `MaxEncodedLen`) + // Storage: `Rolldown::MaxAcceptedRequestIdOnl2` (r:1 w:1) + // Proof: `Rolldown::MaxAcceptedRequestIdOnl2` (`max_values`: None, `max_size`: Some(33), added: 2508, mode: `MaxEncodedLen`) + // Storage: `Rolldown::LastScheduledUpdateIdInExecutionQueue` (r:1 w:1) + // Proof: `Rolldown::LastScheduledUpdateIdInExecutionQueue` (`max_values`: Some(1), `max_size`: Some(16), added: 511, mode: `MaxEncodedLen`) + // Storage: `Rolldown::UpdatesExecutionQueue` (r:0 w:1) + // Proof: `Rolldown::UpdatesExecutionQueue` (`max_values`: None, `max_size`: None, mode: `Measured`) + fn schedule_request_for_execution_if_dispute_period_has_passsed() -> Weight { + (Weight::from_parts(50_720_000, 0)) + .saturating_add(RocksDbWeight::get().reads(7 as u64)) + .saturating_add(RocksDbWeight::get().writes(4 as u64)) + } + // Storage: `Maintenance::MaintenanceStatus` (r:1 w:0) + // Proof: `Maintenance::MaintenanceStatus` (`max_values`: Some(1), `max_size`: Some(2), added: 497, mode: `MaxEncodedLen`) + // Storage: `Rolldown::L2OriginRequestId` (r:1 w:0) + // Proof: `Rolldown::L2OriginRequestId` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + // Storage: `Rolldown::L2RequestsBatchLast` (r:1 w:1) + // Proof: `Rolldown::L2RequestsBatchLast` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + // Storage: `SequencerStaking::SelectedSequencer` (r:1 w:0) + // Proof: `SequencerStaking::SelectedSequencer` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + // Storage: `Rolldown::L2RequestsBatch` (r:0 w:1) + // Proof: `Rolldown::L2RequestsBatch` (`max_values`: None, `max_size`: Some(89), added: 2564, mode: `MaxEncodedLen`) + fn maybe_create_batch() -> Weight { + (Weight::from_parts(27_320_000, 0)) + .saturating_add(RocksDbWeight::get().reads(4 as u64)) + .saturating_add(RocksDbWeight::get().writes(2 as u64)) + } + // Storage: `Rolldown::UpdatesExecutionQueueNextId` (r:1 w:0) + // Proof: `Rolldown::UpdatesExecutionQueueNextId` (`max_values`: Some(1), `max_size`: Some(16), added: 511, mode: `MaxEncodedLen`) + // Storage: `Rolldown::UpdatesExecutionQueue` (r:1 w:0) + // Proof: `Rolldown::UpdatesExecutionQueue` (`max_values`: None, `max_size`: None, mode: `Measured`) + // Storage: `Rolldown::LastMaintananceMode` (r:1 w:0) + // Proof: `Rolldown::LastMaintananceMode` (`max_values`: Some(1), `max_size`: Some(16), added: 511, mode: `MaxEncodedLen`) + // Storage: `Rolldown::PendingSequencerUpdateContent` (r:1 w:0) + // Proof: `Rolldown::PendingSequencerUpdateContent` (`max_values`: None, `max_size`: None, mode: `Measured`) + // Storage: `Rolldown::LastProcessedRequestOnL2` (r:1 w:1) + // Proof: `Rolldown::LastProcessedRequestOnL2` (`max_values`: None, `max_size`: Some(33), added: 2508, mode: `MaxEncodedLen`) + // Storage: `AssetRegistry::L1AssetToId` (r:1 w:1) + // Proof: `AssetRegistry::L1AssetToId` (`max_values`: None, `max_size`: Some(41), added: 2516, mode: `MaxEncodedLen`) + // Storage: `Tokens::NextCurrencyId` (r:1 w:1) + // Proof: `Tokens::NextCurrencyId` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) + // Storage: `Tokens::Accounts` (r:2 w:0) + // Proof: `Tokens::Accounts` (`max_values`: None, `max_size`: Some(96), added: 2571, mode: `MaxEncodedLen`) + // Storage: `AssetRegistry::Metadata` (r:1 w:1) + // Proof: `AssetRegistry::Metadata` (`max_values`: None, `max_size`: Some(153), added: 2628, mode: `MaxEncodedLen`) + // Storage: `Rolldown::FerriedDeposits` (r:50 w:0) + // Proof: `Rolldown::FerriedDeposits` (`max_values`: None, `max_size`: Some(69), added: 2544, mode: `MaxEncodedLen`) + // Storage: `Rolldown::TotalNumberOfDeposits` (r:1 w:1) + // Proof: `Rolldown::TotalNumberOfDeposits` (`max_values`: Some(1), `max_size`: Some(16), added: 511, mode: `MaxEncodedLen`) + // Storage: `AssetRegistry::IdToL1Asset` (r:0 w:1) + // Proof: `AssetRegistry::IdToL1Asset` (`max_values`: None, `max_size`: Some(33), added: 2508, mode: `MaxEncodedLen`) + fn execute_requests_from_execute_queue() -> Weight { + (Weight::from_parts(4_252_650_000, 0)) + .saturating_add(RocksDbWeight::get().reads(61 as u64)) + .saturating_add(RocksDbWeight::get().writes(6 as u64)) + } +} diff --git a/gasp-node/rollup/runtime/src/weights/pallet_session.rs b/gasp-node/rollup/runtime/src/weights/pallet_session.rs new file mode 100644 index 000000000..b2e250e7a --- /dev/null +++ b/gasp-node/rollup/runtime/src/weights/pallet_session.rs @@ -0,0 +1,104 @@ +// This file is part of Mangata. + +// Copyright (C) 2020-2022 Mangata Foundation. +// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 + +// This program 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. + +// This program 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 this program. If not, see . + +//! Autogenerated weights for pallet_session +//! +//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 32.0.0 +//! DATE: 2024-11-29, STEPS: `50`, REPEAT: 20, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! EXECUTION: , WASM-EXECUTION: Compiled, CHAIN: Some("rollup-local"), DB CACHE: 1024 + +// Executed Command: +// target/release/rollup-node +// benchmark +// pallet +// -l=info,runtime::collective=warn,xyk=warn +// --chain +// rollup-local +// --wasm-execution +// compiled +// --pallet +// * +// --extrinsic +// * +// --steps +// 50 +// --repeat +// 20 +// --template +// ./templates/module-weight-template.hbs +// --output +// ./benchmarks/ + +#![cfg_attr(rustfmt, rustfmt_skip)] +#![allow(unused_parens)] +#![allow(unused_imports)] +#![allow(clippy::unnecessary_cast)] + +use frame_support::{traits::Get, weights::{Weight, constants::RocksDbWeight}}; +use sp_std::marker::PhantomData; + +/// Weight functions needed for pallet_session. +pub trait WeightInfo { + fn set_keys() -> Weight; + fn purge_keys() -> Weight; +} + +/// Weights for pallet_session using the Mangata node and recommended hardware. +pub struct ModuleWeight(PhantomData); +impl pallet_session::WeightInfo for ModuleWeight { + // Storage: `Session::NextKeys` (r:1 w:1) + // Proof: `Session::NextKeys` (`max_values`: None, `max_size`: None, mode: `Measured`) + // Storage: `Session::KeyOwner` (r:2 w:2) + // Proof: `Session::KeyOwner` (`max_values`: None, `max_size`: None, mode: `Measured`) + fn set_keys() -> Weight { + (Weight::from_parts(31_880_000, 0)) + .saturating_add(T::DbWeight::get().reads(3 as u64)) + .saturating_add(T::DbWeight::get().writes(3 as u64)) + } + // Storage: `Session::NextKeys` (r:1 w:1) + // Proof: `Session::NextKeys` (`max_values`: None, `max_size`: None, mode: `Measured`) + // Storage: `Session::KeyOwner` (r:0 w:2) + // Proof: `Session::KeyOwner` (`max_values`: None, `max_size`: None, mode: `Measured`) + fn purge_keys() -> Weight { + (Weight::from_parts(22_130_000, 0)) + .saturating_add(T::DbWeight::get().reads(1 as u64)) + .saturating_add(T::DbWeight::get().writes(3 as u64)) + } +} + +// For backwards compatibility and tests +impl WeightInfo for () { + // Storage: `Session::NextKeys` (r:1 w:1) + // Proof: `Session::NextKeys` (`max_values`: None, `max_size`: None, mode: `Measured`) + // Storage: `Session::KeyOwner` (r:2 w:2) + // Proof: `Session::KeyOwner` (`max_values`: None, `max_size`: None, mode: `Measured`) + fn set_keys() -> Weight { + (Weight::from_parts(31_880_000, 0)) + .saturating_add(RocksDbWeight::get().reads(3 as u64)) + .saturating_add(RocksDbWeight::get().writes(3 as u64)) + } + // Storage: `Session::NextKeys` (r:1 w:1) + // Proof: `Session::NextKeys` (`max_values`: None, `max_size`: None, mode: `Measured`) + // Storage: `Session::KeyOwner` (r:0 w:2) + // Proof: `Session::KeyOwner` (`max_values`: None, `max_size`: None, mode: `Measured`) + fn purge_keys() -> Weight { + (Weight::from_parts(22_130_000, 0)) + .saturating_add(RocksDbWeight::get().reads(1 as u64)) + .saturating_add(RocksDbWeight::get().writes(3 as u64)) + } +} diff --git a/gasp-node/rollup/runtime/src/weights/pallet_timestamp.rs b/gasp-node/rollup/runtime/src/weights/pallet_timestamp.rs new file mode 100644 index 000000000..e8b5dd7bb --- /dev/null +++ b/gasp-node/rollup/runtime/src/weights/pallet_timestamp.rs @@ -0,0 +1,88 @@ +// This file is part of Mangata. + +// Copyright (C) 2020-2022 Mangata Foundation. +// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 + +// This program 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. + +// This program 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 this program. If not, see . + +//! Autogenerated weights for pallet_timestamp +//! +//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 32.0.0 +//! DATE: 2024-11-29, STEPS: `50`, REPEAT: 20, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! EXECUTION: , WASM-EXECUTION: Compiled, CHAIN: Some("rollup-local"), DB CACHE: 1024 + +// Executed Command: +// target/release/rollup-node +// benchmark +// pallet +// -l=info,runtime::collective=warn,xyk=warn +// --chain +// rollup-local +// --wasm-execution +// compiled +// --pallet +// * +// --extrinsic +// * +// --steps +// 50 +// --repeat +// 20 +// --template +// ./templates/module-weight-template.hbs +// --output +// ./benchmarks/ + +#![cfg_attr(rustfmt, rustfmt_skip)] +#![allow(unused_parens)] +#![allow(unused_imports)] +#![allow(clippy::unnecessary_cast)] + +use frame_support::{traits::Get, weights::{Weight, constants::RocksDbWeight}}; +use sp_std::marker::PhantomData; + +/// Weight functions needed for pallet_timestamp. +pub trait WeightInfo { + fn set() -> Weight; + fn on_finalize() -> Weight; +} + +/// Weights for pallet_timestamp using the Mangata node and recommended hardware. +pub struct ModuleWeight(PhantomData); +impl pallet_timestamp::WeightInfo for ModuleWeight { + // Storage: `Timestamp::Now` (r:1 w:1) + // Proof: `Timestamp::Now` (`max_values`: Some(1), `max_size`: Some(8), added: 503, mode: `MaxEncodedLen`) + fn set() -> Weight { + (Weight::from_parts(7_200_000, 0)) + .saturating_add(T::DbWeight::get().reads(1 as u64)) + .saturating_add(T::DbWeight::get().writes(1 as u64)) + } + fn on_finalize() -> Weight { + (Weight::from_parts(4_560_000, 0)) + } +} + +// For backwards compatibility and tests +impl WeightInfo for () { + // Storage: `Timestamp::Now` (r:1 w:1) + // Proof: `Timestamp::Now` (`max_values`: Some(1), `max_size`: Some(8), added: 503, mode: `MaxEncodedLen`) + fn set() -> Weight { + (Weight::from_parts(7_200_000, 0)) + .saturating_add(RocksDbWeight::get().reads(1 as u64)) + .saturating_add(RocksDbWeight::get().writes(1 as u64)) + } + fn on_finalize() -> Weight { + (Weight::from_parts(4_560_000, 0)) + } +} diff --git a/gasp-node/rollup/runtime/src/weights/pallet_treasury.rs b/gasp-node/rollup/runtime/src/weights/pallet_treasury.rs new file mode 100644 index 000000000..6dba165df --- /dev/null +++ b/gasp-node/rollup/runtime/src/weights/pallet_treasury.rs @@ -0,0 +1,282 @@ +// This file is part of Mangata. + +// Copyright (C) 2020-2022 Mangata Foundation. +// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 + +// This program 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. + +// This program 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 this program. If not, see . + +//! Autogenerated weights for pallet_treasury +//! +//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 32.0.0 +//! DATE: 2024-11-29, STEPS: `50`, REPEAT: 20, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! EXECUTION: , WASM-EXECUTION: Compiled, CHAIN: Some("rollup-local"), DB CACHE: 1024 + +// Executed Command: +// target/release/rollup-node +// benchmark +// pallet +// -l=info,runtime::collective=warn,xyk=warn +// --chain +// rollup-local +// --wasm-execution +// compiled +// --pallet +// * +// --extrinsic +// * +// --steps +// 50 +// --repeat +// 20 +// --template +// ./templates/module-weight-template.hbs +// --output +// ./benchmarks/ + +#![cfg_attr(rustfmt, rustfmt_skip)] +#![allow(unused_parens)] +#![allow(unused_imports)] +#![allow(clippy::unnecessary_cast)] + +use frame_support::{traits::Get, weights::{Weight, constants::RocksDbWeight}}; +use sp_std::marker::PhantomData; + +/// Weight functions needed for pallet_treasury. +pub trait WeightInfo { + fn spend_local() -> Weight; + fn propose_spend() -> Weight; + fn reject_proposal() -> Weight; + fn approve_proposal(p: u32, ) -> Weight; + fn remove_approval() -> Weight; + fn on_initialize_proposals(p: u32, ) -> Weight; + fn spend() -> Weight; + fn payout() -> Weight; + fn check_status() -> Weight; + fn void_spend() -> Weight; +} + +/// Weights for pallet_treasury using the Mangata node and recommended hardware. +pub struct ModuleWeight(PhantomData); +impl pallet_treasury::WeightInfo for ModuleWeight { + // Storage: `Treasury::ProposalCount` (r:1 w:1) + // Proof: `Treasury::ProposalCount` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) + // Storage: `Treasury::Approvals` (r:1 w:1) + // Proof: `Treasury::Approvals` (`max_values`: Some(1), `max_size`: Some(402), added: 897, mode: `MaxEncodedLen`) + // Storage: `Treasury::Proposals` (r:0 w:1) + // Proof: `Treasury::Proposals` (`max_values`: None, `max_size`: Some(84), added: 2559, mode: `MaxEncodedLen`) + fn spend_local() -> Weight { + (Weight::from_parts(17_190_000, 0)) + .saturating_add(T::DbWeight::get().reads(2 as u64)) + .saturating_add(T::DbWeight::get().writes(3 as u64)) + } + // Storage: `Tokens::Accounts` (r:1 w:1) + // Proof: `Tokens::Accounts` (`max_values`: None, `max_size`: Some(96), added: 2571, mode: `MaxEncodedLen`) + // Storage: `Treasury::ProposalCount` (r:1 w:1) + // Proof: `Treasury::ProposalCount` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) + // Storage: `Treasury::Proposals` (r:0 w:1) + // Proof: `Treasury::Proposals` (`max_values`: None, `max_size`: Some(84), added: 2559, mode: `MaxEncodedLen`) + fn propose_spend() -> Weight { + (Weight::from_parts(34_730_000, 0)) + .saturating_add(T::DbWeight::get().reads(2 as u64)) + .saturating_add(T::DbWeight::get().writes(3 as u64)) + } + // Storage: `Treasury::Proposals` (r:1 w:1) + // Proof: `Treasury::Proposals` (`max_values`: None, `max_size`: Some(84), added: 2559, mode: `MaxEncodedLen`) + // Storage: `Tokens::Accounts` (r:1 w:1) + // Proof: `Tokens::Accounts` (`max_values`: None, `max_size`: Some(96), added: 2571, mode: `MaxEncodedLen`) + // Storage: `Tokens::TotalIssuance` (r:1 w:1) + // Proof: `Tokens::TotalIssuance` (`max_values`: None, `max_size`: Some(28), added: 2503, mode: `MaxEncodedLen`) + fn reject_proposal() -> Weight { + (Weight::from_parts(39_850_000, 0)) + .saturating_add(T::DbWeight::get().reads(3 as u64)) + .saturating_add(T::DbWeight::get().writes(3 as u64)) + } + // Storage: `Treasury::Proposals` (r:1 w:0) + // Proof: `Treasury::Proposals` (`max_values`: None, `max_size`: Some(84), added: 2559, mode: `MaxEncodedLen`) + // Storage: `Treasury::Approvals` (r:1 w:1) + // Proof: `Treasury::Approvals` (`max_values`: Some(1), `max_size`: Some(402), added: 897, mode: `MaxEncodedLen`) + fn approve_proposal(p: u32, ) -> Weight { + (Weight::from_parts(20_024_689, 0)) + // Standard Error: 4_399 + .saturating_add((Weight::from_parts(95_197, 0)).saturating_mul(p as u64)) + .saturating_add(T::DbWeight::get().reads(2 as u64)) + .saturating_add(T::DbWeight::get().writes(1 as u64)) + } + // Storage: `Treasury::Approvals` (r:1 w:1) + // Proof: `Treasury::Approvals` (`max_values`: Some(1), `max_size`: Some(402), added: 897, mode: `MaxEncodedLen`) + fn remove_approval() -> Weight { + (Weight::from_parts(9_640_000, 0)) + .saturating_add(T::DbWeight::get().reads(1 as u64)) + .saturating_add(T::DbWeight::get().writes(1 as u64)) + } + // Storage: `Tokens::Accounts` (r:1 w:0) + // Proof: `Tokens::Accounts` (`max_values`: None, `max_size`: Some(96), added: 2571, mode: `MaxEncodedLen`) + // Storage: `Treasury::Deactivated` (r:1 w:0) + // Proof: `Treasury::Deactivated` (`max_values`: Some(1), `max_size`: Some(16), added: 511, mode: `MaxEncodedLen`) + // Storage: `Treasury::Approvals` (r:1 w:1) + // Proof: `Treasury::Approvals` (`max_values`: Some(1), `max_size`: Some(402), added: 897, mode: `MaxEncodedLen`) + // Storage: `Treasury::Proposals` (r:99 w:0) + // Proof: `Treasury::Proposals` (`max_values`: None, `max_size`: Some(84), added: 2559, mode: `MaxEncodedLen`) + // Storage: `Tokens::TotalIssuance` (r:1 w:1) + // Proof: `Tokens::TotalIssuance` (`max_values`: None, `max_size`: Some(28), added: 2503, mode: `MaxEncodedLen`) + fn on_initialize_proposals(p: u32, ) -> Weight { + (Weight::from_parts(30_956_321, 0)) + // Standard Error: 12_905 + .saturating_add((Weight::from_parts(4_212_972, 0)).saturating_mul(p as u64)) + .saturating_add(T::DbWeight::get().reads(3 as u64)) + .saturating_add(T::DbWeight::get().reads((1 as u64).saturating_mul(p as u64))) + .saturating_add(T::DbWeight::get().writes(1 as u64)) + } + // Storage: `Treasury::SpendCount` (r:1 w:1) + // Proof: `Treasury::SpendCount` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) + // Storage: `Treasury::Spends` (r:0 w:1) + // Proof: `Treasury::Spends` (`max_values`: None, `max_size`: Some(57), added: 2532, mode: `MaxEncodedLen`) + fn spend() -> Weight { + (Weight::from_parts(15_480_000, 0)) + .saturating_add(T::DbWeight::get().reads(1 as u64)) + .saturating_add(T::DbWeight::get().writes(2 as u64)) + } + // Storage: `Treasury::Spends` (r:1 w:1) + // Proof: `Treasury::Spends` (`max_values`: None, `max_size`: Some(57), added: 2532, mode: `MaxEncodedLen`) + // Storage: `Tokens::Accounts` (r:2 w:2) + // Proof: `Tokens::Accounts` (`max_values`: None, `max_size`: Some(96), added: 2571, mode: `MaxEncodedLen`) + // Storage: `System::Account` (r:2 w:1) + // Proof: `System::Account` (`max_values`: None, `max_size`: Some(52), added: 2527, mode: `MaxEncodedLen`) + fn payout() -> Weight { + (Weight::from_parts(57_680_000, 0)) + .saturating_add(T::DbWeight::get().reads(5 as u64)) + .saturating_add(T::DbWeight::get().writes(4 as u64)) + } + // Storage: `Treasury::Spends` (r:1 w:1) + // Proof: `Treasury::Spends` (`max_values`: None, `max_size`: Some(57), added: 2532, mode: `MaxEncodedLen`) + fn check_status() -> Weight { + (Weight::from_parts(18_130_000, 0)) + .saturating_add(T::DbWeight::get().reads(1 as u64)) + .saturating_add(T::DbWeight::get().writes(1 as u64)) + } + // Storage: `Treasury::Spends` (r:1 w:1) + // Proof: `Treasury::Spends` (`max_values`: None, `max_size`: Some(57), added: 2532, mode: `MaxEncodedLen`) + fn void_spend() -> Weight { + (Weight::from_parts(16_020_000, 0)) + .saturating_add(T::DbWeight::get().reads(1 as u64)) + .saturating_add(T::DbWeight::get().writes(1 as u64)) + } +} + +// For backwards compatibility and tests +impl WeightInfo for () { + // Storage: `Treasury::ProposalCount` (r:1 w:1) + // Proof: `Treasury::ProposalCount` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) + // Storage: `Treasury::Approvals` (r:1 w:1) + // Proof: `Treasury::Approvals` (`max_values`: Some(1), `max_size`: Some(402), added: 897, mode: `MaxEncodedLen`) + // Storage: `Treasury::Proposals` (r:0 w:1) + // Proof: `Treasury::Proposals` (`max_values`: None, `max_size`: Some(84), added: 2559, mode: `MaxEncodedLen`) + fn spend_local() -> Weight { + (Weight::from_parts(17_190_000, 0)) + .saturating_add(RocksDbWeight::get().reads(2 as u64)) + .saturating_add(RocksDbWeight::get().writes(3 as u64)) + } + // Storage: `Tokens::Accounts` (r:1 w:1) + // Proof: `Tokens::Accounts` (`max_values`: None, `max_size`: Some(96), added: 2571, mode: `MaxEncodedLen`) + // Storage: `Treasury::ProposalCount` (r:1 w:1) + // Proof: `Treasury::ProposalCount` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) + // Storage: `Treasury::Proposals` (r:0 w:1) + // Proof: `Treasury::Proposals` (`max_values`: None, `max_size`: Some(84), added: 2559, mode: `MaxEncodedLen`) + fn propose_spend() -> Weight { + (Weight::from_parts(34_730_000, 0)) + .saturating_add(RocksDbWeight::get().reads(2 as u64)) + .saturating_add(RocksDbWeight::get().writes(3 as u64)) + } + // Storage: `Treasury::Proposals` (r:1 w:1) + // Proof: `Treasury::Proposals` (`max_values`: None, `max_size`: Some(84), added: 2559, mode: `MaxEncodedLen`) + // Storage: `Tokens::Accounts` (r:1 w:1) + // Proof: `Tokens::Accounts` (`max_values`: None, `max_size`: Some(96), added: 2571, mode: `MaxEncodedLen`) + // Storage: `Tokens::TotalIssuance` (r:1 w:1) + // Proof: `Tokens::TotalIssuance` (`max_values`: None, `max_size`: Some(28), added: 2503, mode: `MaxEncodedLen`) + fn reject_proposal() -> Weight { + (Weight::from_parts(39_850_000, 0)) + .saturating_add(RocksDbWeight::get().reads(3 as u64)) + .saturating_add(RocksDbWeight::get().writes(3 as u64)) + } + // Storage: `Treasury::Proposals` (r:1 w:0) + // Proof: `Treasury::Proposals` (`max_values`: None, `max_size`: Some(84), added: 2559, mode: `MaxEncodedLen`) + // Storage: `Treasury::Approvals` (r:1 w:1) + // Proof: `Treasury::Approvals` (`max_values`: Some(1), `max_size`: Some(402), added: 897, mode: `MaxEncodedLen`) + fn approve_proposal(p: u32, ) -> Weight { + (Weight::from_parts(20_024_689, 0)) + // Standard Error: 4_399 + .saturating_add((Weight::from_parts(95_197, 0)).saturating_mul(p as u64)) + .saturating_add(RocksDbWeight::get().reads(2 as u64)) + .saturating_add(RocksDbWeight::get().writes(1 as u64)) + } + // Storage: `Treasury::Approvals` (r:1 w:1) + // Proof: `Treasury::Approvals` (`max_values`: Some(1), `max_size`: Some(402), added: 897, mode: `MaxEncodedLen`) + fn remove_approval() -> Weight { + (Weight::from_parts(9_640_000, 0)) + .saturating_add(RocksDbWeight::get().reads(1 as u64)) + .saturating_add(RocksDbWeight::get().writes(1 as u64)) + } + // Storage: `Tokens::Accounts` (r:1 w:0) + // Proof: `Tokens::Accounts` (`max_values`: None, `max_size`: Some(96), added: 2571, mode: `MaxEncodedLen`) + // Storage: `Treasury::Deactivated` (r:1 w:0) + // Proof: `Treasury::Deactivated` (`max_values`: Some(1), `max_size`: Some(16), added: 511, mode: `MaxEncodedLen`) + // Storage: `Treasury::Approvals` (r:1 w:1) + // Proof: `Treasury::Approvals` (`max_values`: Some(1), `max_size`: Some(402), added: 897, mode: `MaxEncodedLen`) + // Storage: `Treasury::Proposals` (r:99 w:0) + // Proof: `Treasury::Proposals` (`max_values`: None, `max_size`: Some(84), added: 2559, mode: `MaxEncodedLen`) + // Storage: `Tokens::TotalIssuance` (r:1 w:1) + // Proof: `Tokens::TotalIssuance` (`max_values`: None, `max_size`: Some(28), added: 2503, mode: `MaxEncodedLen`) + fn on_initialize_proposals(p: u32, ) -> Weight { + (Weight::from_parts(30_956_321, 0)) + // Standard Error: 12_905 + .saturating_add((Weight::from_parts(4_212_972, 0)).saturating_mul(p as u64)) + .saturating_add(RocksDbWeight::get().reads(3 as u64)) + .saturating_add(RocksDbWeight::get().reads((1 as u64).saturating_mul(p as u64))) + .saturating_add(RocksDbWeight::get().writes(1 as u64)) + } + // Storage: `Treasury::SpendCount` (r:1 w:1) + // Proof: `Treasury::SpendCount` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) + // Storage: `Treasury::Spends` (r:0 w:1) + // Proof: `Treasury::Spends` (`max_values`: None, `max_size`: Some(57), added: 2532, mode: `MaxEncodedLen`) + fn spend() -> Weight { + (Weight::from_parts(15_480_000, 0)) + .saturating_add(RocksDbWeight::get().reads(1 as u64)) + .saturating_add(RocksDbWeight::get().writes(2 as u64)) + } + // Storage: `Treasury::Spends` (r:1 w:1) + // Proof: `Treasury::Spends` (`max_values`: None, `max_size`: Some(57), added: 2532, mode: `MaxEncodedLen`) + // Storage: `Tokens::Accounts` (r:2 w:2) + // Proof: `Tokens::Accounts` (`max_values`: None, `max_size`: Some(96), added: 2571, mode: `MaxEncodedLen`) + // Storage: `System::Account` (r:2 w:1) + // Proof: `System::Account` (`max_values`: None, `max_size`: Some(52), added: 2527, mode: `MaxEncodedLen`) + fn payout() -> Weight { + (Weight::from_parts(57_680_000, 0)) + .saturating_add(RocksDbWeight::get().reads(5 as u64)) + .saturating_add(RocksDbWeight::get().writes(4 as u64)) + } + // Storage: `Treasury::Spends` (r:1 w:1) + // Proof: `Treasury::Spends` (`max_values`: None, `max_size`: Some(57), added: 2532, mode: `MaxEncodedLen`) + fn check_status() -> Weight { + (Weight::from_parts(18_130_000, 0)) + .saturating_add(RocksDbWeight::get().reads(1 as u64)) + .saturating_add(RocksDbWeight::get().writes(1 as u64)) + } + // Storage: `Treasury::Spends` (r:1 w:1) + // Proof: `Treasury::Spends` (`max_values`: None, `max_size`: Some(57), added: 2532, mode: `MaxEncodedLen`) + fn void_spend() -> Weight { + (Weight::from_parts(16_020_000, 0)) + .saturating_add(RocksDbWeight::get().reads(1 as u64)) + .saturating_add(RocksDbWeight::get().writes(1 as u64)) + } +} diff --git a/gasp-node/rollup/runtime/src/weights/pallet_utility_mangata.rs b/gasp-node/rollup/runtime/src/weights/pallet_utility_mangata.rs new file mode 100644 index 000000000..02bc323a1 --- /dev/null +++ b/gasp-node/rollup/runtime/src/weights/pallet_utility_mangata.rs @@ -0,0 +1,113 @@ +// This file is part of Mangata. + +// Copyright (C) 2020-2022 Mangata Foundation. +// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 + +// This program 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. + +// This program 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 this program. If not, see . + +//! Autogenerated weights for pallet_utility_mangata +//! +//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 32.0.0 +//! DATE: 2024-11-29, STEPS: `50`, REPEAT: 20, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! EXECUTION: , WASM-EXECUTION: Compiled, CHAIN: Some("rollup-local"), DB CACHE: 1024 + +// Executed Command: +// target/release/rollup-node +// benchmark +// pallet +// -l=info,runtime::collective=warn,xyk=warn +// --chain +// rollup-local +// --wasm-execution +// compiled +// --pallet +// * +// --extrinsic +// * +// --steps +// 50 +// --repeat +// 20 +// --template +// ./templates/module-weight-template.hbs +// --output +// ./benchmarks/ + +#![cfg_attr(rustfmt, rustfmt_skip)] +#![allow(unused_parens)] +#![allow(unused_imports)] +#![allow(clippy::unnecessary_cast)] + +use frame_support::{traits::Get, weights::{Weight, constants::RocksDbWeight}}; +use sp_std::marker::PhantomData; + +/// Weight functions needed for pallet_utility_mangata. +pub trait WeightInfo { + fn batch(c: u32, ) -> Weight; + fn as_derivative() -> Weight; + fn batch_all(c: u32, ) -> Weight; + fn dispatch_as() -> Weight; + fn force_batch(c: u32, ) -> Weight; +} + +/// Weights for pallet_utility_mangata using the Mangata node and recommended hardware. +pub struct ModuleWeight(PhantomData); +impl pallet_utility_mangata::WeightInfo for ModuleWeight { + fn batch(c: u32, ) -> Weight { + (Weight::from_parts(20_172_126, 0)) + // Standard Error: 2_496 + .saturating_add((Weight::from_parts(4_622_316, 0)).saturating_mul(c as u64)) + } + fn as_derivative() -> Weight { + (Weight::from_parts(7_500_000, 0)) + } + fn batch_all(c: u32, ) -> Weight { + (Weight::from_parts(28_349_372, 0)) + // Standard Error: 2_569 + .saturating_add((Weight::from_parts(5_008_372, 0)).saturating_mul(c as u64)) + } + fn dispatch_as() -> Weight { + (Weight::from_parts(10_910_000, 0)) + } + fn force_batch(c: u32, ) -> Weight { + (Weight::from_parts(34_255_181, 0)) + // Standard Error: 3_663 + .saturating_add((Weight::from_parts(4_596_415, 0)).saturating_mul(c as u64)) + } +} + +// For backwards compatibility and tests +impl WeightInfo for () { + fn batch(c: u32, ) -> Weight { + (Weight::from_parts(20_172_126, 0)) + // Standard Error: 2_496 + .saturating_add((Weight::from_parts(4_622_316, 0)).saturating_mul(c as u64)) + } + fn as_derivative() -> Weight { + (Weight::from_parts(7_500_000, 0)) + } + fn batch_all(c: u32, ) -> Weight { + (Weight::from_parts(28_349_372, 0)) + // Standard Error: 2_569 + .saturating_add((Weight::from_parts(5_008_372, 0)).saturating_mul(c as u64)) + } + fn dispatch_as() -> Weight { + (Weight::from_parts(10_910_000, 0)) + } + fn force_batch(c: u32, ) -> Weight { + (Weight::from_parts(34_255_181, 0)) + // Standard Error: 3_663 + .saturating_add((Weight::from_parts(4_596_415, 0)).saturating_mul(c as u64)) + } +} diff --git a/gasp-node/rollup/runtime/src/weights/pallet_vesting_mangata.rs b/gasp-node/rollup/runtime/src/weights/pallet_vesting_mangata.rs new file mode 100644 index 000000000..0fc0bf9d6 --- /dev/null +++ b/gasp-node/rollup/runtime/src/weights/pallet_vesting_mangata.rs @@ -0,0 +1,274 @@ +// This file is part of Mangata. + +// Copyright (C) 2020-2022 Mangata Foundation. +// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 + +// This program 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. + +// This program 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 this program. If not, see . + +//! Autogenerated weights for pallet_vesting_mangata +//! +//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev +//! DATE: 2024-01-09, STEPS: `50`, REPEAT: 20, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! EXECUTION: , WASM-EXECUTION: Compiled, CHAIN: Some("mangata-kusama"), DB CACHE: 1024 + +// Executed Command: +// target/release/mangata-node +// benchmark +// pallet +// -l=info,runtime::collective=warn,xyk=warn +// --chain +// mangata-kusama +// --execution +// wasm +// --wasm-execution +// compiled +// --pallet +// * +// --extrinsic +// * +// --steps +// 50 +// --repeat +// 20 +// --template +// ./templates/module-weight-template.hbs +// --output +// ./benchmarks/ + +#![cfg_attr(rustfmt, rustfmt_skip)] +#![allow(unused_parens)] +#![allow(unused_imports)] +#![allow(clippy::unnecessary_cast)] + +use frame_support::{traits::Get, weights::{Weight, constants::RocksDbWeight}}; +use sp_std::marker::PhantomData; + +/// Weight functions needed for pallet_vesting_mangata. +pub trait WeightInfo { + fn vest_locked(l: u32, s: u32, ) -> Weight; + fn vest_unlocked(l: u32, s: u32, ) -> Weight; + fn vest_other_locked(l: u32, s: u32, ) -> Weight; + fn vest_other_unlocked(l: u32, s: u32, ) -> Weight; + fn force_vested_transfer(l: u32, s: u32, ) -> Weight; + fn not_unlocking_merge_schedules(l: u32, s: u32, ) -> Weight; + fn unlocking_merge_schedules(l: u32, s: u32, ) -> Weight; + fn force_remove_vesting_schedule(l: u32, s: u32, ) -> Weight; +} + +/// Weights for pallet_vesting_mangata using the Mangata node and recommended hardware. +pub struct ModuleWeight(PhantomData); +impl pallet_vesting_mangata::WeightInfo for ModuleWeight { + // Storage: `Vesting::Vesting` (r:1 w:1) + // Proof: `Vesting::Vesting` (`max_values`: None, `max_size`: Some(1869), added: 4344, mode: `MaxEncodedLen`) + // Storage: `Tokens::Locks` (r:1 w:0) + // Proof: `Tokens::Locks` (`max_values`: None, `max_size`: Some(1261), added: 3736, mode: `MaxEncodedLen`) + // Storage: `Tokens::Accounts` (r:1 w:1) + // Proof: `Tokens::Accounts` (`max_values`: None, `max_size`: Some(108), added: 2583, mode: `MaxEncodedLen`) + fn vest_locked(_l: u32, s: u32, ) -> Weight { + (Weight::from_parts(48_427_927, 0)) + // Standard Error: 8_514 + .saturating_add((Weight::from_parts(185_409, 0)).saturating_mul(s as u64)) + .saturating_add(T::DbWeight::get().reads(3 as u64)) + .saturating_add(T::DbWeight::get().writes(2 as u64)) + } + // Storage: `Vesting::Vesting` (r:1 w:1) + // Proof: `Vesting::Vesting` (`max_values`: None, `max_size`: Some(1869), added: 4344, mode: `MaxEncodedLen`) + // Storage: `Tokens::Locks` (r:1 w:1) + // Proof: `Tokens::Locks` (`max_values`: None, `max_size`: Some(1261), added: 3736, mode: `MaxEncodedLen`) + // Storage: `Tokens::Accounts` (r:1 w:1) + // Proof: `Tokens::Accounts` (`max_values`: None, `max_size`: Some(108), added: 2583, mode: `MaxEncodedLen`) + fn vest_unlocked(_l: u32, s: u32, ) -> Weight { + (Weight::from_parts(65_301_532, 0)) + // Standard Error: 6_745 + .saturating_add((Weight::from_parts(53_074, 0)).saturating_mul(s as u64)) + .saturating_add(T::DbWeight::get().reads(3 as u64)) + .saturating_add(T::DbWeight::get().writes(3 as u64)) + } + // Storage: `Vesting::Vesting` (r:1 w:1) + // Proof: `Vesting::Vesting` (`max_values`: None, `max_size`: Some(1869), added: 4344, mode: `MaxEncodedLen`) + // Storage: `Tokens::Locks` (r:1 w:0) + // Proof: `Tokens::Locks` (`max_values`: None, `max_size`: Some(1261), added: 3736, mode: `MaxEncodedLen`) + // Storage: `Tokens::Accounts` (r:1 w:1) + // Proof: `Tokens::Accounts` (`max_values`: None, `max_size`: Some(108), added: 2583, mode: `MaxEncodedLen`) + fn vest_other_locked(_l: u32, s: u32, ) -> Weight { + (Weight::from_parts(49_635_157, 0)) + // Standard Error: 9_592 + .saturating_add((Weight::from_parts(168_802, 0)).saturating_mul(s as u64)) + .saturating_add(T::DbWeight::get().reads(3 as u64)) + .saturating_add(T::DbWeight::get().writes(2 as u64)) + } + // Storage: `Vesting::Vesting` (r:1 w:1) + // Proof: `Vesting::Vesting` (`max_values`: None, `max_size`: Some(1869), added: 4344, mode: `MaxEncodedLen`) + // Storage: `Tokens::Locks` (r:1 w:1) + // Proof: `Tokens::Locks` (`max_values`: None, `max_size`: Some(1261), added: 3736, mode: `MaxEncodedLen`) + // Storage: `Tokens::Accounts` (r:1 w:1) + // Proof: `Tokens::Accounts` (`max_values`: None, `max_size`: Some(108), added: 2583, mode: `MaxEncodedLen`) + // Storage: `System::Account` (r:1 w:1) + // Proof: `System::Account` (`max_values`: None, `max_size`: Some(64), added: 2539, mode: `MaxEncodedLen`) + fn vest_other_unlocked(_l: u32, s: u32, ) -> Weight { + (Weight::from_parts(67_351_274, 0)) + // Standard Error: 7_411 + .saturating_add((Weight::from_parts(84_577, 0)).saturating_mul(s as u64)) + .saturating_add(T::DbWeight::get().reads(3 as u64)) + .saturating_add(T::DbWeight::get().writes(3 as u64)) + } + // Storage: `Vesting::Vesting` (r:1 w:1) + // Proof: `Vesting::Vesting` (`max_values`: None, `max_size`: Some(1869), added: 4344, mode: `MaxEncodedLen`) + // Storage: `Tokens::Accounts` (r:2 w:2) + // Proof: `Tokens::Accounts` (`max_values`: None, `max_size`: Some(108), added: 2583, mode: `MaxEncodedLen`) + // Storage: `System::Account` (r:1 w:0) + // Proof: `System::Account` (`max_values`: None, `max_size`: Some(64), added: 2539, mode: `MaxEncodedLen`) + // Storage: `Tokens::Locks` (r:1 w:0) + // Proof: `Tokens::Locks` (`max_values`: None, `max_size`: Some(1261), added: 3736, mode: `MaxEncodedLen`) + fn force_vested_transfer(_l: u32, s: u32, ) -> Weight { + (Weight::from_parts(88_063_629, 0)) + // Standard Error: 14_579 + .saturating_add((Weight::from_parts(210_310, 0)).saturating_mul(s as u64)) + .saturating_add(T::DbWeight::get().reads(5 as u64)) + .saturating_add(T::DbWeight::get().writes(3 as u64)) + } + // Storage: `Vesting::Vesting` (r:1 w:1) + // Proof: `Vesting::Vesting` (`max_values`: None, `max_size`: Some(1869), added: 4344, mode: `MaxEncodedLen`) + // Storage: `Tokens::Locks` (r:1 w:0) + // Proof: `Tokens::Locks` (`max_values`: None, `max_size`: Some(1261), added: 3736, mode: `MaxEncodedLen`) + // Storage: `Tokens::Accounts` (r:1 w:1) + // Proof: `Tokens::Accounts` (`max_values`: None, `max_size`: Some(108), added: 2583, mode: `MaxEncodedLen`) + fn not_unlocking_merge_schedules(_l: u32, s: u32, ) -> Weight { + (Weight::from_parts(50_762_808, 0)) + // Standard Error: 8_663 + .saturating_add((Weight::from_parts(163_040, 0)).saturating_mul(s as u64)) + .saturating_add(T::DbWeight::get().reads(3 as u64)) + .saturating_add(T::DbWeight::get().writes(2 as u64)) + } + // Storage: `Vesting::Vesting` (r:1 w:1) + // Proof: `Vesting::Vesting` (`max_values`: None, `max_size`: Some(1869), added: 4344, mode: `MaxEncodedLen`) + // Storage: `Tokens::Locks` (r:1 w:0) + // Proof: `Tokens::Locks` (`max_values`: None, `max_size`: Some(1261), added: 3736, mode: `MaxEncodedLen`) + // Storage: `Tokens::Accounts` (r:1 w:1) + // Proof: `Tokens::Accounts` (`max_values`: None, `max_size`: Some(108), added: 2583, mode: `MaxEncodedLen`) + fn unlocking_merge_schedules(_l: u32, s: u32, ) -> Weight { + (Weight::from_parts(49_779_765, 0)) + // Standard Error: 13_870 + .saturating_add((Weight::from_parts(262_141, 0)).saturating_mul(s as u64)) + .saturating_add(T::DbWeight::get().reads(3 as u64)) + .saturating_add(T::DbWeight::get().writes(2 as u64)) + } + + fn force_remove_vesting_schedule(l: u32, s: u32, ) -> Weight { + Weight::from_parts(0, 0) + } +} + +// For backwards compatibility and tests +impl WeightInfo for () { + // Storage: `Vesting::Vesting` (r:1 w:1) + // Proof: `Vesting::Vesting` (`max_values`: None, `max_size`: Some(1869), added: 4344, mode: `MaxEncodedLen`) + // Storage: `Tokens::Locks` (r:1 w:0) + // Proof: `Tokens::Locks` (`max_values`: None, `max_size`: Some(1261), added: 3736, mode: `MaxEncodedLen`) + // Storage: `Tokens::Accounts` (r:1 w:1) + // Proof: `Tokens::Accounts` (`max_values`: None, `max_size`: Some(108), added: 2583, mode: `MaxEncodedLen`) + fn vest_locked(_l: u32, s: u32, ) -> Weight { + (Weight::from_parts(48_427_927, 0)) + // Standard Error: 8_514 + .saturating_add((Weight::from_parts(185_409, 0)).saturating_mul(s as u64)) + .saturating_add(RocksDbWeight::get().reads(3 as u64)) + .saturating_add(RocksDbWeight::get().writes(2 as u64)) + } + // Storage: `Vesting::Vesting` (r:1 w:1) + // Proof: `Vesting::Vesting` (`max_values`: None, `max_size`: Some(1869), added: 4344, mode: `MaxEncodedLen`) + // Storage: `Tokens::Locks` (r:1 w:1) + // Proof: `Tokens::Locks` (`max_values`: None, `max_size`: Some(1261), added: 3736, mode: `MaxEncodedLen`) + // Storage: `Tokens::Accounts` (r:1 w:1) + // Proof: `Tokens::Accounts` (`max_values`: None, `max_size`: Some(108), added: 2583, mode: `MaxEncodedLen`) + fn vest_unlocked(_l: u32, s: u32, ) -> Weight { + (Weight::from_parts(65_301_532, 0)) + // Standard Error: 6_745 + .saturating_add((Weight::from_parts(53_074, 0)).saturating_mul(s as u64)) + .saturating_add(RocksDbWeight::get().reads(3 as u64)) + .saturating_add(RocksDbWeight::get().writes(3 as u64)) + } + // Storage: `Vesting::Vesting` (r:1 w:1) + // Proof: `Vesting::Vesting` (`max_values`: None, `max_size`: Some(1869), added: 4344, mode: `MaxEncodedLen`) + // Storage: `Tokens::Locks` (r:1 w:0) + // Proof: `Tokens::Locks` (`max_values`: None, `max_size`: Some(1261), added: 3736, mode: `MaxEncodedLen`) + // Storage: `Tokens::Accounts` (r:1 w:1) + // Proof: `Tokens::Accounts` (`max_values`: None, `max_size`: Some(108), added: 2583, mode: `MaxEncodedLen`) + fn vest_other_locked(_l: u32, s: u32, ) -> Weight { + (Weight::from_parts(49_635_157, 0)) + // Standard Error: 9_592 + .saturating_add((Weight::from_parts(168_802, 0)).saturating_mul(s as u64)) + .saturating_add(RocksDbWeight::get().reads(3 as u64)) + .saturating_add(RocksDbWeight::get().writes(2 as u64)) + } + // Storage: `Vesting::Vesting` (r:1 w:1) + // Proof: `Vesting::Vesting` (`max_values`: None, `max_size`: Some(1869), added: 4344, mode: `MaxEncodedLen`) + // Storage: `Tokens::Locks` (r:1 w:1) + // Proof: `Tokens::Locks` (`max_values`: None, `max_size`: Some(1261), added: 3736, mode: `MaxEncodedLen`) + // Storage: `Tokens::Accounts` (r:1 w:1) + // Proof: `Tokens::Accounts` (`max_values`: None, `max_size`: Some(108), added: 2583, mode: `MaxEncodedLen`) + // Storage: `System::Account` (r:1 w:1) + // Proof: `System::Account` (`max_values`: None, `max_size`: Some(64), added: 2539, mode: `MaxEncodedLen`) + fn vest_other_unlocked(_l: u32, s: u32, ) -> Weight { + (Weight::from_parts(67_351_274, 0)) + // Standard Error: 7_411 + .saturating_add((Weight::from_parts(84_577, 0)).saturating_mul(s as u64)) + .saturating_add(RocksDbWeight::get().reads(3 as u64)) + .saturating_add(RocksDbWeight::get().writes(3 as u64)) + } + // Storage: `Vesting::Vesting` (r:1 w:1) + // Proof: `Vesting::Vesting` (`max_values`: None, `max_size`: Some(1869), added: 4344, mode: `MaxEncodedLen`) + // Storage: `Tokens::Accounts` (r:2 w:2) + // Proof: `Tokens::Accounts` (`max_values`: None, `max_size`: Some(108), added: 2583, mode: `MaxEncodedLen`) + // Storage: `System::Account` (r:1 w:0) + // Proof: `System::Account` (`max_values`: None, `max_size`: Some(64), added: 2539, mode: `MaxEncodedLen`) + // Storage: `Tokens::Locks` (r:1 w:0) + // Proof: `Tokens::Locks` (`max_values`: None, `max_size`: Some(1261), added: 3736, mode: `MaxEncodedLen`) + fn force_vested_transfer(_l: u32, s: u32, ) -> Weight { + (Weight::from_parts(88_063_629, 0)) + // Standard Error: 14_579 + .saturating_add((Weight::from_parts(210_310, 0)).saturating_mul(s as u64)) + .saturating_add(RocksDbWeight::get().reads(5 as u64)) + .saturating_add(RocksDbWeight::get().writes(3 as u64)) + } + // Storage: `Vesting::Vesting` (r:1 w:1) + // Proof: `Vesting::Vesting` (`max_values`: None, `max_size`: Some(1869), added: 4344, mode: `MaxEncodedLen`) + // Storage: `Tokens::Locks` (r:1 w:0) + // Proof: `Tokens::Locks` (`max_values`: None, `max_size`: Some(1261), added: 3736, mode: `MaxEncodedLen`) + // Storage: `Tokens::Accounts` (r:1 w:1) + // Proof: `Tokens::Accounts` (`max_values`: None, `max_size`: Some(108), added: 2583, mode: `MaxEncodedLen`) + fn not_unlocking_merge_schedules(_l: u32, s: u32, ) -> Weight { + (Weight::from_parts(50_762_808, 0)) + // Standard Error: 8_663 + .saturating_add((Weight::from_parts(163_040, 0)).saturating_mul(s as u64)) + .saturating_add(RocksDbWeight::get().reads(3 as u64)) + .saturating_add(RocksDbWeight::get().writes(2 as u64)) + } + // Storage: `Vesting::Vesting` (r:1 w:1) + // Proof: `Vesting::Vesting` (`max_values`: None, `max_size`: Some(1869), added: 4344, mode: `MaxEncodedLen`) + // Storage: `Tokens::Locks` (r:1 w:0) + // Proof: `Tokens::Locks` (`max_values`: None, `max_size`: Some(1261), added: 3736, mode: `MaxEncodedLen`) + // Storage: `Tokens::Accounts` (r:1 w:1) + // Proof: `Tokens::Accounts` (`max_values`: None, `max_size`: Some(108), added: 2583, mode: `MaxEncodedLen`) + fn unlocking_merge_schedules(_l: u32, s: u32, ) -> Weight { + (Weight::from_parts(49_779_765, 0)) + // Standard Error: 13_870 + .saturating_add((Weight::from_parts(262_141, 0)).saturating_mul(s as u64)) + .saturating_add(RocksDbWeight::get().reads(3 as u64)) + .saturating_add(RocksDbWeight::get().writes(2 as u64)) + } + + fn force_remove_vesting_schedule(l: u32, s: u32, ) -> Weight { + Weight::from_parts(0, 0) + } +} diff --git a/gasp-node/rollup/runtime/src/weights/pallet_xyk.rs b/gasp-node/rollup/runtime/src/weights/pallet_xyk.rs new file mode 100644 index 000000000..c28167568 --- /dev/null +++ b/gasp-node/rollup/runtime/src/weights/pallet_xyk.rs @@ -0,0 +1,556 @@ +// This file is part of Mangata. + +// Copyright (C) 2020-2022 Mangata Foundation. +// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 + +// This program 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. + +// This program 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 this program. If not, see . + +//! Autogenerated weights for pallet_xyk +//! +//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 32.0.0 +//! DATE: 2024-11-29, STEPS: `50`, REPEAT: 20, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! EXECUTION: , WASM-EXECUTION: Compiled, CHAIN: Some("rollup-local"), DB CACHE: 1024 + +// Executed Command: +// target/release/rollup-node +// benchmark +// pallet +// -l=info,runtime::collective=warn,xyk=warn +// --chain +// rollup-local +// --wasm-execution +// compiled +// --pallet +// * +// --extrinsic +// * +// --steps +// 50 +// --repeat +// 20 +// --template +// ./templates/module-weight-template.hbs +// --output +// ./benchmarks/ + +#![cfg_attr(rustfmt, rustfmt_skip)] +#![allow(unused_parens)] +#![allow(unused_imports)] +#![allow(clippy::unnecessary_cast)] + +use frame_support::{traits::Get, weights::{Weight, constants::RocksDbWeight}}; +use sp_std::marker::PhantomData; + +/// Weight functions needed for pallet_xyk. +pub trait WeightInfo { + fn create_pool() -> Weight; + fn sell_asset() -> Weight; + fn multiswap_sell_asset(x: u32, ) -> Weight; + fn buy_asset() -> Weight; + fn multiswap_buy_asset(x: u32, ) -> Weight; + fn mint_liquidity() -> Weight; + fn mint_liquidity_using_vesting_native_tokens() -> Weight; + fn burn_liquidity() -> Weight; + fn provide_liquidity_with_conversion() -> Weight; + fn compound_rewards() -> Weight; +} + +/// Weights for pallet_xyk using the Mangata node and recommended hardware. +pub struct ModuleWeight(PhantomData); +impl pallet_xyk::WeightInfo for ModuleWeight { + // Storage: `AssetRegistry::Metadata` (r:3 w:1) + // Proof: `AssetRegistry::Metadata` (`max_values`: None, `max_size`: Some(153), added: 2628, mode: `MaxEncodedLen`) + // Storage: `Bootstrap::BootstrapSchedule` (r:1 w:0) + // Proof: `Bootstrap::BootstrapSchedule` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + // Storage: `Xyk::Pools` (r:2 w:1) + // Proof: `Xyk::Pools` (`max_values`: None, `max_size`: Some(56), added: 2531, mode: `MaxEncodedLen`) + // Storage: `Tokens::Accounts` (r:5 w:5) + // Proof: `Tokens::Accounts` (`max_values`: None, `max_size`: Some(96), added: 2571, mode: `MaxEncodedLen`) + // Storage: `System::Account` (r:1 w:1) + // Proof: `System::Account` (`max_values`: None, `max_size`: Some(52), added: 2527, mode: `MaxEncodedLen`) + // Storage: `Tokens::NextCurrencyId` (r:1 w:1) + // Proof: `Tokens::NextCurrencyId` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) + // Storage: `Tokens::TotalIssuance` (r:1 w:1) + // Proof: `Tokens::TotalIssuance` (`max_values`: None, `max_size`: Some(28), added: 2503, mode: `MaxEncodedLen`) + // Storage: `Xyk::LiquidityAssets` (r:0 w:1) + // Proof: `Xyk::LiquidityAssets` (`max_values`: None, `max_size`: Some(29), added: 2504, mode: `MaxEncodedLen`) + // Storage: `Xyk::LiquidityPools` (r:0 w:1) + // Proof: `Xyk::LiquidityPools` (`max_values`: None, `max_size`: Some(29), added: 2504, mode: `MaxEncodedLen`) + fn create_pool() -> Weight { + (Weight::from_parts(158_080_000, 0)) + .saturating_add(T::DbWeight::get().reads(14 as u64)) + .saturating_add(T::DbWeight::get().writes(12 as u64)) + } + // Storage: `Maintenance::MaintenanceStatus` (r:1 w:0) + // Proof: `Maintenance::MaintenanceStatus` (`max_values`: Some(1), `max_size`: Some(2), added: 497, mode: `MaxEncodedLen`) + // Storage: `AssetRegistry::Metadata` (r:2 w:0) + // Proof: `AssetRegistry::Metadata` (`max_values`: None, `max_size`: Some(153), added: 2628, mode: `MaxEncodedLen`) + // Storage: `Xyk::LiquidityAssets` (r:1 w:0) + // Proof: `Xyk::LiquidityAssets` (`max_values`: None, `max_size`: Some(29), added: 2504, mode: `MaxEncodedLen`) + // Storage: `Tokens::TotalIssuance` (r:1 w:0) + // Proof: `Tokens::TotalIssuance` (`max_values`: None, `max_size`: Some(28), added: 2503, mode: `MaxEncodedLen`) + // Storage: `Xyk::Pools` (r:3 w:1) + // Proof: `Xyk::Pools` (`max_values`: None, `max_size`: Some(56), added: 2531, mode: `MaxEncodedLen`) + // Storage: `Tokens::Accounts` (r:6 w:6) + // Proof: `Tokens::Accounts` (`max_values`: None, `max_size`: Some(96), added: 2571, mode: `MaxEncodedLen`) + // Storage: `System::Account` (r:2 w:2) + // Proof: `System::Account` (`max_values`: None, `max_size`: Some(52), added: 2527, mode: `MaxEncodedLen`) + // Storage: `Xyk::TotalNumberOfSwaps` (r:1 w:1) + // Proof: `Xyk::TotalNumberOfSwaps` (`max_values`: Some(1), `max_size`: Some(16), added: 511, mode: `MaxEncodedLen`) + fn sell_asset() -> Weight { + (Weight::from_parts(195_830_000, 0)) + .saturating_add(T::DbWeight::get().reads(17 as u64)) + .saturating_add(T::DbWeight::get().writes(10 as u64)) + } + // Storage: `Maintenance::MaintenanceStatus` (r:1 w:0) + // Proof: `Maintenance::MaintenanceStatus` (`max_values`: Some(1), `max_size`: Some(2), added: 497, mode: `MaxEncodedLen`) + // Storage: `Xyk::LiquidityAssets` (r:99 w:0) + // Proof: `Xyk::LiquidityAssets` (`max_values`: None, `max_size`: Some(29), added: 2504, mode: `MaxEncodedLen`) + // Storage: `Tokens::TotalIssuance` (r:100 w:1) + // Proof: `Tokens::TotalIssuance` (`max_values`: None, `max_size`: Some(28), added: 2503, mode: `MaxEncodedLen`) + // Storage: `AssetRegistry::Metadata` (r:100 w:0) + // Proof: `AssetRegistry::Metadata` (`max_values`: None, `max_size`: Some(153), added: 2628, mode: `MaxEncodedLen`) + // Storage: `Xyk::Pools` (r:297 w:198) + // Proof: `Xyk::Pools` (`max_values`: None, `max_size`: Some(56), added: 2531, mode: `MaxEncodedLen`) + // Storage: `Tokens::Accounts` (r:400 w:400) + // Proof: `Tokens::Accounts` (`max_values`: None, `max_size`: Some(96), added: 2571, mode: `MaxEncodedLen`) + // Storage: `System::Account` (r:2 w:2) + // Proof: `System::Account` (`max_values`: None, `max_size`: Some(52), added: 2527, mode: `MaxEncodedLen`) + // Storage: `Xyk::TotalNumberOfSwaps` (r:1 w:1) + // Proof: `Xyk::TotalNumberOfSwaps` (`max_values`: Some(1), `max_size`: Some(16), added: 511, mode: `MaxEncodedLen`) + fn multiswap_sell_asset(x: u32, ) -> Weight { + (Weight::from_parts(553_810_000, 0)) + // Standard Error: 468_521 + .saturating_add((Weight::from_parts(231_123_831, 0)).saturating_mul(x as u64)) + .saturating_add(T::DbWeight::get().reads((10 as u64).saturating_mul(x as u64))) + .saturating_add(T::DbWeight::get().writes(2 as u64)) + .saturating_add(T::DbWeight::get().writes((6 as u64).saturating_mul(x as u64))) + } + // Storage: `Maintenance::MaintenanceStatus` (r:1 w:0) + // Proof: `Maintenance::MaintenanceStatus` (`max_values`: Some(1), `max_size`: Some(2), added: 497, mode: `MaxEncodedLen`) + // Storage: `AssetRegistry::Metadata` (r:2 w:0) + // Proof: `AssetRegistry::Metadata` (`max_values`: None, `max_size`: Some(153), added: 2628, mode: `MaxEncodedLen`) + // Storage: `Xyk::LiquidityAssets` (r:2 w:0) + // Proof: `Xyk::LiquidityAssets` (`max_values`: None, `max_size`: Some(29), added: 2504, mode: `MaxEncodedLen`) + // Storage: `Tokens::TotalIssuance` (r:1 w:0) + // Proof: `Tokens::TotalIssuance` (`max_values`: None, `max_size`: Some(28), added: 2503, mode: `MaxEncodedLen`) + // Storage: `Xyk::Pools` (r:4 w:1) + // Proof: `Xyk::Pools` (`max_values`: None, `max_size`: Some(56), added: 2531, mode: `MaxEncodedLen`) + // Storage: `Tokens::Accounts` (r:6 w:6) + // Proof: `Tokens::Accounts` (`max_values`: None, `max_size`: Some(96), added: 2571, mode: `MaxEncodedLen`) + // Storage: `System::Account` (r:2 w:2) + // Proof: `System::Account` (`max_values`: None, `max_size`: Some(52), added: 2527, mode: `MaxEncodedLen`) + // Storage: `Xyk::TotalNumberOfSwaps` (r:1 w:1) + // Proof: `Xyk::TotalNumberOfSwaps` (`max_values`: Some(1), `max_size`: Some(16), added: 511, mode: `MaxEncodedLen`) + fn buy_asset() -> Weight { + (Weight::from_parts(208_920_000, 0)) + .saturating_add(T::DbWeight::get().reads(19 as u64)) + .saturating_add(T::DbWeight::get().writes(10 as u64)) + } + // Storage: `Maintenance::MaintenanceStatus` (r:1 w:0) + // Proof: `Maintenance::MaintenanceStatus` (`max_values`: Some(1), `max_size`: Some(2), added: 497, mode: `MaxEncodedLen`) + // Storage: `AssetRegistry::Metadata` (r:100 w:0) + // Proof: `AssetRegistry::Metadata` (`max_values`: None, `max_size`: Some(153), added: 2628, mode: `MaxEncodedLen`) + // Storage: `Xyk::LiquidityAssets` (r:99 w:0) + // Proof: `Xyk::LiquidityAssets` (`max_values`: None, `max_size`: Some(29), added: 2504, mode: `MaxEncodedLen`) + // Storage: `Tokens::TotalIssuance` (r:100 w:1) + // Proof: `Tokens::TotalIssuance` (`max_values`: None, `max_size`: Some(28), added: 2503, mode: `MaxEncodedLen`) + // Storage: `Xyk::Pools` (r:297 w:198) + // Proof: `Xyk::Pools` (`max_values`: None, `max_size`: Some(56), added: 2531, mode: `MaxEncodedLen`) + // Storage: `Tokens::Accounts` (r:400 w:400) + // Proof: `Tokens::Accounts` (`max_values`: None, `max_size`: Some(96), added: 2571, mode: `MaxEncodedLen`) + // Storage: `System::Account` (r:2 w:2) + // Proof: `System::Account` (`max_values`: None, `max_size`: Some(52), added: 2527, mode: `MaxEncodedLen`) + // Storage: `Xyk::TotalNumberOfSwaps` (r:1 w:1) + // Proof: `Xyk::TotalNumberOfSwaps` (`max_values`: Some(1), `max_size`: Some(16), added: 511, mode: `MaxEncodedLen`) + fn multiswap_buy_asset(x: u32, ) -> Weight { + (Weight::from_parts(610_590_000, 0)) + // Standard Error: 2_732_423 + .saturating_add((Weight::from_parts(261_025_951, 0)).saturating_mul(x as u64)) + .saturating_add(T::DbWeight::get().reads((10 as u64).saturating_mul(x as u64))) + .saturating_add(T::DbWeight::get().writes(2 as u64)) + .saturating_add(T::DbWeight::get().writes((6 as u64).saturating_mul(x as u64))) + } + // Storage: `AssetRegistry::Metadata` (r:2 w:0) + // Proof: `AssetRegistry::Metadata` (`max_values`: None, `max_size`: Some(153), added: 2628, mode: `MaxEncodedLen`) + // Storage: `Xyk::LiquidityAssets` (r:1 w:0) + // Proof: `Xyk::LiquidityAssets` (`max_values`: None, `max_size`: Some(29), added: 2504, mode: `MaxEncodedLen`) + // Storage: `Xyk::Pools` (r:1 w:1) + // Proof: `Xyk::Pools` (`max_values`: None, `max_size`: Some(56), added: 2531, mode: `MaxEncodedLen`) + // Storage: `Tokens::TotalIssuance` (r:1 w:1) + // Proof: `Tokens::TotalIssuance` (`max_values`: None, `max_size`: Some(28), added: 2503, mode: `MaxEncodedLen`) + // Storage: `Tokens::Accounts` (r:5 w:5) + // Proof: `Tokens::Accounts` (`max_values`: None, `max_size`: Some(96), added: 2571, mode: `MaxEncodedLen`) + // Storage: `Tokens::NextCurrencyId` (r:1 w:0) + // Proof: `Tokens::NextCurrencyId` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) + // Storage: `ProofOfStake::PromotedPoolRewards` (r:1 w:0) + // Proof: `ProofOfStake::PromotedPoolRewards` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + // Storage: `MultiPurposeLiquidity::ReserveStatus` (r:1 w:1) + // Proof: `MultiPurposeLiquidity::ReserveStatus` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`) + // Storage: `ProofOfStake::RewardsInfo` (r:1 w:1) + // Proof: `ProofOfStake::RewardsInfo` (`max_values`: None, `max_size`: None, mode: `Measured`) + // Storage: `ProofOfStake::TotalActivatedLiquidity` (r:1 w:1) + // Proof: `ProofOfStake::TotalActivatedLiquidity` (`max_values`: None, `max_size`: None, mode: `Measured`) + fn mint_liquidity() -> Weight { + (Weight::from_parts(210_530_000, 0)) + .saturating_add(T::DbWeight::get().reads(15 as u64)) + .saturating_add(T::DbWeight::get().writes(10 as u64)) + } + // Storage: `Xyk::LiquidityAssets` (r:1 w:0) + // Proof: `Xyk::LiquidityAssets` (`max_values`: None, `max_size`: Some(29), added: 2504, mode: `MaxEncodedLen`) + // Storage: `ProofOfStake::PromotedPoolRewards` (r:1 w:0) + // Proof: `ProofOfStake::PromotedPoolRewards` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + // Storage: `Vesting::Vesting` (r:2 w:2) + // Proof: `Vesting::Vesting` (`max_values`: None, `max_size`: Some(1857), added: 4332, mode: `MaxEncodedLen`) + // Storage: `Tokens::Locks` (r:2 w:2) + // Proof: `Tokens::Locks` (`max_values`: None, `max_size`: Some(1249), added: 3724, mode: `MaxEncodedLen`) + // Storage: `Tokens::Accounts` (r:5 w:5) + // Proof: `Tokens::Accounts` (`max_values`: None, `max_size`: Some(96), added: 2571, mode: `MaxEncodedLen`) + // Storage: `Xyk::Pools` (r:1 w:1) + // Proof: `Xyk::Pools` (`max_values`: None, `max_size`: Some(56), added: 2531, mode: `MaxEncodedLen`) + // Storage: `Tokens::TotalIssuance` (r:1 w:1) + // Proof: `Tokens::TotalIssuance` (`max_values`: None, `max_size`: Some(28), added: 2503, mode: `MaxEncodedLen`) + // Storage: `Tokens::NextCurrencyId` (r:1 w:0) + // Proof: `Tokens::NextCurrencyId` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) + fn mint_liquidity_using_vesting_native_tokens() -> Weight { + (Weight::from_parts(237_829_000, 0)) + .saturating_add(T::DbWeight::get().reads(14 as u64)) + .saturating_add(T::DbWeight::get().writes(11 as u64)) + } + // Storage: `AssetRegistry::Metadata` (r:2 w:0) + // Proof: `AssetRegistry::Metadata` (`max_values`: None, `max_size`: Some(153), added: 2628, mode: `MaxEncodedLen`) + // Storage: `Xyk::LiquidityAssets` (r:1 w:0) + // Proof: `Xyk::LiquidityAssets` (`max_values`: None, `max_size`: Some(29), added: 2504, mode: `MaxEncodedLen`) + // Storage: `MultiPurposeLiquidity::ReserveStatus` (r:1 w:1) + // Proof: `MultiPurposeLiquidity::ReserveStatus` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`) + // Storage: `Xyk::Pools` (r:1 w:1) + // Proof: `Xyk::Pools` (`max_values`: None, `max_size`: Some(56), added: 2531, mode: `MaxEncodedLen`) + // Storage: `Tokens::Accounts` (r:5 w:5) + // Proof: `Tokens::Accounts` (`max_values`: None, `max_size`: Some(96), added: 2571, mode: `MaxEncodedLen`) + // Storage: `ProofOfStake::PromotedPoolRewards` (r:1 w:0) + // Proof: `ProofOfStake::PromotedPoolRewards` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + // Storage: `ProofOfStake::RewardsInfo` (r:1 w:1) + // Proof: `ProofOfStake::RewardsInfo` (`max_values`: None, `max_size`: None, mode: `Measured`) + // Storage: `ProofOfStake::ActivatedNativeRewardsLiq` (r:1 w:0) + // Proof: `ProofOfStake::ActivatedNativeRewardsLiq` (`max_values`: None, `max_size`: None, mode: `Measured`) + // Storage: `ProofOfStake::TotalActivatedLiquidity` (r:1 w:1) + // Proof: `ProofOfStake::TotalActivatedLiquidity` (`max_values`: None, `max_size`: None, mode: `Measured`) + // Storage: `Tokens::TotalIssuance` (r:1 w:1) + // Proof: `Tokens::TotalIssuance` (`max_values`: None, `max_size`: Some(28), added: 2503, mode: `MaxEncodedLen`) + fn burn_liquidity() -> Weight { + (Weight::from_parts(197_420_000, 0)) + .saturating_add(T::DbWeight::get().reads(15 as u64)) + .saturating_add(T::DbWeight::get().writes(10 as u64)) + } + // Storage: `Xyk::LiquidityPools` (r:1 w:0) + // Proof: `Xyk::LiquidityPools` (`max_values`: None, `max_size`: Some(29), added: 2504, mode: `MaxEncodedLen`) + // Storage: `AssetRegistry::Metadata` (r:2 w:0) + // Proof: `AssetRegistry::Metadata` (`max_values`: None, `max_size`: Some(153), added: 2628, mode: `MaxEncodedLen`) + // Storage: `Xyk::LiquidityAssets` (r:2 w:0) + // Proof: `Xyk::LiquidityAssets` (`max_values`: None, `max_size`: Some(29), added: 2504, mode: `MaxEncodedLen`) + // Storage: `Tokens::TotalIssuance` (r:1 w:1) + // Proof: `Tokens::TotalIssuance` (`max_values`: None, `max_size`: Some(28), added: 2503, mode: `MaxEncodedLen`) + // Storage: `Xyk::Pools` (r:4 w:1) + // Proof: `Xyk::Pools` (`max_values`: None, `max_size`: Some(56), added: 2531, mode: `MaxEncodedLen`) + // Storage: `Tokens::Accounts` (r:7 w:7) + // Proof: `Tokens::Accounts` (`max_values`: None, `max_size`: Some(96), added: 2571, mode: `MaxEncodedLen`) + // Storage: `Maintenance::MaintenanceStatus` (r:1 w:0) + // Proof: `Maintenance::MaintenanceStatus` (`max_values`: Some(1), `max_size`: Some(2), added: 497, mode: `MaxEncodedLen`) + // Storage: `System::Account` (r:2 w:2) + // Proof: `System::Account` (`max_values`: None, `max_size`: Some(52), added: 2527, mode: `MaxEncodedLen`) + // Storage: `Tokens::NextCurrencyId` (r:1 w:0) + // Proof: `Tokens::NextCurrencyId` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) + // Storage: `ProofOfStake::PromotedPoolRewards` (r:1 w:0) + // Proof: `ProofOfStake::PromotedPoolRewards` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + fn provide_liquidity_with_conversion() -> Weight { + (Weight::from_parts(334_630_000, 0)) + .saturating_add(T::DbWeight::get().reads(22 as u64)) + .saturating_add(T::DbWeight::get().writes(11 as u64)) + } + // Storage: `Xyk::LiquidityPools` (r:1 w:0) + // Proof: `Xyk::LiquidityPools` (`max_values`: None, `max_size`: Some(29), added: 2504, mode: `MaxEncodedLen`) + // Storage: `AssetRegistry::Metadata` (r:2 w:0) + // Proof: `AssetRegistry::Metadata` (`max_values`: None, `max_size`: Some(153), added: 2628, mode: `MaxEncodedLen`) + // Storage: `ProofOfStake::PromotedPoolRewards` (r:1 w:0) + // Proof: `ProofOfStake::PromotedPoolRewards` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + // Storage: `ProofOfStake::RewardsInfo` (r:1 w:1) + // Proof: `ProofOfStake::RewardsInfo` (`max_values`: None, `max_size`: None, mode: `Measured`) + // Storage: `Tokens::Accounts` (r:8 w:8) + // Proof: `Tokens::Accounts` (`max_values`: None, `max_size`: Some(96), added: 2571, mode: `MaxEncodedLen`) + // Storage: `Xyk::LiquidityAssets` (r:2 w:0) + // Proof: `Xyk::LiquidityAssets` (`max_values`: None, `max_size`: Some(29), added: 2504, mode: `MaxEncodedLen`) + // Storage: `Tokens::TotalIssuance` (r:2 w:2) + // Proof: `Tokens::TotalIssuance` (`max_values`: None, `max_size`: Some(28), added: 2503, mode: `MaxEncodedLen`) + // Storage: `Xyk::Pools` (r:2 w:1) + // Proof: `Xyk::Pools` (`max_values`: None, `max_size`: Some(56), added: 2531, mode: `MaxEncodedLen`) + // Storage: `Maintenance::MaintenanceStatus` (r:1 w:0) + // Proof: `Maintenance::MaintenanceStatus` (`max_values`: Some(1), `max_size`: Some(2), added: 497, mode: `MaxEncodedLen`) + // Storage: `System::Account` (r:2 w:2) + // Proof: `System::Account` (`max_values`: None, `max_size`: Some(52), added: 2527, mode: `MaxEncodedLen`) + // Storage: `Tokens::NextCurrencyId` (r:1 w:0) + // Proof: `Tokens::NextCurrencyId` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) + // Storage: `MultiPurposeLiquidity::ReserveStatus` (r:1 w:1) + // Proof: `MultiPurposeLiquidity::ReserveStatus` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`) + // Storage: `ProofOfStake::TotalActivatedLiquidity` (r:1 w:1) + // Proof: `ProofOfStake::TotalActivatedLiquidity` (`max_values`: None, `max_size`: None, mode: `Measured`) + fn compound_rewards() -> Weight { + (Weight::from_parts(461_670_000, 0)) + .saturating_add(T::DbWeight::get().reads(25 as u64)) + .saturating_add(T::DbWeight::get().writes(16 as u64)) + } +} + +// For backwards compatibility and tests +impl WeightInfo for () { + // Storage: `AssetRegistry::Metadata` (r:3 w:1) + // Proof: `AssetRegistry::Metadata` (`max_values`: None, `max_size`: Some(153), added: 2628, mode: `MaxEncodedLen`) + // Storage: `Bootstrap::BootstrapSchedule` (r:1 w:0) + // Proof: `Bootstrap::BootstrapSchedule` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + // Storage: `Xyk::Pools` (r:2 w:1) + // Proof: `Xyk::Pools` (`max_values`: None, `max_size`: Some(56), added: 2531, mode: `MaxEncodedLen`) + // Storage: `Tokens::Accounts` (r:5 w:5) + // Proof: `Tokens::Accounts` (`max_values`: None, `max_size`: Some(96), added: 2571, mode: `MaxEncodedLen`) + // Storage: `System::Account` (r:1 w:1) + // Proof: `System::Account` (`max_values`: None, `max_size`: Some(52), added: 2527, mode: `MaxEncodedLen`) + // Storage: `Tokens::NextCurrencyId` (r:1 w:1) + // Proof: `Tokens::NextCurrencyId` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) + // Storage: `Tokens::TotalIssuance` (r:1 w:1) + // Proof: `Tokens::TotalIssuance` (`max_values`: None, `max_size`: Some(28), added: 2503, mode: `MaxEncodedLen`) + // Storage: `Xyk::LiquidityAssets` (r:0 w:1) + // Proof: `Xyk::LiquidityAssets` (`max_values`: None, `max_size`: Some(29), added: 2504, mode: `MaxEncodedLen`) + // Storage: `Xyk::LiquidityPools` (r:0 w:1) + // Proof: `Xyk::LiquidityPools` (`max_values`: None, `max_size`: Some(29), added: 2504, mode: `MaxEncodedLen`) + fn create_pool() -> Weight { + (Weight::from_parts(158_080_000, 0)) + .saturating_add(RocksDbWeight::get().reads(14 as u64)) + .saturating_add(RocksDbWeight::get().writes(12 as u64)) + } + // Storage: `Maintenance::MaintenanceStatus` (r:1 w:0) + // Proof: `Maintenance::MaintenanceStatus` (`max_values`: Some(1), `max_size`: Some(2), added: 497, mode: `MaxEncodedLen`) + // Storage: `AssetRegistry::Metadata` (r:2 w:0) + // Proof: `AssetRegistry::Metadata` (`max_values`: None, `max_size`: Some(153), added: 2628, mode: `MaxEncodedLen`) + // Storage: `Xyk::LiquidityAssets` (r:1 w:0) + // Proof: `Xyk::LiquidityAssets` (`max_values`: None, `max_size`: Some(29), added: 2504, mode: `MaxEncodedLen`) + // Storage: `Tokens::TotalIssuance` (r:1 w:0) + // Proof: `Tokens::TotalIssuance` (`max_values`: None, `max_size`: Some(28), added: 2503, mode: `MaxEncodedLen`) + // Storage: `Xyk::Pools` (r:3 w:1) + // Proof: `Xyk::Pools` (`max_values`: None, `max_size`: Some(56), added: 2531, mode: `MaxEncodedLen`) + // Storage: `Tokens::Accounts` (r:6 w:6) + // Proof: `Tokens::Accounts` (`max_values`: None, `max_size`: Some(96), added: 2571, mode: `MaxEncodedLen`) + // Storage: `System::Account` (r:2 w:2) + // Proof: `System::Account` (`max_values`: None, `max_size`: Some(52), added: 2527, mode: `MaxEncodedLen`) + // Storage: `Xyk::TotalNumberOfSwaps` (r:1 w:1) + // Proof: `Xyk::TotalNumberOfSwaps` (`max_values`: Some(1), `max_size`: Some(16), added: 511, mode: `MaxEncodedLen`) + fn sell_asset() -> Weight { + (Weight::from_parts(195_830_000, 0)) + .saturating_add(RocksDbWeight::get().reads(17 as u64)) + .saturating_add(RocksDbWeight::get().writes(10 as u64)) + } + // Storage: `Maintenance::MaintenanceStatus` (r:1 w:0) + // Proof: `Maintenance::MaintenanceStatus` (`max_values`: Some(1), `max_size`: Some(2), added: 497, mode: `MaxEncodedLen`) + // Storage: `Xyk::LiquidityAssets` (r:99 w:0) + // Proof: `Xyk::LiquidityAssets` (`max_values`: None, `max_size`: Some(29), added: 2504, mode: `MaxEncodedLen`) + // Storage: `Tokens::TotalIssuance` (r:100 w:1) + // Proof: `Tokens::TotalIssuance` (`max_values`: None, `max_size`: Some(28), added: 2503, mode: `MaxEncodedLen`) + // Storage: `AssetRegistry::Metadata` (r:100 w:0) + // Proof: `AssetRegistry::Metadata` (`max_values`: None, `max_size`: Some(153), added: 2628, mode: `MaxEncodedLen`) + // Storage: `Xyk::Pools` (r:297 w:198) + // Proof: `Xyk::Pools` (`max_values`: None, `max_size`: Some(56), added: 2531, mode: `MaxEncodedLen`) + // Storage: `Tokens::Accounts` (r:400 w:400) + // Proof: `Tokens::Accounts` (`max_values`: None, `max_size`: Some(96), added: 2571, mode: `MaxEncodedLen`) + // Storage: `System::Account` (r:2 w:2) + // Proof: `System::Account` (`max_values`: None, `max_size`: Some(52), added: 2527, mode: `MaxEncodedLen`) + // Storage: `Xyk::TotalNumberOfSwaps` (r:1 w:1) + // Proof: `Xyk::TotalNumberOfSwaps` (`max_values`: Some(1), `max_size`: Some(16), added: 511, mode: `MaxEncodedLen`) + fn multiswap_sell_asset(x: u32, ) -> Weight { + (Weight::from_parts(553_810_000, 0)) + // Standard Error: 468_521 + .saturating_add((Weight::from_parts(231_123_831, 0)).saturating_mul(x as u64)) + .saturating_add(RocksDbWeight::get().reads((10 as u64).saturating_mul(x as u64))) + .saturating_add(RocksDbWeight::get().writes(2 as u64)) + .saturating_add(RocksDbWeight::get().writes((6 as u64).saturating_mul(x as u64))) + } + // Storage: `Maintenance::MaintenanceStatus` (r:1 w:0) + // Proof: `Maintenance::MaintenanceStatus` (`max_values`: Some(1), `max_size`: Some(2), added: 497, mode: `MaxEncodedLen`) + // Storage: `AssetRegistry::Metadata` (r:2 w:0) + // Proof: `AssetRegistry::Metadata` (`max_values`: None, `max_size`: Some(153), added: 2628, mode: `MaxEncodedLen`) + // Storage: `Xyk::LiquidityAssets` (r:2 w:0) + // Proof: `Xyk::LiquidityAssets` (`max_values`: None, `max_size`: Some(29), added: 2504, mode: `MaxEncodedLen`) + // Storage: `Tokens::TotalIssuance` (r:1 w:0) + // Proof: `Tokens::TotalIssuance` (`max_values`: None, `max_size`: Some(28), added: 2503, mode: `MaxEncodedLen`) + // Storage: `Xyk::Pools` (r:4 w:1) + // Proof: `Xyk::Pools` (`max_values`: None, `max_size`: Some(56), added: 2531, mode: `MaxEncodedLen`) + // Storage: `Tokens::Accounts` (r:6 w:6) + // Proof: `Tokens::Accounts` (`max_values`: None, `max_size`: Some(96), added: 2571, mode: `MaxEncodedLen`) + // Storage: `System::Account` (r:2 w:2) + // Proof: `System::Account` (`max_values`: None, `max_size`: Some(52), added: 2527, mode: `MaxEncodedLen`) + // Storage: `Xyk::TotalNumberOfSwaps` (r:1 w:1) + // Proof: `Xyk::TotalNumberOfSwaps` (`max_values`: Some(1), `max_size`: Some(16), added: 511, mode: `MaxEncodedLen`) + fn buy_asset() -> Weight { + (Weight::from_parts(208_920_000, 0)) + .saturating_add(RocksDbWeight::get().reads(19 as u64)) + .saturating_add(RocksDbWeight::get().writes(10 as u64)) + } + // Storage: `Maintenance::MaintenanceStatus` (r:1 w:0) + // Proof: `Maintenance::MaintenanceStatus` (`max_values`: Some(1), `max_size`: Some(2), added: 497, mode: `MaxEncodedLen`) + // Storage: `AssetRegistry::Metadata` (r:100 w:0) + // Proof: `AssetRegistry::Metadata` (`max_values`: None, `max_size`: Some(153), added: 2628, mode: `MaxEncodedLen`) + // Storage: `Xyk::LiquidityAssets` (r:99 w:0) + // Proof: `Xyk::LiquidityAssets` (`max_values`: None, `max_size`: Some(29), added: 2504, mode: `MaxEncodedLen`) + // Storage: `Tokens::TotalIssuance` (r:100 w:1) + // Proof: `Tokens::TotalIssuance` (`max_values`: None, `max_size`: Some(28), added: 2503, mode: `MaxEncodedLen`) + // Storage: `Xyk::Pools` (r:297 w:198) + // Proof: `Xyk::Pools` (`max_values`: None, `max_size`: Some(56), added: 2531, mode: `MaxEncodedLen`) + // Storage: `Tokens::Accounts` (r:400 w:400) + // Proof: `Tokens::Accounts` (`max_values`: None, `max_size`: Some(96), added: 2571, mode: `MaxEncodedLen`) + // Storage: `System::Account` (r:2 w:2) + // Proof: `System::Account` (`max_values`: None, `max_size`: Some(52), added: 2527, mode: `MaxEncodedLen`) + // Storage: `Xyk::TotalNumberOfSwaps` (r:1 w:1) + // Proof: `Xyk::TotalNumberOfSwaps` (`max_values`: Some(1), `max_size`: Some(16), added: 511, mode: `MaxEncodedLen`) + fn multiswap_buy_asset(x: u32, ) -> Weight { + (Weight::from_parts(610_590_000, 0)) + // Standard Error: 2_732_423 + .saturating_add((Weight::from_parts(261_025_951, 0)).saturating_mul(x as u64)) + .saturating_add(RocksDbWeight::get().reads((10 as u64).saturating_mul(x as u64))) + .saturating_add(RocksDbWeight::get().writes(2 as u64)) + .saturating_add(RocksDbWeight::get().writes((6 as u64).saturating_mul(x as u64))) + } + // Storage: `AssetRegistry::Metadata` (r:2 w:0) + // Proof: `AssetRegistry::Metadata` (`max_values`: None, `max_size`: Some(153), added: 2628, mode: `MaxEncodedLen`) + // Storage: `Xyk::LiquidityAssets` (r:1 w:0) + // Proof: `Xyk::LiquidityAssets` (`max_values`: None, `max_size`: Some(29), added: 2504, mode: `MaxEncodedLen`) + // Storage: `Xyk::Pools` (r:1 w:1) + // Proof: `Xyk::Pools` (`max_values`: None, `max_size`: Some(56), added: 2531, mode: `MaxEncodedLen`) + // Storage: `Tokens::TotalIssuance` (r:1 w:1) + // Proof: `Tokens::TotalIssuance` (`max_values`: None, `max_size`: Some(28), added: 2503, mode: `MaxEncodedLen`) + // Storage: `Tokens::Accounts` (r:5 w:5) + // Proof: `Tokens::Accounts` (`max_values`: None, `max_size`: Some(96), added: 2571, mode: `MaxEncodedLen`) + // Storage: `Tokens::NextCurrencyId` (r:1 w:0) + // Proof: `Tokens::NextCurrencyId` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) + // Storage: `ProofOfStake::PromotedPoolRewards` (r:1 w:0) + // Proof: `ProofOfStake::PromotedPoolRewards` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + // Storage: `MultiPurposeLiquidity::ReserveStatus` (r:1 w:1) + // Proof: `MultiPurposeLiquidity::ReserveStatus` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`) + // Storage: `ProofOfStake::RewardsInfo` (r:1 w:1) + // Proof: `ProofOfStake::RewardsInfo` (`max_values`: None, `max_size`: None, mode: `Measured`) + // Storage: `ProofOfStake::TotalActivatedLiquidity` (r:1 w:1) + // Proof: `ProofOfStake::TotalActivatedLiquidity` (`max_values`: None, `max_size`: None, mode: `Measured`) + fn mint_liquidity() -> Weight { + (Weight::from_parts(210_530_000, 0)) + .saturating_add(RocksDbWeight::get().reads(15 as u64)) + .saturating_add(RocksDbWeight::get().writes(10 as u64)) + } + // Storage: `Xyk::LiquidityAssets` (r:1 w:0) + // Proof: `Xyk::LiquidityAssets` (`max_values`: None, `max_size`: Some(29), added: 2504, mode: `MaxEncodedLen`) + // Storage: `ProofOfStake::PromotedPoolRewards` (r:1 w:0) + // Proof: `ProofOfStake::PromotedPoolRewards` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + // Storage: `Vesting::Vesting` (r:2 w:2) + // Proof: `Vesting::Vesting` (`max_values`: None, `max_size`: Some(1857), added: 4332, mode: `MaxEncodedLen`) + // Storage: `Tokens::Locks` (r:2 w:2) + // Proof: `Tokens::Locks` (`max_values`: None, `max_size`: Some(1249), added: 3724, mode: `MaxEncodedLen`) + // Storage: `Tokens::Accounts` (r:5 w:5) + // Proof: `Tokens::Accounts` (`max_values`: None, `max_size`: Some(96), added: 2571, mode: `MaxEncodedLen`) + // Storage: `Xyk::Pools` (r:1 w:1) + // Proof: `Xyk::Pools` (`max_values`: None, `max_size`: Some(56), added: 2531, mode: `MaxEncodedLen`) + // Storage: `Tokens::TotalIssuance` (r:1 w:1) + // Proof: `Tokens::TotalIssuance` (`max_values`: None, `max_size`: Some(28), added: 2503, mode: `MaxEncodedLen`) + // Storage: `Tokens::NextCurrencyId` (r:1 w:0) + // Proof: `Tokens::NextCurrencyId` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) + fn mint_liquidity_using_vesting_native_tokens() -> Weight { + (Weight::from_parts(237_829_000, 0)) + .saturating_add(RocksDbWeight::get().reads(14 as u64)) + .saturating_add(RocksDbWeight::get().writes(11 as u64)) + } + // Storage: `AssetRegistry::Metadata` (r:2 w:0) + // Proof: `AssetRegistry::Metadata` (`max_values`: None, `max_size`: Some(153), added: 2628, mode: `MaxEncodedLen`) + // Storage: `Xyk::LiquidityAssets` (r:1 w:0) + // Proof: `Xyk::LiquidityAssets` (`max_values`: None, `max_size`: Some(29), added: 2504, mode: `MaxEncodedLen`) + // Storage: `MultiPurposeLiquidity::ReserveStatus` (r:1 w:1) + // Proof: `MultiPurposeLiquidity::ReserveStatus` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`) + // Storage: `Xyk::Pools` (r:1 w:1) + // Proof: `Xyk::Pools` (`max_values`: None, `max_size`: Some(56), added: 2531, mode: `MaxEncodedLen`) + // Storage: `Tokens::Accounts` (r:5 w:5) + // Proof: `Tokens::Accounts` (`max_values`: None, `max_size`: Some(96), added: 2571, mode: `MaxEncodedLen`) + // Storage: `ProofOfStake::PromotedPoolRewards` (r:1 w:0) + // Proof: `ProofOfStake::PromotedPoolRewards` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + // Storage: `ProofOfStake::RewardsInfo` (r:1 w:1) + // Proof: `ProofOfStake::RewardsInfo` (`max_values`: None, `max_size`: None, mode: `Measured`) + // Storage: `ProofOfStake::ActivatedNativeRewardsLiq` (r:1 w:0) + // Proof: `ProofOfStake::ActivatedNativeRewardsLiq` (`max_values`: None, `max_size`: None, mode: `Measured`) + // Storage: `ProofOfStake::TotalActivatedLiquidity` (r:1 w:1) + // Proof: `ProofOfStake::TotalActivatedLiquidity` (`max_values`: None, `max_size`: None, mode: `Measured`) + // Storage: `Tokens::TotalIssuance` (r:1 w:1) + // Proof: `Tokens::TotalIssuance` (`max_values`: None, `max_size`: Some(28), added: 2503, mode: `MaxEncodedLen`) + fn burn_liquidity() -> Weight { + (Weight::from_parts(197_420_000, 0)) + .saturating_add(RocksDbWeight::get().reads(15 as u64)) + .saturating_add(RocksDbWeight::get().writes(10 as u64)) + } + // Storage: `Xyk::LiquidityPools` (r:1 w:0) + // Proof: `Xyk::LiquidityPools` (`max_values`: None, `max_size`: Some(29), added: 2504, mode: `MaxEncodedLen`) + // Storage: `AssetRegistry::Metadata` (r:2 w:0) + // Proof: `AssetRegistry::Metadata` (`max_values`: None, `max_size`: Some(153), added: 2628, mode: `MaxEncodedLen`) + // Storage: `Xyk::LiquidityAssets` (r:2 w:0) + // Proof: `Xyk::LiquidityAssets` (`max_values`: None, `max_size`: Some(29), added: 2504, mode: `MaxEncodedLen`) + // Storage: `Tokens::TotalIssuance` (r:1 w:1) + // Proof: `Tokens::TotalIssuance` (`max_values`: None, `max_size`: Some(28), added: 2503, mode: `MaxEncodedLen`) + // Storage: `Xyk::Pools` (r:4 w:1) + // Proof: `Xyk::Pools` (`max_values`: None, `max_size`: Some(56), added: 2531, mode: `MaxEncodedLen`) + // Storage: `Tokens::Accounts` (r:7 w:7) + // Proof: `Tokens::Accounts` (`max_values`: None, `max_size`: Some(96), added: 2571, mode: `MaxEncodedLen`) + // Storage: `Maintenance::MaintenanceStatus` (r:1 w:0) + // Proof: `Maintenance::MaintenanceStatus` (`max_values`: Some(1), `max_size`: Some(2), added: 497, mode: `MaxEncodedLen`) + // Storage: `System::Account` (r:2 w:2) + // Proof: `System::Account` (`max_values`: None, `max_size`: Some(52), added: 2527, mode: `MaxEncodedLen`) + // Storage: `Tokens::NextCurrencyId` (r:1 w:0) + // Proof: `Tokens::NextCurrencyId` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) + // Storage: `ProofOfStake::PromotedPoolRewards` (r:1 w:0) + // Proof: `ProofOfStake::PromotedPoolRewards` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + fn provide_liquidity_with_conversion() -> Weight { + (Weight::from_parts(334_630_000, 0)) + .saturating_add(RocksDbWeight::get().reads(22 as u64)) + .saturating_add(RocksDbWeight::get().writes(11 as u64)) + } + // Storage: `Xyk::LiquidityPools` (r:1 w:0) + // Proof: `Xyk::LiquidityPools` (`max_values`: None, `max_size`: Some(29), added: 2504, mode: `MaxEncodedLen`) + // Storage: `AssetRegistry::Metadata` (r:2 w:0) + // Proof: `AssetRegistry::Metadata` (`max_values`: None, `max_size`: Some(153), added: 2628, mode: `MaxEncodedLen`) + // Storage: `ProofOfStake::PromotedPoolRewards` (r:1 w:0) + // Proof: `ProofOfStake::PromotedPoolRewards` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + // Storage: `ProofOfStake::RewardsInfo` (r:1 w:1) + // Proof: `ProofOfStake::RewardsInfo` (`max_values`: None, `max_size`: None, mode: `Measured`) + // Storage: `Tokens::Accounts` (r:8 w:8) + // Proof: `Tokens::Accounts` (`max_values`: None, `max_size`: Some(96), added: 2571, mode: `MaxEncodedLen`) + // Storage: `Xyk::LiquidityAssets` (r:2 w:0) + // Proof: `Xyk::LiquidityAssets` (`max_values`: None, `max_size`: Some(29), added: 2504, mode: `MaxEncodedLen`) + // Storage: `Tokens::TotalIssuance` (r:2 w:2) + // Proof: `Tokens::TotalIssuance` (`max_values`: None, `max_size`: Some(28), added: 2503, mode: `MaxEncodedLen`) + // Storage: `Xyk::Pools` (r:2 w:1) + // Proof: `Xyk::Pools` (`max_values`: None, `max_size`: Some(56), added: 2531, mode: `MaxEncodedLen`) + // Storage: `Maintenance::MaintenanceStatus` (r:1 w:0) + // Proof: `Maintenance::MaintenanceStatus` (`max_values`: Some(1), `max_size`: Some(2), added: 497, mode: `MaxEncodedLen`) + // Storage: `System::Account` (r:2 w:2) + // Proof: `System::Account` (`max_values`: None, `max_size`: Some(52), added: 2527, mode: `MaxEncodedLen`) + // Storage: `Tokens::NextCurrencyId` (r:1 w:0) + // Proof: `Tokens::NextCurrencyId` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) + // Storage: `MultiPurposeLiquidity::ReserveStatus` (r:1 w:1) + // Proof: `MultiPurposeLiquidity::ReserveStatus` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`) + // Storage: `ProofOfStake::TotalActivatedLiquidity` (r:1 w:1) + // Proof: `ProofOfStake::TotalActivatedLiquidity` (`max_values`: None, `max_size`: None, mode: `Measured`) + fn compound_rewards() -> Weight { + (Weight::from_parts(461_670_000, 0)) + .saturating_add(RocksDbWeight::get().reads(25 as u64)) + .saturating_add(RocksDbWeight::get().writes(16 as u64)) + } +} diff --git a/gasp-node/rollup/runtime/src/weights/parachain_staking.rs b/gasp-node/rollup/runtime/src/weights/parachain_staking.rs new file mode 100644 index 000000000..37d4bc87b --- /dev/null +++ b/gasp-node/rollup/runtime/src/weights/parachain_staking.rs @@ -0,0 +1,1139 @@ +// This file is part of Mangata. + +// Copyright (C) 2020-2022 Mangata Foundation. +// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 + +// This program 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. + +// This program 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 this program. If not, see . + +//! Autogenerated weights for parachain_staking +//! +//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 32.0.0 +//! DATE: 2024-11-29, STEPS: `50`, REPEAT: 20, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! EXECUTION: , WASM-EXECUTION: Compiled, CHAIN: Some("rollup-local"), DB CACHE: 1024 + +// Executed Command: +// target/release/rollup-node +// benchmark +// pallet +// -l=info,runtime::collective=warn,xyk=warn +// --chain +// rollup-local +// --wasm-execution +// compiled +// --pallet +// * +// --extrinsic +// * +// --steps +// 50 +// --repeat +// 20 +// --template +// ./templates/module-weight-template.hbs +// --output +// ./benchmarks/ + +#![cfg_attr(rustfmt, rustfmt_skip)] +#![allow(unused_parens)] +#![allow(unused_imports)] +#![allow(clippy::unnecessary_cast)] + +use frame_support::{traits::Get, weights::{Weight, constants::RocksDbWeight}}; +use sp_std::marker::PhantomData; + +/// Weight functions needed for parachain_staking. +pub trait WeightInfo { + fn set_total_selected() -> Weight; + fn set_collator_commission() -> Weight; + fn join_candidates(x: u32, y: u32, ) -> Weight; + fn schedule_leave_candidates(x: u32, ) -> Weight; + fn execute_leave_candidates(x: u32, ) -> Weight; + fn cancel_leave_candidates(x: u32, ) -> Weight; + fn go_offline() -> Weight; + fn go_online() -> Weight; + fn schedule_candidate_bond_more() -> Weight; + fn schedule_candidate_bond_less() -> Weight; + fn execute_candidate_bond_more() -> Weight; + fn execute_candidate_bond_less() -> Weight; + fn cancel_candidate_bond_more() -> Weight; + fn cancel_candidate_bond_less() -> Weight; + fn delegate(x: u32, y: u32, ) -> Weight; + fn schedule_leave_delegators() -> Weight; + fn execute_leave_delegators(x: u32, ) -> Weight; + fn cancel_leave_delegators() -> Weight; + fn schedule_revoke_delegation() -> Weight; + fn schedule_delegator_bond_more() -> Weight; + fn schedule_delegator_bond_less() -> Weight; + fn execute_revoke_delegation() -> Weight; + fn execute_delegator_bond_more() -> Weight; + fn execute_delegator_bond_less() -> Weight; + fn cancel_revoke_delegation() -> Weight; + fn cancel_delegator_bond_more() -> Weight; + fn cancel_delegator_bond_less() -> Weight; + fn add_staking_liquidity_token(x: u32, ) -> Weight; + fn remove_staking_liquidity_token(x: u32, ) -> Weight; + fn aggregator_update_metadata() -> Weight; + fn update_candidate_aggregator() -> Weight; + fn payout_collator_rewards() -> Weight; + fn payout_delegator_reward() -> Weight; + fn passive_session_change() -> Weight; + fn active_session_change(x: u32, y: u32, z: u32, ) -> Weight; +} + +/// Weights for parachain_staking using the Mangata node and recommended hardware. +pub struct ModuleWeight(PhantomData); +impl parachain_staking::WeightInfo for ModuleWeight { + // Storage: `ParachainStaking::TotalSelected` (r:1 w:1) + // Proof: `ParachainStaking::TotalSelected` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + fn set_total_selected() -> Weight { + (Weight::from_parts(12_320_000, 0)) + .saturating_add(T::DbWeight::get().reads(1 as u64)) + .saturating_add(T::DbWeight::get().writes(1 as u64)) + } + // Storage: `ParachainStaking::CollatorCommission` (r:1 w:1) + // Proof: `ParachainStaking::CollatorCommission` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + fn set_collator_commission() -> Weight { + (Weight::from_parts(12_351_000, 0)) + .saturating_add(T::DbWeight::get().reads(1 as u64)) + .saturating_add(T::DbWeight::get().writes(1 as u64)) + } + // Storage: `ParachainStaking::CandidateState` (r:1 w:1) + // Proof: `ParachainStaking::CandidateState` (`max_values`: None, `max_size`: None, mode: `Measured`) + // Storage: `ParachainStaking::DelegatorState` (r:1 w:0) + // Proof: `ParachainStaking::DelegatorState` (`max_values`: None, `max_size`: None, mode: `Measured`) + // Storage: `ParachainStaking::AggregatorMetadata` (r:1 w:0) + // Proof: `ParachainStaking::AggregatorMetadata` (`max_values`: None, `max_size`: None, mode: `Measured`) + // Storage: `ParachainStaking::StakingLiquidityTokens` (r:1 w:0) + // Proof: `ParachainStaking::StakingLiquidityTokens` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + // Storage: `Xyk::LiquidityPools` (r:1 w:0) + // Proof: `Xyk::LiquidityPools` (`max_values`: None, `max_size`: Some(29), added: 2504, mode: `MaxEncodedLen`) + // Storage: `Xyk::Pools` (r:1 w:0) + // Proof: `Xyk::Pools` (`max_values`: None, `max_size`: Some(56), added: 2531, mode: `MaxEncodedLen`) + // Storage: `Tokens::TotalIssuance` (r:1 w:0) + // Proof: `Tokens::TotalIssuance` (`max_values`: None, `max_size`: Some(28), added: 2503, mode: `MaxEncodedLen`) + // Storage: `ParachainStaking::CandidatePool` (r:1 w:1) + // Proof: `ParachainStaking::CandidatePool` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + // Storage: `MultiPurposeLiquidity::ReserveStatus` (r:1 w:1) + // Proof: `MultiPurposeLiquidity::ReserveStatus` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`) + // Storage: `Tokens::Accounts` (r:1 w:1) + // Proof: `Tokens::Accounts` (`max_values`: None, `max_size`: Some(96), added: 2571, mode: `MaxEncodedLen`) + // Storage: `ParachainStaking::Total` (r:1 w:1) + // Proof: `ParachainStaking::Total` (`max_values`: None, `max_size`: None, mode: `Measured`) + fn join_candidates(x: u32, y: u32, ) -> Weight { + (Weight::from_parts(116_564_834, 0)) + // Standard Error: 16_473 + .saturating_add((Weight::from_parts(213_562, 0)).saturating_mul(x as u64)) + // Standard Error: 16_273 + .saturating_add((Weight::from_parts(283_611, 0)).saturating_mul(y as u64)) + .saturating_add(T::DbWeight::get().reads(11 as u64)) + .saturating_add(T::DbWeight::get().writes(5 as u64)) + } + // Storage: `ParachainStaking::CandidateState` (r:1 w:1) + // Proof: `ParachainStaking::CandidateState` (`max_values`: None, `max_size`: None, mode: `Measured`) + // Storage: `ParachainStaking::Round` (r:1 w:0) + // Proof: `ParachainStaking::Round` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + // Storage: `ParachainStaking::CandidatePool` (r:1 w:1) + // Proof: `ParachainStaking::CandidatePool` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + fn schedule_leave_candidates(x: u32, ) -> Weight { + (Weight::from_parts(31_889_184, 0)) + // Standard Error: 5_233 + .saturating_add((Weight::from_parts(190_865, 0)).saturating_mul(x as u64)) + .saturating_add(T::DbWeight::get().reads(3 as u64)) + .saturating_add(T::DbWeight::get().writes(2 as u64)) + } + // Storage: `ParachainStaking::CandidateState` (r:1 w:1) + // Proof: `ParachainStaking::CandidateState` (`max_values`: None, `max_size`: None, mode: `Measured`) + // Storage: `ParachainStaking::Round` (r:1 w:0) + // Proof: `ParachainStaking::Round` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + // Storage: `MultiPurposeLiquidity::ReserveStatus` (r:30 w:30) + // Proof: `MultiPurposeLiquidity::ReserveStatus` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`) + // Storage: `Tokens::Accounts` (r:30 w:30) + // Proof: `Tokens::Accounts` (`max_values`: None, `max_size`: Some(96), added: 2571, mode: `MaxEncodedLen`) + // Storage: `ParachainStaking::DelegatorState` (r:29 w:29) + // Proof: `ParachainStaking::DelegatorState` (`max_values`: None, `max_size`: None, mode: `Measured`) + // Storage: `ParachainStaking::CandidateAggregator` (r:1 w:0) + // Proof: `ParachainStaking::CandidateAggregator` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + // Storage: `ParachainStaking::Total` (r:1 w:1) + // Proof: `ParachainStaking::Total` (`max_values`: None, `max_size`: None, mode: `Measured`) + fn execute_leave_candidates(x: u32, ) -> Weight { + (Weight::from_parts(102_215_060, 0)) + // Standard Error: 203_935 + .saturating_add((Weight::from_parts(24_857_739, 0)).saturating_mul(x as u64)) + .saturating_add(T::DbWeight::get().reads(3 as u64)) + .saturating_add(T::DbWeight::get().reads((3 as u64).saturating_mul(x as u64))) + .saturating_add(T::DbWeight::get().writes(1 as u64)) + .saturating_add(T::DbWeight::get().writes((3 as u64).saturating_mul(x as u64))) + } + // Storage: `ParachainStaking::CandidateState` (r:1 w:1) + // Proof: `ParachainStaking::CandidateState` (`max_values`: None, `max_size`: None, mode: `Measured`) + // Storage: `ParachainStaking::CandidatePool` (r:1 w:1) + // Proof: `ParachainStaking::CandidatePool` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + fn cancel_leave_candidates(x: u32, ) -> Weight { + (Weight::from_parts(30_887_318, 0)) + // Standard Error: 4_124 + .saturating_add((Weight::from_parts(137_216, 0)).saturating_mul(x as u64)) + .saturating_add(T::DbWeight::get().reads(2 as u64)) + .saturating_add(T::DbWeight::get().writes(2 as u64)) + } + // Storage: `ParachainStaking::CandidateState` (r:1 w:1) + // Proof: `ParachainStaking::CandidateState` (`max_values`: None, `max_size`: None, mode: `Measured`) + // Storage: `ParachainStaking::CandidatePool` (r:1 w:1) + // Proof: `ParachainStaking::CandidatePool` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + // Storage: `ParachainStaking::Round` (r:1 w:0) + // Proof: `ParachainStaking::Round` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + fn go_offline() -> Weight { + (Weight::from_parts(30_860_000, 0)) + .saturating_add(T::DbWeight::get().reads(3 as u64)) + .saturating_add(T::DbWeight::get().writes(2 as u64)) + } + // Storage: `ParachainStaking::CandidateState` (r:1 w:1) + // Proof: `ParachainStaking::CandidateState` (`max_values`: None, `max_size`: None, mode: `Measured`) + // Storage: `ParachainStaking::CandidatePool` (r:1 w:1) + // Proof: `ParachainStaking::CandidatePool` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + // Storage: `ParachainStaking::Round` (r:1 w:0) + // Proof: `ParachainStaking::Round` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + fn go_online() -> Weight { + (Weight::from_parts(30_280_000, 0)) + .saturating_add(T::DbWeight::get().reads(3 as u64)) + .saturating_add(T::DbWeight::get().writes(2 as u64)) + } + // Storage: `ParachainStaking::CandidateState` (r:1 w:1) + // Proof: `ParachainStaking::CandidateState` (`max_values`: None, `max_size`: None, mode: `Measured`) + // Storage: `MultiPurposeLiquidity::ReserveStatus` (r:1 w:0) + // Proof: `MultiPurposeLiquidity::ReserveStatus` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`) + // Storage: `Tokens::Accounts` (r:1 w:0) + // Proof: `Tokens::Accounts` (`max_values`: None, `max_size`: Some(96), added: 2571, mode: `MaxEncodedLen`) + // Storage: `ParachainStaking::Round` (r:1 w:0) + // Proof: `ParachainStaking::Round` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + fn schedule_candidate_bond_more() -> Weight { + (Weight::from_parts(47_650_000, 0)) + .saturating_add(T::DbWeight::get().reads(4 as u64)) + .saturating_add(T::DbWeight::get().writes(1 as u64)) + } + // Storage: `ParachainStaking::CandidateState` (r:1 w:1) + // Proof: `ParachainStaking::CandidateState` (`max_values`: None, `max_size`: None, mode: `Measured`) + // Storage: `Xyk::LiquidityPools` (r:1 w:0) + // Proof: `Xyk::LiquidityPools` (`max_values`: None, `max_size`: Some(29), added: 2504, mode: `MaxEncodedLen`) + // Storage: `Xyk::Pools` (r:1 w:0) + // Proof: `Xyk::Pools` (`max_values`: None, `max_size`: Some(56), added: 2531, mode: `MaxEncodedLen`) + // Storage: `Tokens::TotalIssuance` (r:1 w:0) + // Proof: `Tokens::TotalIssuance` (`max_values`: None, `max_size`: Some(28), added: 2503, mode: `MaxEncodedLen`) + // Storage: `ParachainStaking::Round` (r:1 w:0) + // Proof: `ParachainStaking::Round` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + fn schedule_candidate_bond_less() -> Weight { + (Weight::from_parts(49_030_000, 0)) + .saturating_add(T::DbWeight::get().reads(5 as u64)) + .saturating_add(T::DbWeight::get().writes(1 as u64)) + } + // Storage: `ParachainStaking::CandidateState` (r:1 w:1) + // Proof: `ParachainStaking::CandidateState` (`max_values`: None, `max_size`: None, mode: `Measured`) + // Storage: `ParachainStaking::Round` (r:1 w:0) + // Proof: `ParachainStaking::Round` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + // Storage: `MultiPurposeLiquidity::ReserveStatus` (r:1 w:1) + // Proof: `MultiPurposeLiquidity::ReserveStatus` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`) + // Storage: `Tokens::Accounts` (r:1 w:1) + // Proof: `Tokens::Accounts` (`max_values`: None, `max_size`: Some(96), added: 2571, mode: `MaxEncodedLen`) + // Storage: `ParachainStaking::Total` (r:1 w:1) + // Proof: `ParachainStaking::Total` (`max_values`: None, `max_size`: None, mode: `Measured`) + // Storage: `ParachainStaking::CandidatePool` (r:1 w:1) + // Proof: `ParachainStaking::CandidatePool` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + fn execute_candidate_bond_more() -> Weight { + (Weight::from_parts(92_090_000, 0)) + .saturating_add(T::DbWeight::get().reads(6 as u64)) + .saturating_add(T::DbWeight::get().writes(5 as u64)) + } + // Storage: `ParachainStaking::CandidateState` (r:1 w:1) + // Proof: `ParachainStaking::CandidateState` (`max_values`: None, `max_size`: None, mode: `Measured`) + // Storage: `ParachainStaking::Round` (r:1 w:0) + // Proof: `ParachainStaking::Round` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + // Storage: `MultiPurposeLiquidity::ReserveStatus` (r:1 w:1) + // Proof: `MultiPurposeLiquidity::ReserveStatus` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`) + // Storage: `Tokens::Accounts` (r:1 w:1) + // Proof: `Tokens::Accounts` (`max_values`: None, `max_size`: Some(96), added: 2571, mode: `MaxEncodedLen`) + // Storage: `ParachainStaking::Total` (r:1 w:1) + // Proof: `ParachainStaking::Total` (`max_values`: None, `max_size`: None, mode: `Measured`) + // Storage: `ParachainStaking::CandidatePool` (r:1 w:1) + // Proof: `ParachainStaking::CandidatePool` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + fn execute_candidate_bond_less() -> Weight { + (Weight::from_parts(77_700_000, 0)) + .saturating_add(T::DbWeight::get().reads(6 as u64)) + .saturating_add(T::DbWeight::get().writes(5 as u64)) + } + // Storage: `ParachainStaking::CandidateState` (r:1 w:1) + // Proof: `ParachainStaking::CandidateState` (`max_values`: None, `max_size`: None, mode: `Measured`) + fn cancel_candidate_bond_more() -> Weight { + (Weight::from_parts(24_720_000, 0)) + .saturating_add(T::DbWeight::get().reads(1 as u64)) + .saturating_add(T::DbWeight::get().writes(1 as u64)) + } + // Storage: `ParachainStaking::CandidateState` (r:1 w:1) + // Proof: `ParachainStaking::CandidateState` (`max_values`: None, `max_size`: None, mode: `Measured`) + fn cancel_candidate_bond_less() -> Weight { + (Weight::from_parts(24_040_000, 0)) + .saturating_add(T::DbWeight::get().reads(1 as u64)) + .saturating_add(T::DbWeight::get().writes(1 as u64)) + } + // Storage: `ParachainStaking::AggregatorMetadata` (r:1 w:0) + // Proof: `ParachainStaking::AggregatorMetadata` (`max_values`: None, `max_size`: None, mode: `Measured`) + // Storage: `ParachainStaking::CandidateState` (r:1 w:1) + // Proof: `ParachainStaking::CandidateState` (`max_values`: None, `max_size`: None, mode: `Measured`) + // Storage: `ParachainStaking::DelegatorState` (r:1 w:1) + // Proof: `ParachainStaking::DelegatorState` (`max_values`: None, `max_size`: None, mode: `Measured`) + // Storage: `Xyk::LiquidityPools` (r:1 w:0) + // Proof: `Xyk::LiquidityPools` (`max_values`: None, `max_size`: Some(29), added: 2504, mode: `MaxEncodedLen`) + // Storage: `Xyk::Pools` (r:1 w:0) + // Proof: `Xyk::Pools` (`max_values`: None, `max_size`: Some(56), added: 2531, mode: `MaxEncodedLen`) + // Storage: `Tokens::TotalIssuance` (r:1 w:0) + // Proof: `Tokens::TotalIssuance` (`max_values`: None, `max_size`: Some(28), added: 2503, mode: `MaxEncodedLen`) + // Storage: `MultiPurposeLiquidity::ReserveStatus` (r:1 w:1) + // Proof: `MultiPurposeLiquidity::ReserveStatus` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`) + // Storage: `Tokens::Accounts` (r:1 w:1) + // Proof: `Tokens::Accounts` (`max_values`: None, `max_size`: Some(96), added: 2571, mode: `MaxEncodedLen`) + // Storage: `ParachainStaking::CandidatePool` (r:1 w:1) + // Proof: `ParachainStaking::CandidatePool` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + // Storage: `ParachainStaking::Total` (r:1 w:1) + // Proof: `ParachainStaking::Total` (`max_values`: None, `max_size`: None, mode: `Measured`) + fn delegate(x: u32, y: u32, ) -> Weight { + (Weight::from_parts(105_936_341, 0)) + // Standard Error: 22_237 + .saturating_add((Weight::from_parts(491_007, 0)).saturating_mul(x as u64)) + // Standard Error: 21_489 + .saturating_add((Weight::from_parts(440_176, 0)).saturating_mul(y as u64)) + .saturating_add(T::DbWeight::get().reads(10 as u64)) + .saturating_add(T::DbWeight::get().writes(6 as u64)) + } + // Storage: `ParachainStaking::DelegatorState` (r:1 w:1) + // Proof: `ParachainStaking::DelegatorState` (`max_values`: None, `max_size`: None, mode: `Measured`) + // Storage: `ParachainStaking::Round` (r:1 w:0) + // Proof: `ParachainStaking::Round` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + fn schedule_leave_delegators() -> Weight { + (Weight::from_parts(25_450_000, 0)) + .saturating_add(T::DbWeight::get().reads(2 as u64)) + .saturating_add(T::DbWeight::get().writes(1 as u64)) + } + // Storage: `ParachainStaking::DelegatorState` (r:1 w:1) + // Proof: `ParachainStaking::DelegatorState` (`max_values`: None, `max_size`: None, mode: `Measured`) + // Storage: `ParachainStaking::Round` (r:1 w:0) + // Proof: `ParachainStaking::Round` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + // Storage: `ParachainStaking::CandidateState` (r:29 w:29) + // Proof: `ParachainStaking::CandidateState` (`max_values`: None, `max_size`: None, mode: `Measured`) + // Storage: `MultiPurposeLiquidity::ReserveStatus` (r:1 w:1) + // Proof: `MultiPurposeLiquidity::ReserveStatus` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`) + // Storage: `Tokens::Accounts` (r:1 w:1) + // Proof: `Tokens::Accounts` (`max_values`: None, `max_size`: Some(96), added: 2571, mode: `MaxEncodedLen`) + // Storage: `ParachainStaking::CandidatePool` (r:1 w:1) + // Proof: `ParachainStaking::CandidatePool` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + // Storage: `ParachainStaking::Total` (r:1 w:1) + // Proof: `ParachainStaking::Total` (`max_values`: None, `max_size`: None, mode: `Measured`) + fn execute_leave_delegators(x: u32, ) -> Weight { + (Weight::from_parts(51_574_939, 0)) + // Standard Error: 41_324 + .saturating_add((Weight::from_parts(27_903_261, 0)).saturating_mul(x as u64)) + .saturating_add(T::DbWeight::get().reads(5 as u64)) + .saturating_add(T::DbWeight::get().reads((1 as u64).saturating_mul(x as u64))) + .saturating_add(T::DbWeight::get().writes(4 as u64)) + .saturating_add(T::DbWeight::get().writes((1 as u64).saturating_mul(x as u64))) + } + // Storage: `ParachainStaking::DelegatorState` (r:1 w:1) + // Proof: `ParachainStaking::DelegatorState` (`max_values`: None, `max_size`: None, mode: `Measured`) + fn cancel_leave_delegators() -> Weight { + (Weight::from_parts(24_570_000, 0)) + .saturating_add(T::DbWeight::get().reads(1 as u64)) + .saturating_add(T::DbWeight::get().writes(1 as u64)) + } + // Storage: `ParachainStaking::DelegatorState` (r:1 w:1) + // Proof: `ParachainStaking::DelegatorState` (`max_values`: None, `max_size`: None, mode: `Measured`) + // Storage: `ParachainStaking::Round` (r:1 w:0) + // Proof: `ParachainStaking::Round` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + fn schedule_revoke_delegation() -> Weight { + (Weight::from_parts(26_329_000, 0)) + .saturating_add(T::DbWeight::get().reads(2 as u64)) + .saturating_add(T::DbWeight::get().writes(1 as u64)) + } + // Storage: `ParachainStaking::DelegatorState` (r:1 w:1) + // Proof: `ParachainStaking::DelegatorState` (`max_values`: None, `max_size`: None, mode: `Measured`) + // Storage: `MultiPurposeLiquidity::ReserveStatus` (r:1 w:0) + // Proof: `MultiPurposeLiquidity::ReserveStatus` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`) + // Storage: `Tokens::Accounts` (r:1 w:0) + // Proof: `Tokens::Accounts` (`max_values`: None, `max_size`: Some(96), added: 2571, mode: `MaxEncodedLen`) + // Storage: `ParachainStaking::Round` (r:1 w:0) + // Proof: `ParachainStaking::Round` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + fn schedule_delegator_bond_more() -> Weight { + (Weight::from_parts(47_440_000, 0)) + .saturating_add(T::DbWeight::get().reads(4 as u64)) + .saturating_add(T::DbWeight::get().writes(1 as u64)) + } + // Storage: `ParachainStaking::DelegatorState` (r:1 w:1) + // Proof: `ParachainStaking::DelegatorState` (`max_values`: None, `max_size`: None, mode: `Measured`) + // Storage: `ParachainStaking::Round` (r:1 w:0) + // Proof: `ParachainStaking::Round` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + fn schedule_delegator_bond_less() -> Weight { + (Weight::from_parts(26_290_000, 0)) + .saturating_add(T::DbWeight::get().reads(2 as u64)) + .saturating_add(T::DbWeight::get().writes(1 as u64)) + } + // Storage: `ParachainStaking::DelegatorState` (r:1 w:1) + // Proof: `ParachainStaking::DelegatorState` (`max_values`: None, `max_size`: None, mode: `Measured`) + // Storage: `ParachainStaking::Round` (r:1 w:0) + // Proof: `ParachainStaking::Round` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + // Storage: `ParachainStaking::CandidateState` (r:1 w:1) + // Proof: `ParachainStaking::CandidateState` (`max_values`: None, `max_size`: None, mode: `Measured`) + // Storage: `MultiPurposeLiquidity::ReserveStatus` (r:1 w:1) + // Proof: `MultiPurposeLiquidity::ReserveStatus` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`) + // Storage: `Tokens::Accounts` (r:1 w:1) + // Proof: `Tokens::Accounts` (`max_values`: None, `max_size`: Some(96), added: 2571, mode: `MaxEncodedLen`) + // Storage: `ParachainStaking::CandidatePool` (r:1 w:1) + // Proof: `ParachainStaking::CandidatePool` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + // Storage: `ParachainStaking::Total` (r:1 w:1) + // Proof: `ParachainStaking::Total` (`max_values`: None, `max_size`: None, mode: `Measured`) + fn execute_revoke_delegation() -> Weight { + (Weight::from_parts(111_760_000, 0)) + .saturating_add(T::DbWeight::get().reads(7 as u64)) + .saturating_add(T::DbWeight::get().writes(6 as u64)) + } + // Storage: `ParachainStaking::DelegatorState` (r:1 w:1) + // Proof: `ParachainStaking::DelegatorState` (`max_values`: None, `max_size`: None, mode: `Measured`) + // Storage: `ParachainStaking::Round` (r:1 w:0) + // Proof: `ParachainStaking::Round` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + // Storage: `ParachainStaking::CandidateState` (r:1 w:1) + // Proof: `ParachainStaking::CandidateState` (`max_values`: None, `max_size`: None, mode: `Measured`) + // Storage: `MultiPurposeLiquidity::ReserveStatus` (r:1 w:1) + // Proof: `MultiPurposeLiquidity::ReserveStatus` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`) + // Storage: `Tokens::Accounts` (r:1 w:1) + // Proof: `Tokens::Accounts` (`max_values`: None, `max_size`: Some(96), added: 2571, mode: `MaxEncodedLen`) + // Storage: `ParachainStaking::CandidatePool` (r:1 w:1) + // Proof: `ParachainStaking::CandidatePool` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + // Storage: `ParachainStaking::Total` (r:1 w:1) + // Proof: `ParachainStaking::Total` (`max_values`: None, `max_size`: None, mode: `Measured`) + fn execute_delegator_bond_more() -> Weight { + (Weight::from_parts(117_260_000, 0)) + .saturating_add(T::DbWeight::get().reads(7 as u64)) + .saturating_add(T::DbWeight::get().writes(6 as u64)) + } + // Storage: `ParachainStaking::DelegatorState` (r:1 w:1) + // Proof: `ParachainStaking::DelegatorState` (`max_values`: None, `max_size`: None, mode: `Measured`) + // Storage: `ParachainStaking::Round` (r:1 w:0) + // Proof: `ParachainStaking::Round` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + // Storage: `ParachainStaking::CandidateState` (r:1 w:1) + // Proof: `ParachainStaking::CandidateState` (`max_values`: None, `max_size`: None, mode: `Measured`) + // Storage: `MultiPurposeLiquidity::ReserveStatus` (r:1 w:1) + // Proof: `MultiPurposeLiquidity::ReserveStatus` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`) + // Storage: `Tokens::Accounts` (r:1 w:1) + // Proof: `Tokens::Accounts` (`max_values`: None, `max_size`: Some(96), added: 2571, mode: `MaxEncodedLen`) + // Storage: `ParachainStaking::CandidatePool` (r:1 w:1) + // Proof: `ParachainStaking::CandidatePool` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + // Storage: `ParachainStaking::Total` (r:1 w:1) + // Proof: `ParachainStaking::Total` (`max_values`: None, `max_size`: None, mode: `Measured`) + fn execute_delegator_bond_less() -> Weight { + (Weight::from_parts(106_450_000, 0)) + .saturating_add(T::DbWeight::get().reads(7 as u64)) + .saturating_add(T::DbWeight::get().writes(6 as u64)) + } + // Storage: `ParachainStaking::DelegatorState` (r:1 w:1) + // Proof: `ParachainStaking::DelegatorState` (`max_values`: None, `max_size`: None, mode: `Measured`) + fn cancel_revoke_delegation() -> Weight { + (Weight::from_parts(25_840_000, 0)) + .saturating_add(T::DbWeight::get().reads(1 as u64)) + .saturating_add(T::DbWeight::get().writes(1 as u64)) + } + // Storage: `ParachainStaking::DelegatorState` (r:1 w:1) + // Proof: `ParachainStaking::DelegatorState` (`max_values`: None, `max_size`: None, mode: `Measured`) + fn cancel_delegator_bond_more() -> Weight { + (Weight::from_parts(43_620_000, 0)) + .saturating_add(T::DbWeight::get().reads(1 as u64)) + .saturating_add(T::DbWeight::get().writes(1 as u64)) + } + // Storage: `ParachainStaking::DelegatorState` (r:1 w:1) + // Proof: `ParachainStaking::DelegatorState` (`max_values`: None, `max_size`: None, mode: `Measured`) + fn cancel_delegator_bond_less() -> Weight { + (Weight::from_parts(45_260_000, 0)) + .saturating_add(T::DbWeight::get().reads(1 as u64)) + .saturating_add(T::DbWeight::get().writes(1 as u64)) + } + // Storage: `Xyk::LiquidityPools` (r:1 w:0) + // Proof: `Xyk::LiquidityPools` (`max_values`: None, `max_size`: Some(29), added: 2504, mode: `MaxEncodedLen`) + // Storage: `ParachainStaking::StakingLiquidityTokens` (r:1 w:1) + // Proof: `ParachainStaking::StakingLiquidityTokens` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + fn add_staking_liquidity_token(x: u32, ) -> Weight { + (Weight::from_parts(27_991_165, 0)) + // Standard Error: 6_623 + .saturating_add((Weight::from_parts(211_946, 0)).saturating_mul(x as u64)) + .saturating_add(T::DbWeight::get().reads(2 as u64)) + .saturating_add(T::DbWeight::get().writes(1 as u64)) + } + // Storage: `ParachainStaking::StakingLiquidityTokens` (r:1 w:1) + // Proof: `ParachainStaking::StakingLiquidityTokens` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + fn remove_staking_liquidity_token(x: u32, ) -> Weight { + (Weight::from_parts(18_590_480, 0)) + // Standard Error: 5_707 + .saturating_add((Weight::from_parts(165_805, 0)).saturating_mul(x as u64)) + .saturating_add(T::DbWeight::get().reads(1 as u64)) + .saturating_add(T::DbWeight::get().writes(1 as u64)) + } + // Storage: `ParachainStaking::CandidateState` (r:99 w:0) + // Proof: `ParachainStaking::CandidateState` (`max_values`: None, `max_size`: None, mode: `Measured`) + // Storage: `ParachainStaking::DelegatorState` (r:1 w:0) + // Proof: `ParachainStaking::DelegatorState` (`max_values`: None, `max_size`: None, mode: `Measured`) + // Storage: `ParachainStaking::AggregatorMetadata` (r:1 w:1) + // Proof: `ParachainStaking::AggregatorMetadata` (`max_values`: None, `max_size`: None, mode: `Measured`) + // Storage: `ParachainStaking::CandidateAggregator` (r:1 w:1) + // Proof: `ParachainStaking::CandidateAggregator` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + fn aggregator_update_metadata() -> Weight { + (Weight::from_parts(1_355_640_000, 0)) + .saturating_add(T::DbWeight::get().reads(102 as u64)) + .saturating_add(T::DbWeight::get().writes(2 as u64)) + } + // Storage: `ParachainStaking::CandidateState` (r:1 w:0) + // Proof: `ParachainStaking::CandidateState` (`max_values`: None, `max_size`: None, mode: `Measured`) + // Storage: `ParachainStaking::CandidateAggregator` (r:1 w:1) + // Proof: `ParachainStaking::CandidateAggregator` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + // Storage: `ParachainStaking::AggregatorMetadata` (r:2 w:2) + // Proof: `ParachainStaking::AggregatorMetadata` (`max_values`: None, `max_size`: None, mode: `Measured`) + fn update_candidate_aggregator() -> Weight { + (Weight::from_parts(112_310_000, 0)) + .saturating_add(T::DbWeight::get().reads(4 as u64)) + .saturating_add(T::DbWeight::get().writes(3 as u64)) + } + // Storage: `ParachainStaking::RoundCollatorRewardInfo` (r:2 w:1) + // Proof: `ParachainStaking::RoundCollatorRewardInfo` (`max_values`: None, `max_size`: None, mode: `Measured`) + // Storage: `Tokens::Accounts` (r:32 w:32) + // Proof: `Tokens::Accounts` (`max_values`: None, `max_size`: Some(96), added: 2571, mode: `MaxEncodedLen`) + // Storage: `System::Account` (r:32 w:31) + // Proof: `System::Account` (`max_values`: None, `max_size`: Some(52), added: 2527, mode: `MaxEncodedLen`) + fn payout_collator_rewards() -> Weight { + (Weight::from_parts(949_290_000, 0)) + .saturating_add(T::DbWeight::get().reads(66 as u64)) + .saturating_add(T::DbWeight::get().writes(64 as u64)) + } + // Storage: `ParachainStaking::RoundCollatorRewardInfo` (r:1 w:1) + // Proof: `ParachainStaking::RoundCollatorRewardInfo` (`max_values`: None, `max_size`: None, mode: `Measured`) + // Storage: `Tokens::Accounts` (r:2 w:2) + // Proof: `Tokens::Accounts` (`max_values`: None, `max_size`: Some(96), added: 2571, mode: `MaxEncodedLen`) + // Storage: `System::Account` (r:2 w:1) + // Proof: `System::Account` (`max_values`: None, `max_size`: Some(52), added: 2527, mode: `MaxEncodedLen`) + fn payout_delegator_reward() -> Weight { + (Weight::from_parts(65_790_000, 0)) + .saturating_add(T::DbWeight::get().reads(5 as u64)) + .saturating_add(T::DbWeight::get().writes(4 as u64)) + } + // Storage: `ParachainStaking::Round` (r:1 w:0) + // Proof: `ParachainStaking::Round` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + fn passive_session_change() -> Weight { + (Weight::from_parts(5_770_000, 0)) + .saturating_add(T::DbWeight::get().reads(1 as u64)) + } + // Storage: `ParachainStaking::Round` (r:1 w:1) + // Proof: `ParachainStaking::Round` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + // Storage: `Session::CurrentIndex` (r:1 w:1) + // Proof: `Session::CurrentIndex` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + // Storage: `Session::QueuedChanged` (r:1 w:1) + // Proof: `Session::QueuedChanged` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + // Storage: `Session::QueuedKeys` (r:1 w:1) + // Proof: `Session::QueuedKeys` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + // Storage: `Session::DisabledValidators` (r:1 w:0) + // Proof: `Session::DisabledValidators` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + // Storage: `ParachainStaking::Points` (r:1 w:1) + // Proof: `ParachainStaking::Points` (`max_values`: None, `max_size`: None, mode: `Measured`) + // Storage: `Issuance::SessionIssuance` (r:1 w:1) + // Proof: `Issuance::SessionIssuance` (`max_values`: None, `max_size`: None, mode: `Measured`) + // Storage: `ParachainStaking::RoundAggregatorInfo` (r:1 w:2) + // Proof: `ParachainStaking::RoundAggregatorInfo` (`max_values`: None, `max_size`: None, mode: `Measured`) + // Storage: `ParachainStaking::AwardedPts` (r:52 w:51) + // Proof: `ParachainStaking::AwardedPts` (`max_values`: None, `max_size`: None, mode: `Measured`) + // Storage: `ParachainStaking::AtStake` (r:51 w:102) + // Proof: `ParachainStaking::AtStake` (`max_values`: None, `max_size`: None, mode: `Measured`) + // Storage: `ParachainStaking::CollatorCommission` (r:1 w:0) + // Proof: `ParachainStaking::CollatorCommission` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + // Storage: `SequencerStaking::Points` (r:1 w:0) + // Proof: `SequencerStaking::Points` (`max_values`: None, `max_size`: Some(16), added: 2491, mode: `MaxEncodedLen`) + // Storage: `ParachainStaking::StakingLiquidityTokens` (r:1 w:1) + // Proof: `ParachainStaking::StakingLiquidityTokens` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + // Storage: `Xyk::LiquidityPools` (r:100 w:0) + // Proof: `Xyk::LiquidityPools` (`max_values`: None, `max_size`: Some(29), added: 2504, mode: `MaxEncodedLen`) + // Storage: `Xyk::Pools` (r:100 w:0) + // Proof: `Xyk::Pools` (`max_values`: None, `max_size`: Some(56), added: 2531, mode: `MaxEncodedLen`) + // Storage: `Tokens::TotalIssuance` (r:101 w:1) + // Proof: `Tokens::TotalIssuance` (`max_values`: None, `max_size`: Some(28), added: 2503, mode: `MaxEncodedLen`) + // Storage: `ParachainStaking::CandidatePool` (r:1 w:0) + // Proof: `ParachainStaking::CandidatePool` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + // Storage: `ParachainStaking::CandidateAggregator` (r:1 w:0) + // Proof: `ParachainStaking::CandidateAggregator` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + // Storage: `ParachainStaking::TotalSelected` (r:1 w:0) + // Proof: `ParachainStaking::TotalSelected` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + // Storage: `ParachainStaking::CandidateState` (r:51 w:0) + // Proof: `ParachainStaking::CandidateState` (`max_values`: None, `max_size`: None, mode: `Measured`) + // Storage: `Issuance::IssuanceConfigStore` (r:1 w:0) + // Proof: `Issuance::IssuanceConfigStore` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + // Storage: `ProofOfStake::PromotedPoolRewards` (r:1 w:1) + // Proof: `ProofOfStake::PromotedPoolRewards` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + // Storage: `ProofOfStake::TotalActivatedLiquidity` (r:100 w:0) + // Proof: `ProofOfStake::TotalActivatedLiquidity` (`max_values`: None, `max_size`: None, mode: `Measured`) + // Storage: `Tokens::Accounts` (r:3 w:3) + // Proof: `Tokens::Accounts` (`max_values`: None, `max_size`: Some(96), added: 2571, mode: `MaxEncodedLen`) + // Storage: `Session::NextKeys` (r:51 w:0) + // Proof: `Session::NextKeys` (`max_values`: None, `max_size`: None, mode: `Measured`) + // Storage: `Aura::Authorities` (r:1 w:0) + // Proof: `Aura::Authorities` (`max_values`: Some(1), `max_size`: Some(3200004), added: 3200499, mode: `MaxEncodedLen`) + // Storage: `Grandpa::Stalled` (r:1 w:0) + // Proof: `Grandpa::Stalled` (`max_values`: Some(1), `max_size`: Some(8), added: 503, mode: `MaxEncodedLen`) + // Storage: `Grandpa::PendingChange` (r:1 w:0) + // Proof: `Grandpa::PendingChange` (`max_values`: Some(1), `max_size`: Some(1294), added: 1789, mode: `MaxEncodedLen`) + // Storage: `Grandpa::CurrentSetId` (r:1 w:0) + // Proof: `Grandpa::CurrentSetId` (`max_values`: Some(1), `max_size`: Some(8), added: 503, mode: `MaxEncodedLen`) + // Storage: `SequencerStaking::CurrentRound` (r:0 w:1) + // Proof: `SequencerStaking::CurrentRound` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) + // Storage: `Grandpa::SetIdSession` (r:0 w:1) + // Proof: `Grandpa::SetIdSession` (`max_values`: None, `max_size`: Some(20), added: 2495, mode: `MaxEncodedLen`) + // Storage: `ParachainStaking::SelectedCandidates` (r:0 w:1) + // Proof: `ParachainStaking::SelectedCandidates` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + // Storage: `ParachainStaking::RoundCollatorRewardInfo` (r:0 w:51) + // Proof: `ParachainStaking::RoundCollatorRewardInfo` (`max_values`: None, `max_size`: None, mode: `Measured`) + // Storage: `Session::Validators` (r:0 w:1) + // Proof: `Session::Validators` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + fn active_session_change(x: u32, y: u32, z: u32, ) -> Weight { + (Weight::from_parts(1_840_795_663, 0)) + // Standard Error: 237_951 + .saturating_add((Weight::from_parts(19_017_290, 0)).saturating_mul(x as u64)) + // Standard Error: 487_980 + .saturating_add((Weight::from_parts(4_274_143, 0)).saturating_mul(y as u64)) + // Standard Error: 845_240 + .saturating_add((Weight::from_parts(35_696_789, 0)).saturating_mul(z as u64)) + .saturating_add(T::DbWeight::get().reads(229 as u64)) + .saturating_add(T::DbWeight::get().reads((4 as u64).saturating_mul(x as u64))) + .saturating_add(T::DbWeight::get().writes(222 as u64)) + } +} + +// For backwards compatibility and tests +impl WeightInfo for () { + // Storage: `ParachainStaking::TotalSelected` (r:1 w:1) + // Proof: `ParachainStaking::TotalSelected` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + fn set_total_selected() -> Weight { + (Weight::from_parts(12_320_000, 0)) + .saturating_add(RocksDbWeight::get().reads(1 as u64)) + .saturating_add(RocksDbWeight::get().writes(1 as u64)) + } + // Storage: `ParachainStaking::CollatorCommission` (r:1 w:1) + // Proof: `ParachainStaking::CollatorCommission` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + fn set_collator_commission() -> Weight { + (Weight::from_parts(12_351_000, 0)) + .saturating_add(RocksDbWeight::get().reads(1 as u64)) + .saturating_add(RocksDbWeight::get().writes(1 as u64)) + } + // Storage: `ParachainStaking::CandidateState` (r:1 w:1) + // Proof: `ParachainStaking::CandidateState` (`max_values`: None, `max_size`: None, mode: `Measured`) + // Storage: `ParachainStaking::DelegatorState` (r:1 w:0) + // Proof: `ParachainStaking::DelegatorState` (`max_values`: None, `max_size`: None, mode: `Measured`) + // Storage: `ParachainStaking::AggregatorMetadata` (r:1 w:0) + // Proof: `ParachainStaking::AggregatorMetadata` (`max_values`: None, `max_size`: None, mode: `Measured`) + // Storage: `ParachainStaking::StakingLiquidityTokens` (r:1 w:0) + // Proof: `ParachainStaking::StakingLiquidityTokens` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + // Storage: `Xyk::LiquidityPools` (r:1 w:0) + // Proof: `Xyk::LiquidityPools` (`max_values`: None, `max_size`: Some(29), added: 2504, mode: `MaxEncodedLen`) + // Storage: `Xyk::Pools` (r:1 w:0) + // Proof: `Xyk::Pools` (`max_values`: None, `max_size`: Some(56), added: 2531, mode: `MaxEncodedLen`) + // Storage: `Tokens::TotalIssuance` (r:1 w:0) + // Proof: `Tokens::TotalIssuance` (`max_values`: None, `max_size`: Some(28), added: 2503, mode: `MaxEncodedLen`) + // Storage: `ParachainStaking::CandidatePool` (r:1 w:1) + // Proof: `ParachainStaking::CandidatePool` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + // Storage: `MultiPurposeLiquidity::ReserveStatus` (r:1 w:1) + // Proof: `MultiPurposeLiquidity::ReserveStatus` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`) + // Storage: `Tokens::Accounts` (r:1 w:1) + // Proof: `Tokens::Accounts` (`max_values`: None, `max_size`: Some(96), added: 2571, mode: `MaxEncodedLen`) + // Storage: `ParachainStaking::Total` (r:1 w:1) + // Proof: `ParachainStaking::Total` (`max_values`: None, `max_size`: None, mode: `Measured`) + fn join_candidates(x: u32, y: u32, ) -> Weight { + (Weight::from_parts(116_564_834, 0)) + // Standard Error: 16_473 + .saturating_add((Weight::from_parts(213_562, 0)).saturating_mul(x as u64)) + // Standard Error: 16_273 + .saturating_add((Weight::from_parts(283_611, 0)).saturating_mul(y as u64)) + .saturating_add(RocksDbWeight::get().reads(11 as u64)) + .saturating_add(RocksDbWeight::get().writes(5 as u64)) + } + // Storage: `ParachainStaking::CandidateState` (r:1 w:1) + // Proof: `ParachainStaking::CandidateState` (`max_values`: None, `max_size`: None, mode: `Measured`) + // Storage: `ParachainStaking::Round` (r:1 w:0) + // Proof: `ParachainStaking::Round` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + // Storage: `ParachainStaking::CandidatePool` (r:1 w:1) + // Proof: `ParachainStaking::CandidatePool` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + fn schedule_leave_candidates(x: u32, ) -> Weight { + (Weight::from_parts(31_889_184, 0)) + // Standard Error: 5_233 + .saturating_add((Weight::from_parts(190_865, 0)).saturating_mul(x as u64)) + .saturating_add(RocksDbWeight::get().reads(3 as u64)) + .saturating_add(RocksDbWeight::get().writes(2 as u64)) + } + // Storage: `ParachainStaking::CandidateState` (r:1 w:1) + // Proof: `ParachainStaking::CandidateState` (`max_values`: None, `max_size`: None, mode: `Measured`) + // Storage: `ParachainStaking::Round` (r:1 w:0) + // Proof: `ParachainStaking::Round` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + // Storage: `MultiPurposeLiquidity::ReserveStatus` (r:30 w:30) + // Proof: `MultiPurposeLiquidity::ReserveStatus` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`) + // Storage: `Tokens::Accounts` (r:30 w:30) + // Proof: `Tokens::Accounts` (`max_values`: None, `max_size`: Some(96), added: 2571, mode: `MaxEncodedLen`) + // Storage: `ParachainStaking::DelegatorState` (r:29 w:29) + // Proof: `ParachainStaking::DelegatorState` (`max_values`: None, `max_size`: None, mode: `Measured`) + // Storage: `ParachainStaking::CandidateAggregator` (r:1 w:0) + // Proof: `ParachainStaking::CandidateAggregator` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + // Storage: `ParachainStaking::Total` (r:1 w:1) + // Proof: `ParachainStaking::Total` (`max_values`: None, `max_size`: None, mode: `Measured`) + fn execute_leave_candidates(x: u32, ) -> Weight { + (Weight::from_parts(102_215_060, 0)) + // Standard Error: 203_935 + .saturating_add((Weight::from_parts(24_857_739, 0)).saturating_mul(x as u64)) + .saturating_add(RocksDbWeight::get().reads(3 as u64)) + .saturating_add(RocksDbWeight::get().reads((3 as u64).saturating_mul(x as u64))) + .saturating_add(RocksDbWeight::get().writes(1 as u64)) + .saturating_add(RocksDbWeight::get().writes((3 as u64).saturating_mul(x as u64))) + } + // Storage: `ParachainStaking::CandidateState` (r:1 w:1) + // Proof: `ParachainStaking::CandidateState` (`max_values`: None, `max_size`: None, mode: `Measured`) + // Storage: `ParachainStaking::CandidatePool` (r:1 w:1) + // Proof: `ParachainStaking::CandidatePool` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + fn cancel_leave_candidates(x: u32, ) -> Weight { + (Weight::from_parts(30_887_318, 0)) + // Standard Error: 4_124 + .saturating_add((Weight::from_parts(137_216, 0)).saturating_mul(x as u64)) + .saturating_add(RocksDbWeight::get().reads(2 as u64)) + .saturating_add(RocksDbWeight::get().writes(2 as u64)) + } + // Storage: `ParachainStaking::CandidateState` (r:1 w:1) + // Proof: `ParachainStaking::CandidateState` (`max_values`: None, `max_size`: None, mode: `Measured`) + // Storage: `ParachainStaking::CandidatePool` (r:1 w:1) + // Proof: `ParachainStaking::CandidatePool` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + // Storage: `ParachainStaking::Round` (r:1 w:0) + // Proof: `ParachainStaking::Round` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + fn go_offline() -> Weight { + (Weight::from_parts(30_860_000, 0)) + .saturating_add(RocksDbWeight::get().reads(3 as u64)) + .saturating_add(RocksDbWeight::get().writes(2 as u64)) + } + // Storage: `ParachainStaking::CandidateState` (r:1 w:1) + // Proof: `ParachainStaking::CandidateState` (`max_values`: None, `max_size`: None, mode: `Measured`) + // Storage: `ParachainStaking::CandidatePool` (r:1 w:1) + // Proof: `ParachainStaking::CandidatePool` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + // Storage: `ParachainStaking::Round` (r:1 w:0) + // Proof: `ParachainStaking::Round` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + fn go_online() -> Weight { + (Weight::from_parts(30_280_000, 0)) + .saturating_add(RocksDbWeight::get().reads(3 as u64)) + .saturating_add(RocksDbWeight::get().writes(2 as u64)) + } + // Storage: `ParachainStaking::CandidateState` (r:1 w:1) + // Proof: `ParachainStaking::CandidateState` (`max_values`: None, `max_size`: None, mode: `Measured`) + // Storage: `MultiPurposeLiquidity::ReserveStatus` (r:1 w:0) + // Proof: `MultiPurposeLiquidity::ReserveStatus` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`) + // Storage: `Tokens::Accounts` (r:1 w:0) + // Proof: `Tokens::Accounts` (`max_values`: None, `max_size`: Some(96), added: 2571, mode: `MaxEncodedLen`) + // Storage: `ParachainStaking::Round` (r:1 w:0) + // Proof: `ParachainStaking::Round` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + fn schedule_candidate_bond_more() -> Weight { + (Weight::from_parts(47_650_000, 0)) + .saturating_add(RocksDbWeight::get().reads(4 as u64)) + .saturating_add(RocksDbWeight::get().writes(1 as u64)) + } + // Storage: `ParachainStaking::CandidateState` (r:1 w:1) + // Proof: `ParachainStaking::CandidateState` (`max_values`: None, `max_size`: None, mode: `Measured`) + // Storage: `Xyk::LiquidityPools` (r:1 w:0) + // Proof: `Xyk::LiquidityPools` (`max_values`: None, `max_size`: Some(29), added: 2504, mode: `MaxEncodedLen`) + // Storage: `Xyk::Pools` (r:1 w:0) + // Proof: `Xyk::Pools` (`max_values`: None, `max_size`: Some(56), added: 2531, mode: `MaxEncodedLen`) + // Storage: `Tokens::TotalIssuance` (r:1 w:0) + // Proof: `Tokens::TotalIssuance` (`max_values`: None, `max_size`: Some(28), added: 2503, mode: `MaxEncodedLen`) + // Storage: `ParachainStaking::Round` (r:1 w:0) + // Proof: `ParachainStaking::Round` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + fn schedule_candidate_bond_less() -> Weight { + (Weight::from_parts(49_030_000, 0)) + .saturating_add(RocksDbWeight::get().reads(5 as u64)) + .saturating_add(RocksDbWeight::get().writes(1 as u64)) + } + // Storage: `ParachainStaking::CandidateState` (r:1 w:1) + // Proof: `ParachainStaking::CandidateState` (`max_values`: None, `max_size`: None, mode: `Measured`) + // Storage: `ParachainStaking::Round` (r:1 w:0) + // Proof: `ParachainStaking::Round` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + // Storage: `MultiPurposeLiquidity::ReserveStatus` (r:1 w:1) + // Proof: `MultiPurposeLiquidity::ReserveStatus` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`) + // Storage: `Tokens::Accounts` (r:1 w:1) + // Proof: `Tokens::Accounts` (`max_values`: None, `max_size`: Some(96), added: 2571, mode: `MaxEncodedLen`) + // Storage: `ParachainStaking::Total` (r:1 w:1) + // Proof: `ParachainStaking::Total` (`max_values`: None, `max_size`: None, mode: `Measured`) + // Storage: `ParachainStaking::CandidatePool` (r:1 w:1) + // Proof: `ParachainStaking::CandidatePool` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + fn execute_candidate_bond_more() -> Weight { + (Weight::from_parts(92_090_000, 0)) + .saturating_add(RocksDbWeight::get().reads(6 as u64)) + .saturating_add(RocksDbWeight::get().writes(5 as u64)) + } + // Storage: `ParachainStaking::CandidateState` (r:1 w:1) + // Proof: `ParachainStaking::CandidateState` (`max_values`: None, `max_size`: None, mode: `Measured`) + // Storage: `ParachainStaking::Round` (r:1 w:0) + // Proof: `ParachainStaking::Round` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + // Storage: `MultiPurposeLiquidity::ReserveStatus` (r:1 w:1) + // Proof: `MultiPurposeLiquidity::ReserveStatus` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`) + // Storage: `Tokens::Accounts` (r:1 w:1) + // Proof: `Tokens::Accounts` (`max_values`: None, `max_size`: Some(96), added: 2571, mode: `MaxEncodedLen`) + // Storage: `ParachainStaking::Total` (r:1 w:1) + // Proof: `ParachainStaking::Total` (`max_values`: None, `max_size`: None, mode: `Measured`) + // Storage: `ParachainStaking::CandidatePool` (r:1 w:1) + // Proof: `ParachainStaking::CandidatePool` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + fn execute_candidate_bond_less() -> Weight { + (Weight::from_parts(77_700_000, 0)) + .saturating_add(RocksDbWeight::get().reads(6 as u64)) + .saturating_add(RocksDbWeight::get().writes(5 as u64)) + } + // Storage: `ParachainStaking::CandidateState` (r:1 w:1) + // Proof: `ParachainStaking::CandidateState` (`max_values`: None, `max_size`: None, mode: `Measured`) + fn cancel_candidate_bond_more() -> Weight { + (Weight::from_parts(24_720_000, 0)) + .saturating_add(RocksDbWeight::get().reads(1 as u64)) + .saturating_add(RocksDbWeight::get().writes(1 as u64)) + } + // Storage: `ParachainStaking::CandidateState` (r:1 w:1) + // Proof: `ParachainStaking::CandidateState` (`max_values`: None, `max_size`: None, mode: `Measured`) + fn cancel_candidate_bond_less() -> Weight { + (Weight::from_parts(24_040_000, 0)) + .saturating_add(RocksDbWeight::get().reads(1 as u64)) + .saturating_add(RocksDbWeight::get().writes(1 as u64)) + } + // Storage: `ParachainStaking::AggregatorMetadata` (r:1 w:0) + // Proof: `ParachainStaking::AggregatorMetadata` (`max_values`: None, `max_size`: None, mode: `Measured`) + // Storage: `ParachainStaking::CandidateState` (r:1 w:1) + // Proof: `ParachainStaking::CandidateState` (`max_values`: None, `max_size`: None, mode: `Measured`) + // Storage: `ParachainStaking::DelegatorState` (r:1 w:1) + // Proof: `ParachainStaking::DelegatorState` (`max_values`: None, `max_size`: None, mode: `Measured`) + // Storage: `Xyk::LiquidityPools` (r:1 w:0) + // Proof: `Xyk::LiquidityPools` (`max_values`: None, `max_size`: Some(29), added: 2504, mode: `MaxEncodedLen`) + // Storage: `Xyk::Pools` (r:1 w:0) + // Proof: `Xyk::Pools` (`max_values`: None, `max_size`: Some(56), added: 2531, mode: `MaxEncodedLen`) + // Storage: `Tokens::TotalIssuance` (r:1 w:0) + // Proof: `Tokens::TotalIssuance` (`max_values`: None, `max_size`: Some(28), added: 2503, mode: `MaxEncodedLen`) + // Storage: `MultiPurposeLiquidity::ReserveStatus` (r:1 w:1) + // Proof: `MultiPurposeLiquidity::ReserveStatus` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`) + // Storage: `Tokens::Accounts` (r:1 w:1) + // Proof: `Tokens::Accounts` (`max_values`: None, `max_size`: Some(96), added: 2571, mode: `MaxEncodedLen`) + // Storage: `ParachainStaking::CandidatePool` (r:1 w:1) + // Proof: `ParachainStaking::CandidatePool` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + // Storage: `ParachainStaking::Total` (r:1 w:1) + // Proof: `ParachainStaking::Total` (`max_values`: None, `max_size`: None, mode: `Measured`) + fn delegate(x: u32, y: u32, ) -> Weight { + (Weight::from_parts(105_936_341, 0)) + // Standard Error: 22_237 + .saturating_add((Weight::from_parts(491_007, 0)).saturating_mul(x as u64)) + // Standard Error: 21_489 + .saturating_add((Weight::from_parts(440_176, 0)).saturating_mul(y as u64)) + .saturating_add(RocksDbWeight::get().reads(10 as u64)) + .saturating_add(RocksDbWeight::get().writes(6 as u64)) + } + // Storage: `ParachainStaking::DelegatorState` (r:1 w:1) + // Proof: `ParachainStaking::DelegatorState` (`max_values`: None, `max_size`: None, mode: `Measured`) + // Storage: `ParachainStaking::Round` (r:1 w:0) + // Proof: `ParachainStaking::Round` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + fn schedule_leave_delegators() -> Weight { + (Weight::from_parts(25_450_000, 0)) + .saturating_add(RocksDbWeight::get().reads(2 as u64)) + .saturating_add(RocksDbWeight::get().writes(1 as u64)) + } + // Storage: `ParachainStaking::DelegatorState` (r:1 w:1) + // Proof: `ParachainStaking::DelegatorState` (`max_values`: None, `max_size`: None, mode: `Measured`) + // Storage: `ParachainStaking::Round` (r:1 w:0) + // Proof: `ParachainStaking::Round` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + // Storage: `ParachainStaking::CandidateState` (r:29 w:29) + // Proof: `ParachainStaking::CandidateState` (`max_values`: None, `max_size`: None, mode: `Measured`) + // Storage: `MultiPurposeLiquidity::ReserveStatus` (r:1 w:1) + // Proof: `MultiPurposeLiquidity::ReserveStatus` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`) + // Storage: `Tokens::Accounts` (r:1 w:1) + // Proof: `Tokens::Accounts` (`max_values`: None, `max_size`: Some(96), added: 2571, mode: `MaxEncodedLen`) + // Storage: `ParachainStaking::CandidatePool` (r:1 w:1) + // Proof: `ParachainStaking::CandidatePool` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + // Storage: `ParachainStaking::Total` (r:1 w:1) + // Proof: `ParachainStaking::Total` (`max_values`: None, `max_size`: None, mode: `Measured`) + fn execute_leave_delegators(x: u32, ) -> Weight { + (Weight::from_parts(51_574_939, 0)) + // Standard Error: 41_324 + .saturating_add((Weight::from_parts(27_903_261, 0)).saturating_mul(x as u64)) + .saturating_add(RocksDbWeight::get().reads(5 as u64)) + .saturating_add(RocksDbWeight::get().reads((1 as u64).saturating_mul(x as u64))) + .saturating_add(RocksDbWeight::get().writes(4 as u64)) + .saturating_add(RocksDbWeight::get().writes((1 as u64).saturating_mul(x as u64))) + } + // Storage: `ParachainStaking::DelegatorState` (r:1 w:1) + // Proof: `ParachainStaking::DelegatorState` (`max_values`: None, `max_size`: None, mode: `Measured`) + fn cancel_leave_delegators() -> Weight { + (Weight::from_parts(24_570_000, 0)) + .saturating_add(RocksDbWeight::get().reads(1 as u64)) + .saturating_add(RocksDbWeight::get().writes(1 as u64)) + } + // Storage: `ParachainStaking::DelegatorState` (r:1 w:1) + // Proof: `ParachainStaking::DelegatorState` (`max_values`: None, `max_size`: None, mode: `Measured`) + // Storage: `ParachainStaking::Round` (r:1 w:0) + // Proof: `ParachainStaking::Round` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + fn schedule_revoke_delegation() -> Weight { + (Weight::from_parts(26_329_000, 0)) + .saturating_add(RocksDbWeight::get().reads(2 as u64)) + .saturating_add(RocksDbWeight::get().writes(1 as u64)) + } + // Storage: `ParachainStaking::DelegatorState` (r:1 w:1) + // Proof: `ParachainStaking::DelegatorState` (`max_values`: None, `max_size`: None, mode: `Measured`) + // Storage: `MultiPurposeLiquidity::ReserveStatus` (r:1 w:0) + // Proof: `MultiPurposeLiquidity::ReserveStatus` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`) + // Storage: `Tokens::Accounts` (r:1 w:0) + // Proof: `Tokens::Accounts` (`max_values`: None, `max_size`: Some(96), added: 2571, mode: `MaxEncodedLen`) + // Storage: `ParachainStaking::Round` (r:1 w:0) + // Proof: `ParachainStaking::Round` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + fn schedule_delegator_bond_more() -> Weight { + (Weight::from_parts(47_440_000, 0)) + .saturating_add(RocksDbWeight::get().reads(4 as u64)) + .saturating_add(RocksDbWeight::get().writes(1 as u64)) + } + // Storage: `ParachainStaking::DelegatorState` (r:1 w:1) + // Proof: `ParachainStaking::DelegatorState` (`max_values`: None, `max_size`: None, mode: `Measured`) + // Storage: `ParachainStaking::Round` (r:1 w:0) + // Proof: `ParachainStaking::Round` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + fn schedule_delegator_bond_less() -> Weight { + (Weight::from_parts(26_290_000, 0)) + .saturating_add(RocksDbWeight::get().reads(2 as u64)) + .saturating_add(RocksDbWeight::get().writes(1 as u64)) + } + // Storage: `ParachainStaking::DelegatorState` (r:1 w:1) + // Proof: `ParachainStaking::DelegatorState` (`max_values`: None, `max_size`: None, mode: `Measured`) + // Storage: `ParachainStaking::Round` (r:1 w:0) + // Proof: `ParachainStaking::Round` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + // Storage: `ParachainStaking::CandidateState` (r:1 w:1) + // Proof: `ParachainStaking::CandidateState` (`max_values`: None, `max_size`: None, mode: `Measured`) + // Storage: `MultiPurposeLiquidity::ReserveStatus` (r:1 w:1) + // Proof: `MultiPurposeLiquidity::ReserveStatus` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`) + // Storage: `Tokens::Accounts` (r:1 w:1) + // Proof: `Tokens::Accounts` (`max_values`: None, `max_size`: Some(96), added: 2571, mode: `MaxEncodedLen`) + // Storage: `ParachainStaking::CandidatePool` (r:1 w:1) + // Proof: `ParachainStaking::CandidatePool` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + // Storage: `ParachainStaking::Total` (r:1 w:1) + // Proof: `ParachainStaking::Total` (`max_values`: None, `max_size`: None, mode: `Measured`) + fn execute_revoke_delegation() -> Weight { + (Weight::from_parts(111_760_000, 0)) + .saturating_add(RocksDbWeight::get().reads(7 as u64)) + .saturating_add(RocksDbWeight::get().writes(6 as u64)) + } + // Storage: `ParachainStaking::DelegatorState` (r:1 w:1) + // Proof: `ParachainStaking::DelegatorState` (`max_values`: None, `max_size`: None, mode: `Measured`) + // Storage: `ParachainStaking::Round` (r:1 w:0) + // Proof: `ParachainStaking::Round` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + // Storage: `ParachainStaking::CandidateState` (r:1 w:1) + // Proof: `ParachainStaking::CandidateState` (`max_values`: None, `max_size`: None, mode: `Measured`) + // Storage: `MultiPurposeLiquidity::ReserveStatus` (r:1 w:1) + // Proof: `MultiPurposeLiquidity::ReserveStatus` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`) + // Storage: `Tokens::Accounts` (r:1 w:1) + // Proof: `Tokens::Accounts` (`max_values`: None, `max_size`: Some(96), added: 2571, mode: `MaxEncodedLen`) + // Storage: `ParachainStaking::CandidatePool` (r:1 w:1) + // Proof: `ParachainStaking::CandidatePool` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + // Storage: `ParachainStaking::Total` (r:1 w:1) + // Proof: `ParachainStaking::Total` (`max_values`: None, `max_size`: None, mode: `Measured`) + fn execute_delegator_bond_more() -> Weight { + (Weight::from_parts(117_260_000, 0)) + .saturating_add(RocksDbWeight::get().reads(7 as u64)) + .saturating_add(RocksDbWeight::get().writes(6 as u64)) + } + // Storage: `ParachainStaking::DelegatorState` (r:1 w:1) + // Proof: `ParachainStaking::DelegatorState` (`max_values`: None, `max_size`: None, mode: `Measured`) + // Storage: `ParachainStaking::Round` (r:1 w:0) + // Proof: `ParachainStaking::Round` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + // Storage: `ParachainStaking::CandidateState` (r:1 w:1) + // Proof: `ParachainStaking::CandidateState` (`max_values`: None, `max_size`: None, mode: `Measured`) + // Storage: `MultiPurposeLiquidity::ReserveStatus` (r:1 w:1) + // Proof: `MultiPurposeLiquidity::ReserveStatus` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`) + // Storage: `Tokens::Accounts` (r:1 w:1) + // Proof: `Tokens::Accounts` (`max_values`: None, `max_size`: Some(96), added: 2571, mode: `MaxEncodedLen`) + // Storage: `ParachainStaking::CandidatePool` (r:1 w:1) + // Proof: `ParachainStaking::CandidatePool` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + // Storage: `ParachainStaking::Total` (r:1 w:1) + // Proof: `ParachainStaking::Total` (`max_values`: None, `max_size`: None, mode: `Measured`) + fn execute_delegator_bond_less() -> Weight { + (Weight::from_parts(106_450_000, 0)) + .saturating_add(RocksDbWeight::get().reads(7 as u64)) + .saturating_add(RocksDbWeight::get().writes(6 as u64)) + } + // Storage: `ParachainStaking::DelegatorState` (r:1 w:1) + // Proof: `ParachainStaking::DelegatorState` (`max_values`: None, `max_size`: None, mode: `Measured`) + fn cancel_revoke_delegation() -> Weight { + (Weight::from_parts(25_840_000, 0)) + .saturating_add(RocksDbWeight::get().reads(1 as u64)) + .saturating_add(RocksDbWeight::get().writes(1 as u64)) + } + // Storage: `ParachainStaking::DelegatorState` (r:1 w:1) + // Proof: `ParachainStaking::DelegatorState` (`max_values`: None, `max_size`: None, mode: `Measured`) + fn cancel_delegator_bond_more() -> Weight { + (Weight::from_parts(43_620_000, 0)) + .saturating_add(RocksDbWeight::get().reads(1 as u64)) + .saturating_add(RocksDbWeight::get().writes(1 as u64)) + } + // Storage: `ParachainStaking::DelegatorState` (r:1 w:1) + // Proof: `ParachainStaking::DelegatorState` (`max_values`: None, `max_size`: None, mode: `Measured`) + fn cancel_delegator_bond_less() -> Weight { + (Weight::from_parts(45_260_000, 0)) + .saturating_add(RocksDbWeight::get().reads(1 as u64)) + .saturating_add(RocksDbWeight::get().writes(1 as u64)) + } + // Storage: `Xyk::LiquidityPools` (r:1 w:0) + // Proof: `Xyk::LiquidityPools` (`max_values`: None, `max_size`: Some(29), added: 2504, mode: `MaxEncodedLen`) + // Storage: `ParachainStaking::StakingLiquidityTokens` (r:1 w:1) + // Proof: `ParachainStaking::StakingLiquidityTokens` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + fn add_staking_liquidity_token(x: u32, ) -> Weight { + (Weight::from_parts(27_991_165, 0)) + // Standard Error: 6_623 + .saturating_add((Weight::from_parts(211_946, 0)).saturating_mul(x as u64)) + .saturating_add(RocksDbWeight::get().reads(2 as u64)) + .saturating_add(RocksDbWeight::get().writes(1 as u64)) + } + // Storage: `ParachainStaking::StakingLiquidityTokens` (r:1 w:1) + // Proof: `ParachainStaking::StakingLiquidityTokens` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + fn remove_staking_liquidity_token(x: u32, ) -> Weight { + (Weight::from_parts(18_590_480, 0)) + // Standard Error: 5_707 + .saturating_add((Weight::from_parts(165_805, 0)).saturating_mul(x as u64)) + .saturating_add(RocksDbWeight::get().reads(1 as u64)) + .saturating_add(RocksDbWeight::get().writes(1 as u64)) + } + // Storage: `ParachainStaking::CandidateState` (r:99 w:0) + // Proof: `ParachainStaking::CandidateState` (`max_values`: None, `max_size`: None, mode: `Measured`) + // Storage: `ParachainStaking::DelegatorState` (r:1 w:0) + // Proof: `ParachainStaking::DelegatorState` (`max_values`: None, `max_size`: None, mode: `Measured`) + // Storage: `ParachainStaking::AggregatorMetadata` (r:1 w:1) + // Proof: `ParachainStaking::AggregatorMetadata` (`max_values`: None, `max_size`: None, mode: `Measured`) + // Storage: `ParachainStaking::CandidateAggregator` (r:1 w:1) + // Proof: `ParachainStaking::CandidateAggregator` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + fn aggregator_update_metadata() -> Weight { + (Weight::from_parts(1_355_640_000, 0)) + .saturating_add(RocksDbWeight::get().reads(102 as u64)) + .saturating_add(RocksDbWeight::get().writes(2 as u64)) + } + // Storage: `ParachainStaking::CandidateState` (r:1 w:0) + // Proof: `ParachainStaking::CandidateState` (`max_values`: None, `max_size`: None, mode: `Measured`) + // Storage: `ParachainStaking::CandidateAggregator` (r:1 w:1) + // Proof: `ParachainStaking::CandidateAggregator` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + // Storage: `ParachainStaking::AggregatorMetadata` (r:2 w:2) + // Proof: `ParachainStaking::AggregatorMetadata` (`max_values`: None, `max_size`: None, mode: `Measured`) + fn update_candidate_aggregator() -> Weight { + (Weight::from_parts(112_310_000, 0)) + .saturating_add(RocksDbWeight::get().reads(4 as u64)) + .saturating_add(RocksDbWeight::get().writes(3 as u64)) + } + // Storage: `ParachainStaking::RoundCollatorRewardInfo` (r:2 w:1) + // Proof: `ParachainStaking::RoundCollatorRewardInfo` (`max_values`: None, `max_size`: None, mode: `Measured`) + // Storage: `Tokens::Accounts` (r:32 w:32) + // Proof: `Tokens::Accounts` (`max_values`: None, `max_size`: Some(96), added: 2571, mode: `MaxEncodedLen`) + // Storage: `System::Account` (r:32 w:31) + // Proof: `System::Account` (`max_values`: None, `max_size`: Some(52), added: 2527, mode: `MaxEncodedLen`) + fn payout_collator_rewards() -> Weight { + (Weight::from_parts(949_290_000, 0)) + .saturating_add(RocksDbWeight::get().reads(66 as u64)) + .saturating_add(RocksDbWeight::get().writes(64 as u64)) + } + // Storage: `ParachainStaking::RoundCollatorRewardInfo` (r:1 w:1) + // Proof: `ParachainStaking::RoundCollatorRewardInfo` (`max_values`: None, `max_size`: None, mode: `Measured`) + // Storage: `Tokens::Accounts` (r:2 w:2) + // Proof: `Tokens::Accounts` (`max_values`: None, `max_size`: Some(96), added: 2571, mode: `MaxEncodedLen`) + // Storage: `System::Account` (r:2 w:1) + // Proof: `System::Account` (`max_values`: None, `max_size`: Some(52), added: 2527, mode: `MaxEncodedLen`) + fn payout_delegator_reward() -> Weight { + (Weight::from_parts(65_790_000, 0)) + .saturating_add(RocksDbWeight::get().reads(5 as u64)) + .saturating_add(RocksDbWeight::get().writes(4 as u64)) + } + // Storage: `ParachainStaking::Round` (r:1 w:0) + // Proof: `ParachainStaking::Round` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + fn passive_session_change() -> Weight { + (Weight::from_parts(5_770_000, 0)) + .saturating_add(RocksDbWeight::get().reads(1 as u64)) + } + // Storage: `ParachainStaking::Round` (r:1 w:1) + // Proof: `ParachainStaking::Round` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + // Storage: `Session::CurrentIndex` (r:1 w:1) + // Proof: `Session::CurrentIndex` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + // Storage: `Session::QueuedChanged` (r:1 w:1) + // Proof: `Session::QueuedChanged` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + // Storage: `Session::QueuedKeys` (r:1 w:1) + // Proof: `Session::QueuedKeys` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + // Storage: `Session::DisabledValidators` (r:1 w:0) + // Proof: `Session::DisabledValidators` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + // Storage: `ParachainStaking::Points` (r:1 w:1) + // Proof: `ParachainStaking::Points` (`max_values`: None, `max_size`: None, mode: `Measured`) + // Storage: `Issuance::SessionIssuance` (r:1 w:1) + // Proof: `Issuance::SessionIssuance` (`max_values`: None, `max_size`: None, mode: `Measured`) + // Storage: `ParachainStaking::RoundAggregatorInfo` (r:1 w:2) + // Proof: `ParachainStaking::RoundAggregatorInfo` (`max_values`: None, `max_size`: None, mode: `Measured`) + // Storage: `ParachainStaking::AwardedPts` (r:52 w:51) + // Proof: `ParachainStaking::AwardedPts` (`max_values`: None, `max_size`: None, mode: `Measured`) + // Storage: `ParachainStaking::AtStake` (r:51 w:102) + // Proof: `ParachainStaking::AtStake` (`max_values`: None, `max_size`: None, mode: `Measured`) + // Storage: `ParachainStaking::CollatorCommission` (r:1 w:0) + // Proof: `ParachainStaking::CollatorCommission` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + // Storage: `SequencerStaking::Points` (r:1 w:0) + // Proof: `SequencerStaking::Points` (`max_values`: None, `max_size`: Some(16), added: 2491, mode: `MaxEncodedLen`) + // Storage: `ParachainStaking::StakingLiquidityTokens` (r:1 w:1) + // Proof: `ParachainStaking::StakingLiquidityTokens` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + // Storage: `Xyk::LiquidityPools` (r:100 w:0) + // Proof: `Xyk::LiquidityPools` (`max_values`: None, `max_size`: Some(29), added: 2504, mode: `MaxEncodedLen`) + // Storage: `Xyk::Pools` (r:100 w:0) + // Proof: `Xyk::Pools` (`max_values`: None, `max_size`: Some(56), added: 2531, mode: `MaxEncodedLen`) + // Storage: `Tokens::TotalIssuance` (r:101 w:1) + // Proof: `Tokens::TotalIssuance` (`max_values`: None, `max_size`: Some(28), added: 2503, mode: `MaxEncodedLen`) + // Storage: `ParachainStaking::CandidatePool` (r:1 w:0) + // Proof: `ParachainStaking::CandidatePool` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + // Storage: `ParachainStaking::CandidateAggregator` (r:1 w:0) + // Proof: `ParachainStaking::CandidateAggregator` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + // Storage: `ParachainStaking::TotalSelected` (r:1 w:0) + // Proof: `ParachainStaking::TotalSelected` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + // Storage: `ParachainStaking::CandidateState` (r:51 w:0) + // Proof: `ParachainStaking::CandidateState` (`max_values`: None, `max_size`: None, mode: `Measured`) + // Storage: `Issuance::IssuanceConfigStore` (r:1 w:0) + // Proof: `Issuance::IssuanceConfigStore` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + // Storage: `ProofOfStake::PromotedPoolRewards` (r:1 w:1) + // Proof: `ProofOfStake::PromotedPoolRewards` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + // Storage: `ProofOfStake::TotalActivatedLiquidity` (r:100 w:0) + // Proof: `ProofOfStake::TotalActivatedLiquidity` (`max_values`: None, `max_size`: None, mode: `Measured`) + // Storage: `Tokens::Accounts` (r:3 w:3) + // Proof: `Tokens::Accounts` (`max_values`: None, `max_size`: Some(96), added: 2571, mode: `MaxEncodedLen`) + // Storage: `Session::NextKeys` (r:51 w:0) + // Proof: `Session::NextKeys` (`max_values`: None, `max_size`: None, mode: `Measured`) + // Storage: `Aura::Authorities` (r:1 w:0) + // Proof: `Aura::Authorities` (`max_values`: Some(1), `max_size`: Some(3200004), added: 3200499, mode: `MaxEncodedLen`) + // Storage: `Grandpa::Stalled` (r:1 w:0) + // Proof: `Grandpa::Stalled` (`max_values`: Some(1), `max_size`: Some(8), added: 503, mode: `MaxEncodedLen`) + // Storage: `Grandpa::PendingChange` (r:1 w:0) + // Proof: `Grandpa::PendingChange` (`max_values`: Some(1), `max_size`: Some(1294), added: 1789, mode: `MaxEncodedLen`) + // Storage: `Grandpa::CurrentSetId` (r:1 w:0) + // Proof: `Grandpa::CurrentSetId` (`max_values`: Some(1), `max_size`: Some(8), added: 503, mode: `MaxEncodedLen`) + // Storage: `SequencerStaking::CurrentRound` (r:0 w:1) + // Proof: `SequencerStaking::CurrentRound` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) + // Storage: `Grandpa::SetIdSession` (r:0 w:1) + // Proof: `Grandpa::SetIdSession` (`max_values`: None, `max_size`: Some(20), added: 2495, mode: `MaxEncodedLen`) + // Storage: `ParachainStaking::SelectedCandidates` (r:0 w:1) + // Proof: `ParachainStaking::SelectedCandidates` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + // Storage: `ParachainStaking::RoundCollatorRewardInfo` (r:0 w:51) + // Proof: `ParachainStaking::RoundCollatorRewardInfo` (`max_values`: None, `max_size`: None, mode: `Measured`) + // Storage: `Session::Validators` (r:0 w:1) + // Proof: `Session::Validators` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + fn active_session_change(x: u32, y: u32, z: u32, ) -> Weight { + (Weight::from_parts(1_840_795_663, 0)) + // Standard Error: 237_951 + .saturating_add((Weight::from_parts(19_017_290, 0)).saturating_mul(x as u64)) + // Standard Error: 487_980 + .saturating_add((Weight::from_parts(4_274_143, 0)).saturating_mul(y as u64)) + // Standard Error: 845_240 + .saturating_add((Weight::from_parts(35_696_789, 0)).saturating_mul(z as u64)) + .saturating_add(RocksDbWeight::get().reads(229 as u64)) + .saturating_add(RocksDbWeight::get().reads((4 as u64).saturating_mul(x as u64))) + .saturating_add(RocksDbWeight::get().writes(222 as u64)) + } +} diff --git a/gasp-node/rpc/nonce/Cargo.toml b/gasp-node/rpc/nonce/Cargo.toml new file mode 100644 index 000000000..417807bba --- /dev/null +++ b/gasp-node/rpc/nonce/Cargo.toml @@ -0,0 +1,37 @@ +[package] +name = "mangata-rpc-nonce" +version = "4.0.0-dev" +authors = ["Parity Technologies "] +edition = "2018" +license = "Apache-2.0" +homepage = "https://substrate.io" +description = "FRAME's system exposed over Substrate RPC" +readme = "README.md" + +[package.metadata.docs.rs] +targets = ["x86_64-unknown-linux-gnu"] + +[dependencies] +codec = { workspace = true } +futures = { workspace = true } +jsonrpsee = { workspace = true, features = ["server"] } +log = { workspace = true } + +frame-system-rpc-runtime-api = { workspace = true } +sc-client-api = { workspace = true } +sc-rpc-api = { workspace = true } +sc-transaction-pool-api = { workspace = true } +sp-api = { workspace = true } +sp-blockchain = { workspace = true } +sp-block-builder = { workspace = true } +sp-core = { workspace = true } +sp-runtime = { workspace = true } +ver-api = { workspace = true } + +[dev-dependencies] +assert_matches.workspace = true +tokio.workspace = true + +sp-tracing = { workspace = true } +sc-transaction-pool = { workspace = true } +substrate-test-runtime-client = { workspace = true } diff --git a/gasp-node/rpc/nonce/README.md b/gasp-node/rpc/nonce/README.md new file mode 100644 index 000000000..38986983d --- /dev/null +++ b/gasp-node/rpc/nonce/README.md @@ -0,0 +1,3 @@ +System FRAME specific RPC methods. + +License: Apache-2.0 \ No newline at end of file diff --git a/gasp-node/rpc/nonce/src/lib.rs b/gasp-node/rpc/nonce/src/lib.rs new file mode 100644 index 000000000..43a8649a5 --- /dev/null +++ b/gasp-node/rpc/nonce/src/lib.rs @@ -0,0 +1,316 @@ +// This file is part of Substrate. + +// Copyright (C) 2019-2022 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. + +//! System FRAME specific RPC methods. + +use std::sync::Arc; + +use codec::{Codec, Decode, Encode}; +use jsonrpsee::{ + core::{async_trait, RpcResult}, + proc_macros::rpc, + types::error::ErrorObject, +}; +use sc_rpc_api::DenyUnsafe; +use sc_transaction_pool_api::{InPoolTransaction, TransactionPool}; +use sp_block_builder::BlockBuilder; +use sp_blockchain::HeaderBackend; +use sp_core::{hexdisplay::HexDisplay, Bytes}; +use sp_runtime::traits; + +pub use frame_system_rpc_runtime_api::AccountNonceApi; +use sc_client_api::BlockBackend; +use sp_api::ProvideRuntimeApi; + +use ver_api::VerNonceApi; + +/// System RPC methods. +#[rpc(client, server)] +pub trait SystemApi { + /// Returns the next valid index (aka nonce) for given account. + /// + /// This method takes into consideration all pending transactions + /// currently in the pool and if no transactions are found in the pool + /// it fallbacks to query the index from the runtime (aka. state nonce). + #[method(name = "system_accountNextIndex", aliases = ["account_nextIndex"])] + async fn nonce(&self, account: AccountId) -> RpcResult; + + /// Dry run an extrinsic at a given block. Return SCALE encoded ApplyExtrinsicResult. + #[method(name = "system_dryRun", aliases = ["system_dryRunAt"])] + async fn dry_run(&self, extrinsic: Bytes, at: Option) -> RpcResult; +} + +/// Error type of this RPC api. +pub enum Error { + /// The transaction was not decodable. + DecodeError, + /// The call to runtime failed. + RuntimeError, +} + +impl From for i32 { + fn from(e: Error) -> i32 { + match e { + Error::RuntimeError => 1, + Error::DecodeError => 2, + } + } +} + +/// An implementation of System-specific RPC methods on full client. +pub struct System { + client: Arc, + pool: Arc

, + deny_unsafe: DenyUnsafe, + _marker: std::marker::PhantomData, +} + +impl System { + /// Create new `FullSystem` given client and transaction pool. + pub fn new(client: Arc, pool: Arc

, deny_unsafe: DenyUnsafe) -> Self { + Self { client, pool, deny_unsafe, _marker: Default::default() } + } +} + +#[async_trait] +impl + SystemApiServer<::Hash, AccountId, Index> for System +where + C: sp_api::ProvideRuntimeApi, + C: HeaderBackend, + C: BlockBackend, + C: Send + Sync + 'static, + C: ProvideRuntimeApi, + C::Api: AccountNonceApi, + C::Api: BlockBuilder, + C::Api: VerNonceApi, + P: TransactionPool + 'static, + Block: traits::Block, + AccountId: Clone + std::fmt::Display + Codec + Send + 'static + std::cmp::PartialEq, + Index: Clone + std::fmt::Display + Codec + Send + traits::AtLeast32Bit + 'static, +{ + async fn nonce(&self, account: AccountId) -> RpcResult { + let api = self.client.runtime_api(); + let best = self.client.info().best_hash; + + let mut nonce = api.account_nonce(best, account.clone()).map_err(|e| { + ErrorObject::owned( + Error::RuntimeError.into(), + "Unable to query nonce.", + Some(e.to_string()), + ) + })?; + + let txs_in_queue = api.enqueued_txs_count(best, account.clone()).unwrap(); + for _ in 0..txs_in_queue { + nonce += traits::One::one(); + } + log::debug!(target: "rpc::nonce", "nonce for {} at block {} => {} ({} in queue)", account, best, nonce, txs_in_queue); + + Ok(adjust_nonce(&*self.pool, account, nonce)) + } + + async fn dry_run( + &self, + extrinsic: Bytes, + _at: Option<::Hash>, + ) -> RpcResult { + self.deny_unsafe.check_if_safe()?; + let api = self.client.runtime_api(); + let at = self.client.info().best_hash; + + let uxt: ::Extrinsic = + Decode::decode(&mut &*extrinsic).map_err(|e| { + ErrorObject::owned( + Error::DecodeError.into(), + "Unable to dry run extrinsic", + Some(e.to_string()), + ) + })?; + + let result = api.apply_extrinsic(at, uxt).map_err(|e| { + ErrorObject::owned( + Error::RuntimeError.into(), + "Unable to dry run extrinsic.", + Some(e.to_string()), + ) + })?; + + Ok(Encode::encode(&result).into()) + } +} + +/// Adjust account nonce from state, so that tx with the nonce will be +/// placed after all ready txpool transactions. +fn adjust_nonce(pool: &P, account: AccountId, nonce: Index) -> Index +where + P: TransactionPool, + AccountId: Clone + std::fmt::Display + Encode, + Index: Clone + std::fmt::Display + Encode + traits::AtLeast32Bit + 'static, +{ + log::debug!(target: "rpc", "State nonce for {}: {}", account, nonce); + // Now we need to query the transaction pool + // and find transactions originating from the same sender. + // + // Since extrinsics are opaque to us, we look for them using + // `provides` tag. And increment the nonce if we find a transaction + // that matches the current one. + let mut current_nonce = nonce.clone(); + let mut current_tag = (account.clone(), nonce).encode(); + for tx in pool.ready() { + log::debug!( + target: "rpc", + "Current nonce to {}, checking {} vs {:?}", + current_nonce, + HexDisplay::from(¤t_tag), + tx.provides().iter().map(|x| format!("{}", HexDisplay::from(x))).collect::>(), + ); + // since transactions in `ready()` need to be ordered by nonce + // it's fine to continue with current iterator. + if tx.provides().get(0) == Some(¤t_tag) { + current_nonce += traits::One::one(); + current_tag = (account.clone(), current_nonce.clone()).encode(); + } + } + + current_nonce +} + +#[cfg(test)] +mod tests { + use super::*; + + use assert_matches::assert_matches; + use futures::executor::block_on; + use sc_transaction_pool::BasicPool; + use sp_runtime::{ + transaction_validity::{InvalidTransaction, TransactionValidityError}, + ApplyExtrinsicResult, + }; + use substrate_test_runtime_client::{runtime::Transfer, AccountKeyring}; + + #[tokio::test] + async fn should_return_next_nonce_for_some_account() { + sp_tracing::try_init_simple(); + + // given + let client = Arc::new(substrate_test_runtime_client::new()); + let spawner = sp_core::testing::TaskExecutor::new(); + let pool = + BasicPool::new_full(Default::default(), true.into(), None, spawner, client.clone()); + + let source = sp_runtime::transaction_validity::TransactionSource::External; + let new_transaction = |nonce: u64| { + let t = Transfer { + from: AccountKeyring::Alice.into(), + to: AccountKeyring::Bob.into(), + amount: 5, + nonce, + }; + t.into_unchecked_extrinsic() + }; + let hash_of_block0 = client.info().genesis_hash; + // Populate the pool + let ext0 = new_transaction(0); + block_on(pool.submit_one(hash_of_block0, source, ext0)).unwrap(); + let ext1 = new_transaction(1); + block_on(pool.submit_one(hash_of_block0, source, ext1)).unwrap(); + + let accounts = System::new(client, pool, DenyUnsafe::Yes); + + // when + let nonce = accounts.nonce(AccountKeyring::Alice.into()).await; + + // then + assert_eq!(nonce.unwrap(), 2); + } + + #[tokio::test] + async fn dry_run_should_deny_unsafe() { + sp_tracing::try_init_simple(); + + // given + let client = Arc::new(substrate_test_runtime_client::new()); + let spawner = sp_core::testing::TaskExecutor::new(); + let pool = + BasicPool::new_full(Default::default(), true.into(), None, spawner, client.clone()); + + let accounts = System::new(client, pool, DenyUnsafe::Yes); + + // when + let res = accounts.dry_run(vec![].into(), None).await; + assert_matches!(res, Err(e) => { + assert!(e.message().contains("RPC call is unsafe to be called externally")); + }); + } + + #[tokio::test] + async fn dry_run_should_work() { + sp_tracing::try_init_simple(); + + // given + let client = Arc::new(substrate_test_runtime_client::new()); + let spawner = sp_core::testing::TaskExecutor::new(); + let pool = + BasicPool::new_full(Default::default(), true.into(), None, spawner, client.clone()); + + let accounts = System::new(client, pool, DenyUnsafe::No); + + let tx = Transfer { + from: AccountKeyring::Alice.into(), + to: AccountKeyring::Bob.into(), + amount: 5, + nonce: 0, + } + .into_unchecked_extrinsic(); + + // when + let bytes = accounts.dry_run(tx.encode().into(), None).await.expect("Call is successful"); + + // then + let apply_res: ApplyExtrinsicResult = Decode::decode(&mut bytes.as_ref()).unwrap(); + assert_eq!(apply_res, Ok(Ok(()))); + } + + #[tokio::test] + async fn dry_run_should_indicate_error() { + sp_tracing::try_init_simple(); + + // given + let client = Arc::new(substrate_test_runtime_client::new()); + let spawner = sp_core::testing::TaskExecutor::new(); + let pool = + BasicPool::new_full(Default::default(), true.into(), None, spawner, client.clone()); + + let accounts = System::new(client, pool, DenyUnsafe::No); + + let tx = Transfer { + from: AccountKeyring::Alice.into(), + to: AccountKeyring::Bob.into(), + amount: 5, + nonce: 100, + } + .into_unchecked_extrinsic(); + + // when + let bytes = accounts.dry_run(tx.encode().into(), None).await.expect("Call is successful"); + + // then + let apply_res: ApplyExtrinsicResult = Decode::decode(&mut bytes.as_ref()).unwrap(); + assert_eq!(apply_res, Err(TransactionValidityError::Invalid(InvalidTransaction::Future))); + } +} diff --git a/gasp-node/rust-toolchain.toml b/gasp-node/rust-toolchain.toml new file mode 100644 index 000000000..cb5ae6e1d --- /dev/null +++ b/gasp-node/rust-toolchain.toml @@ -0,0 +1,4 @@ +[toolchain] +channel = "nightly-2024-01-20" +components = [ "rustfmt", "clippy", "rust-src" ] +targets = [ "wasm32-unknown-unknown" ] \ No newline at end of file diff --git a/gasp-node/scripts/blake2-hash/index.js b/gasp-node/scripts/blake2-hash/index.js new file mode 100755 index 000000000..9231489e5 --- /dev/null +++ b/gasp-node/scripts/blake2-hash/index.js @@ -0,0 +1,26 @@ +#!/usr/bin/env node + +import _yargs from 'yargs'; +import { hideBin } from 'yargs/helpers'; +const yargs = _yargs(hideBin(process.argv)); +import { blake2AsHex } from '@polkadot/util-crypto'; +import fs from "fs"; + + +const main = async () => { + + const cli = yargs + .option("i", { + alias: "input", + type: "string", + demandOption: "The input is required.", + type: "string", + }) + + const { input } = cli.argv; + const wasm = fs.readFileSync(input); + const hash = blake2AsHex(wasm) + console.log(hash) +}; + +main().then(() => process.exit(0)); diff --git a/gasp-node/scripts/blake2-hash/package.json b/gasp-node/scripts/blake2-hash/package.json new file mode 100644 index 000000000..a0f97d29b --- /dev/null +++ b/gasp-node/scripts/blake2-hash/package.json @@ -0,0 +1,18 @@ +{ + "name": "mangata-crowdloan-tge", + "version": "1.0.0", + "description": "Script for tge and crowdloan rewards", + "main": "index.js", + "type": "module", + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1" + }, + "keywords": [], + "author": "Michal", + "license": "ISC", + "dependencies": { + "@polkadot/util-crypto": "^10.1.11", + "@types/yargs": "^17.0.13", + "yargs": "17.6.0" + } +} diff --git a/gasp-node/scripts/build-image.sh b/gasp-node/scripts/build-image.sh new file mode 100755 index 000000000..e633d8e7c --- /dev/null +++ b/gasp-node/scripts/build-image.sh @@ -0,0 +1,37 @@ +#!/bin/bash -xe +REPO_ROOT=$(readlink -f $(dirname $(dirname $(readlink -f $0)))) + +if [ -z "${SKIP_BUILD}" ]; then + ${REPO_ROOT}/docker-cargo.sh build --release +else + echo "build skipped because SKIP_BUILD flag is set" +fi +BINARY_PATH=${BINARY_PATH:-./docker-cargo/release/rollup-node} +GIT_REV=$(git -C ${REPO_ROOT} rev-parse HEAD) + +if git -C ${REPO_ROOT} diff --quiet HEAD; then + DOCKER_LABEL=${GIT_REV} +else + DOCKER_LABEL=${GIT_REV}-dirty +fi + +DOCKER_IMAGE_TAG=${@:-gaspxyz/rollup-node:local} +DOCKER_TAGS="" + +echo "building docker image ${DOCKER_IMAGE_TAG} with tags:" +for tag in ${DOCKER_IMAGE_TAG}; do + echo \t - ${tag} + DOCKER_TAGS="${DOCKER_TAGS} -t ${tag}" +done + +if [ ! -e ${BINARY_PATH} ]; then + echo "env variable 'BUILD_DIR' : ${BINARY_PATH} not found" >&2 + exit 1 +fi + +docker build \ + --build-arg BINARY_PATH=${BINARY_PATH} \ + --label "git_rev=${DOCKER_LABEL}" \ + ${DOCKER_TAGS} \ + -f ${REPO_ROOT}/Dockerfile \ + . diff --git a/gasp-node/scripts/dev_manifest.sh b/gasp-node/scripts/dev_manifest.sh new file mode 100755 index 000000000..642fa2d11 --- /dev/null +++ b/gasp-node/scripts/dev_manifest.sh @@ -0,0 +1,33 @@ +#!/bin/bash +REPO_ROOT=$(readlink -f $(dirname $(readlink -f $0))/..) +MANIFEST_PATH=$REPO_ROOT/Cargo.toml +LOCAL_SUBSTRATE_REPO_PATH=$1 + +if [ -z "$LOCAL_SUBSTRATE_REPO_PATH" ]; then + echo "usage $0 PATH_TO_SUBSTRATE_REPO" + exit -1 +fi + +# remove local changes to the file +# git -C $REPO_ROOT checkout Cargo.toml > /dev/null + +ALL_DEPENDENCIES=`cargo tree --manifest-path $REPO_ROOT/Cargo.toml --prefix=none | cut -d " " -f 1 | sort | uniq` + +echo "" >> $MANIFEST_PATH +echo "# patch generated by './scripts/dev_manifest.sh $LOCAL_SUBSTRATE_REPO_PATH'" >> $MANIFEST_PATH +echo "[patch.\"https://github.com/gasp-xyz/$(basename $LOCAL_SUBSTRATE_REPO_PATH)\"]" >> $MANIFEST_PATH + +# recursively find all packages from local repository and patch them temporarly +for i in `find $LOCAL_SUBSTRATE_REPO_PATH -name Cargo.toml`; do + MANIFEST_ABS_PATH=$(readlink -f $i) + git -C $LOCAL_SUBSTRATE_REPO_PATH ls-files --error-unmatch $MANIFEST_ABS_PATH &> /dev/null + if [ 0 -eq $? ] ; then + PACKAGE_PATH=`dirname $i` + PACKAGE_NAME=`sed -n 's/.*name.*=.*\"\(.*\)\"/\1/p' $i | head -1` + IS_DEPENDENCY=`echo $ALL_DEPENDENCIES| sed 's/ /\n/g' | grep "^$PACKAGE_NAME$"` + + if [ -n "$PACKAGE_NAME" ] && [ -n "$IS_DEPENDENCY" ]; then + echo "$PACKAGE_NAME = { path = \"$PACKAGE_PATH\" }" >> $MANIFEST_PATH + fi + fi +done diff --git a/gasp-node/scripts/run_benchmark.sh b/gasp-node/scripts/run_benchmark.sh new file mode 100755 index 000000000..3afe8238a --- /dev/null +++ b/gasp-node/scripts/run_benchmark.sh @@ -0,0 +1,16 @@ +#!/bin/bash +REPO_ROOT=$(dirname $(readlink -f $0))/../ + +mkdir ./benchmarks + +${REPO_ROOT}/target/release/rollup-node benchmark pallet \ + --chain rollup-local \ + --execution wasm \ + --wasm-execution compiled \ + --pallet $1 \ + --extrinsic "*" \ + --steps 2 \ + --repeat 2 \ + --output ./benchmarks/$1_weights.rs \ + --template ./templates/module-weight-template.hbs \ + &> ./benchmarks/benchmark_$1.txt diff --git a/gasp-node/scripts/run_benchmark_overhead.sh b/gasp-node/scripts/run_benchmark_overhead.sh new file mode 100755 index 000000000..69b6e880d --- /dev/null +++ b/gasp-node/scripts/run_benchmark_overhead.sh @@ -0,0 +1,12 @@ +#!/bin/bash +REPO_ROOT=$(dirname $(readlink -f $0))/../ + +mkdir ./benchmarks +cd ./benchmarks + +${REPO_ROOT}/target/release/rollup-node benchmark overhead \ + --chain rollup-local \ + -lblock_builder=debug \ + --max-ext-per-block 50000 \ + --base-path . \ + &>./overhead_benchmark.txt diff --git a/gasp-node/scripts/run_benchmarks.sh b/gasp-node/scripts/run_benchmarks.sh new file mode 100755 index 000000000..a63f38137 --- /dev/null +++ b/gasp-node/scripts/run_benchmarks.sh @@ -0,0 +1,33 @@ +#!/bin/bash +REPO_ROOT=$(dirname $(readlink -f $0))/../ + +rm -rf ./benchmarks +mkdir ./benchmarks + +benchmarks=( + "frame_system" + "pallet_session" + "pallet_timestamp" + "orml_tokens" + "parachain_staking" + "pallet_xyk" + "orml_asset_registry" + "pallet_treasury" + "pallet_collective_mangata" + "pallet_crowdloan_rewards" + "pallet_utility_mangata" + "pallet_vesting_mangata" + "pallet_issuance" + "pallet_bootstrap" + "pallet_multipurpose_liquidity" + "pallet_fee_lock" + "pallet_proof_of_stake" + "pallet_rolldown" + "pallet_market" +) + +for bench in ${benchmarks[@]}; do + ${REPO_ROOT}/scripts/run_benchmark.sh $bench +done + +# ${REPO_ROOT}/scripts/run_benchmark_overhead.sh diff --git a/gasp-node/templates/module-weight-template.hbs b/gasp-node/templates/module-weight-template.hbs new file mode 100644 index 000000000..a661bbdfd --- /dev/null +++ b/gasp-node/templates/module-weight-template.hbs @@ -0,0 +1,112 @@ +// This file is part of Mangata. + +// Copyright (C) 2020-2022 Mangata Foundation. +// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 + +// This program 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. + +// This program 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 this program. If not, see . + +//! Autogenerated weights for {{pallet}} +//! +//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION {{version}} +//! DATE: {{date}}, STEPS: `{{cmd.steps}}`, REPEAT: {{cmd.repeat}}, LOW RANGE: `{{cmd.lowest_range_values}}`, HIGH RANGE: `{{cmd.highest_range_values}}` +//! EXECUTION: {{cmd.execution}}, WASM-EXECUTION: {{cmd.wasm_execution}}, CHAIN: {{cmd.chain}}, DB CACHE: {{cmd.db_cache}} + +// Executed Command: +{{#each args as |arg|~}} +// {{arg}} +{{/each}} + +#![cfg_attr(rustfmt, rustfmt_skip)] +#![allow(unused_parens)] +#![allow(unused_imports)] +#![allow(clippy::unnecessary_cast)] + +use frame_support::{traits::Get, weights::{Weight, constants::RocksDbWeight}}; +use sp_std::marker::PhantomData; + +/// Weight functions needed for {{pallet}}. +pub trait WeightInfo { + {{#each benchmarks as |benchmark|}} + fn {{benchmark.name~}} + ( + {{~#each benchmark.components as |c| ~}} + {{c.name}}: u32, {{/each~}} + ) -> Weight; + {{/each}} +} + +/// Weights for {{pallet}} using the Mangata node and recommended hardware. +pub struct ModuleWeight(PhantomData); +impl {{pallet}}::WeightInfo for ModuleWeight { + {{#each benchmarks as |benchmark|}} + {{#each benchmark.comments as |comment|}} + // {{comment}} + {{/each}} + fn {{benchmark.name~}} + ( + {{~#each benchmark.components as |c| ~}} + {{~#if (not c.is_used)}}_{{/if}}{{c.name}}: u32, {{/each~}} + ) -> Weight { + (Weight::from_parts({{underscore benchmark.base_weight}}, 0)) + {{#each benchmark.component_weight as |cw|}} + // Standard Error: {{underscore cw.error}} + .saturating_add((Weight::from_parts({{underscore cw.slope}}, 0)).saturating_mul({{cw.name}} as u64)) + {{/each}} + {{#if (ne benchmark.base_reads "0")}} + .saturating_add(T::DbWeight::get().reads({{benchmark.base_reads}} as u64)) + {{/if}} + {{#each benchmark.component_reads as |cr|}} + .saturating_add(T::DbWeight::get().reads(({{cr.slope}} as u64).saturating_mul({{cr.name}} as u64))) + {{/each}} + {{#if (ne benchmark.base_writes "0")}} + .saturating_add(T::DbWeight::get().writes({{benchmark.base_writes}} as u64)) + {{/if}} + {{#each benchmark.component_writes as |cw|}} + .saturating_add(T::DbWeight::get().writes(({{cw.slope}} as u64).saturating_mul({{cw.name}} as u64))) + {{/each}} + } + {{/each}} +} + +// For backwards compatibility and tests +impl WeightInfo for () { + {{#each benchmarks as |benchmark|}} + {{#each benchmark.comments as |comment|}} + // {{comment}} + {{/each}} + fn {{benchmark.name~}} + ( + {{~#each benchmark.components as |c| ~}} + {{~#if (not c.is_used)}}_{{/if}}{{c.name}}: u32, {{/each~}} + ) -> Weight { + (Weight::from_parts({{underscore benchmark.base_weight}}, 0)) + {{#each benchmark.component_weight as |cw|}} + // Standard Error: {{underscore cw.error}} + .saturating_add((Weight::from_parts({{underscore cw.slope}}, 0)).saturating_mul({{cw.name}} as u64)) + {{/each}} + {{#if (ne benchmark.base_reads "0")}} + .saturating_add(RocksDbWeight::get().reads({{benchmark.base_reads}} as u64)) + {{/if}} + {{#each benchmark.component_reads as |cr|}} + .saturating_add(RocksDbWeight::get().reads(({{cr.slope}} as u64).saturating_mul({{cr.name}} as u64))) + {{/each}} + {{#if (ne benchmark.base_writes "0")}} + .saturating_add(RocksDbWeight::get().writes({{benchmark.base_writes}} as u64)) + {{/if}} + {{#each benchmark.component_writes as |cw|}} + .saturating_add(RocksDbWeight::get().writes(({{cw.slope}} as u64).saturating_mul({{cw.name}} as u64))) + {{/each}} + } + {{/each}} +}